POST/api/v1/renders
Render one PDF from HTML and a row of data. Responds with the PDF bytes.
- •html is a template; data fills its placeholders. page_settings takes the app page-setup keys: page_size (a4, a5, a3, letter, legal, tabloid), orientation, margins (top/right/bottom/left in mm), page_numbers, scale, print_background. Unknown keys are rejected with 400.
- •Billed as one document against your monthly allowance. On the free plan the PDF carries the SheetRender watermark footer.
- •HTML over 2 MB is rejected with 413 — both the template you send and the document it renders to.
Request body
{
"html": "<h1>Invoice {{ invoice_no }}</h1><p>{{ client }}</p>",
"data": { "invoice_no": "1043", "client": "Acme Ltd" },
"page_settings": { "page_size": "a4", "margins": { "top": 18, "right": 18, "bottom": 18, "left": 18 } }
}
Response
200 application/pdf
Content-Disposition: inline; filename="render.pdf"
<PDF bytes>
Example
curl -X POST https://sheetrender.com/api/v1/renders \
-H "Authorization: Bearer $SHEETRENDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"html":"<h1>Invoice {{ invoice_no }}</h1>","data":{"invoice_no":"1043"}}' \
--output invoice.pdf
GET/api/v1/templates
List the templates in your account, newest first.
Response
200 application/json
[
{
"id": "tpl_8f2c…",
"name": "Monthly invoice",
"created_at": "2026-07-02T09:14:31Z",
"updated_at": "2026-08-11T16:02:08Z"
}
]
Example
curl https://sheetrender.com/api/v1/templates \
-H "Authorization: Bearer $SHEETRENDER_API_KEY"
POST/api/v1/templates/{id}/render
Render one PDF from a saved template and a row of data.
- •Uses the template’s current version and its saved page settings. Pass page_settings to override them for this call only.
- •Billed as one document, same as /renders. The filename comes from the template name.
- •A template with no current version answers 400.
Request body
{
"data": { "invoice_no": "1043", "client": "Acme Ltd", "total": "1,280.00" },
"page_settings": { "page_size": "letter" }
}
Response
200 application/pdf
Content-Disposition: inline; filename="Monthly invoice.pdf"
<PDF bytes>
Example
curl -X POST https://sheetrender.com/api/v1/templates/tpl_8f2c/render \
-H "Authorization: Bearer $SHEETRENDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"data":{"invoice_no":"1043","client":"Acme Ltd"}}' \
--output invoice.pdf
POST/api/v1/templates/{id}/datasets
Upload a .csv or .xlsx as a dataset the given template can run a batch against.
- •The body is multipart/form-data with one part named file. The dataset is stored on the template’s project, so it is immediately usable with POST /jobs — and with any other template in that project.
- •columns[].key is the sanitized column name that POST /jobs group_by and template placeholders use. original is the spreadsheet header as uploaded; inferred_type is "string" or "number".
- •Creating a dataset is free. Only rendering against it counts toward your monthly document allowance. Files over 20 MB answer 413; more than 500,000 cells, or a file that is not a readable spreadsheet, answers 400. An unknown template answers 404.
Request body
multipart/form-data
file: customers.csv
Response
201 application/json
{
"id": "ds_1a90…",
"filename": "customers.csv",
"sheet_name": "csv",
"columns": [
{ "key": "client", "original": "Client", "inferred_type": "string" },
{ "key": "invoice_no", "original": "Invoice No", "inferred_type": "string" },
{ "key": "total", "original": "Total", "inferred_type": "number" }
],
"row_count": 240,
"created_at": "2026-08-19T11:18:02Z"
}
Example
curl -X POST https://sheetrender.com/api/v1/templates/tpl_8f2c/datasets \
-H "Authorization: Bearer $SHEETRENDER_API_KEY" \
-F "file=@customers.csv"
POST/api/v1/templates/{id}/datasets/rows
Create the same kind of dataset from JSON rows, when you have no file to post.
- •rows is a JSON array of objects. The spreadsheet header is the union of those objects’ keys, in first-seen order; a missing key on a later row becomes a blank cell. name is an optional label stored as the filename.
- •Creating a dataset is free. Only rendering against it counts toward your monthly document allowance.
- •At most 50,000 rows, and at most 500,000 cells including the header row. Over either limit, or an empty rows list, answers 400.
Request body
{
"rows": [
{ "name": "Ada", "total": 42 },
{ "name": "Ben", "total": 17 }
],
"name": "August customers"
}
Response
201 application/json
{
"id": "ds_7e22…",
"filename": "August customers.xlsx",
"sheet_name": "Data",
"columns": [
{ "key": "name", "original": "name", "inferred_type": "string" },
{ "key": "total", "original": "total", "inferred_type": "number" }
],
"row_count": 2,
"created_at": "2026-08-19T11:18:02Z"
}
Example
curl -X POST https://sheetrender.com/api/v1/templates/tpl_8f2c/datasets/rows \
-H "Authorization: Bearer $SHEETRENDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"rows":[{"name":"Ada","total":42},{"name":"Ben","total":17}],"name":"August customers"}'
GET/api/v1/templates/{id}/datasets
List the datasets this template can run a batch against, newest first.
- •Scoped to the template’s project, so a dataset uploaded against any template there appears here. An unknown template answers 404.
Response
200 application/json
[
{
"id": "ds_1a90…",
"filename": "customers.csv",
"sheet_name": "csv",
"columns": [
{ "key": "client", "original": "Client", "inferred_type": "string" },
{ "key": "invoice_no", "original": "Invoice No", "inferred_type": "string" },
{ "key": "total", "original": "Total", "inferred_type": "number" }
],
"row_count": 240,
"created_at": "2026-08-19T11:18:02Z"
}
]
Example
curl https://sheetrender.com/api/v1/templates/tpl_8f2c/datasets \
-H "Authorization: Bearer $SHEETRENDER_API_KEY"
POST/api/v1/jobs
Queue a batch: every row of a dataset rendered with a template.
- •The template and the dataset must belong to the same project. Create the dataset with POST /templates/{id}/datasets or POST /templates/{id}/datasets/rows, or pick one from GET /templates/{id}/datasets.
- •filename_template and group_by are project settings, not per-job arguments: passing them durably reconfigures the project, exactly as editing them in the app would. Later jobs — including scheduled ones — use the new values.
- •group_by must name a column key from the dataset (columns[].key), otherwise 400.
- •Billed per rendered document as the job runs.
Request body
{
"template_id": "tpl_8f2c…",
"dataset_id": "ds_1a90…",
"filename_template": "{{ client }}-{{ invoice_no }}.pdf",
"group_by": "client"
}
Response
201 application/json
{ "job_id": "job_44b1…" }
Example
curl -X POST https://sheetrender.com/api/v1/jobs \
-H "Authorization: Bearer $SHEETRENDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"template_id":"tpl_8f2c","dataset_id":"ds_1a90"}'
GET/api/v1/jobs/{id}
Poll a batch job and collect the document ids it produced.
- •status moves through queued and running to succeeded, failed, or cancelled.
- •documents is empty until the job finishes, then lists one entry per rendered PDF in row order.
Response
200 application/json
{
"id": "job_44b1…",
"status": "succeeded",
"rows_total": 240,
"rows_done": 240,
"rows_failed": 0,
"created_at": "2026-08-19T11:20:04Z",
"finished_at": "2026-08-19T11:22:47Z",
"documents": [{ "id": "doc_71c3…", "filename": "Acme-1043.pdf" }],
"merged_available": true,
"zip_available": true
}
Example
curl https://sheetrender.com/api/v1/jobs/job_44b1 \
-H "Authorization: Bearer $SHEETRENDER_API_KEY"
GET/api/v1/documents/{id}
Download one rendered PDF from a batch.
- •A document id from another account answers 404, never 403.
Response
200 application/pdf
Content-Disposition: attachment; filename="Acme-1043.pdf"
<PDF bytes>
Example
curl https://sheetrender.com/api/v1/documents/doc_71c3 \
-H "Authorization: Bearer $SHEETRENDER_API_KEY" \
--output Acme-1043.pdf