Documentation

API reference

Render a single PDF from HTML and data, render a saved template, or run a batch: upload a spreadsheet or POST JSON rows as a dataset, queue a job, poll it, and download the PDFs. JSON in, PDF bytes out. Every endpoint lives under https://sheetrender.com/api/v1.

Authentication

Every request carries an API key as a bearer token. Create one in Settings → API keys. The key is shown once, when it is created; after that only its prefix is visible, so store it in your secret manager rather than in the page you copied it from. Revoking a key takes effect immediately.

Authorization: Bearer sr_live_...

Keys carry the permissions of the account that owns them and are not scoped per project. Treat one as a password: server-side only, never in browser JavaScript or a mobile app.

Endpoints

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

Rate limits and errors

Each key is limited to 120 requests per minute. Over that, requests answer 429 until the window rolls forward. Batch jobs additionally allow at most 12 job starts per 10 minutes per account. They are still the cheaper way to render a lot of documents: one request queues the whole dataset.

Errors come back with the matching HTTP status and a JSON body in FastAPI's shape, so the message is always at detail.

{ "detail": "A maximum of 10 active API keys is allowed" }
StatusWhen you see it
400The request cannot be acted on — a template placeholder that does not resolve, a grouping column that is not in the dataset, a template with no current version, an unreadable spreadsheet, more than 500,000 cells, or more than 50,000 rows on POST /templates/{id}/datasets/rows.
401Missing, malformed, or revoked key. Check the Authorization header.
403The key is good but the plan is not: a monthly document limit, or a feature your plan does not include.
404No such template, job, dataset, or document in your account. Ids belonging to another account also answer 404.
413The HTML exceeds 2 MB, before or after data substitution, or a dataset file exceeds 20 MB.
429Rate limit exceeded. Back off and retry.

MCP server

The same API is available to AI assistants over the Model Context Protocol, so a chat client can list your templates and render documents without you writing the HTTP calls. Add this to your MCP client configuration:

{
  "mcpServers": {
    "sheetrender": {
      "command": "npx",
      "args": ["-y", "@sheetrender/mcp"],
      "env": { "SHEETRENDER_API_KEY": "sr_live_..." }
    }
  }
}

The server reads its key from SHEETRENDER_API_KEY and calls the endpoints above, so the same rate limit, plan limits, and document billing apply.