Many documents, one file
Statements, payslips, certificates, per-customer invoices: the same form, once per person. Compose PDF renders those in a single call and returns a single file, with each record laid out as its own document.
There are two ways to say how many documents you want, and which one to use depends on who owns the list.
The caller owns the list
Section titled “The caller owns the list”Send dataList — an array of payloads — instead of data. Each entry is
validated and rendered as its own document, and the documents are collated in
order:
curl -X POST https://composepdf.com/v1/canvases/{canvasId}/pdf \ -H "x-api-key: $COMPOSEPDF_API_KEY" \ -H "content-type: application/json" \ -d '{ "dataList": [ { "customer": { "name": "Acme Inc." }, "items": [ … ] }, { "customer": { "name": "Brightline LLC" }, "items": [ … ] } ] }' \ --output statements.pdfA contract violation names the record it came from, so a bad row in position
four reads dataList[3].customer.name rather than “invalid request”.
The template owns the list
Section titled “The template owns the list”Sometimes the payload is naturally one object with a list inside it, and each entry of that list should become a document. Say so in the studio: under Documents to print, choose One per row of your list instead of One, from all of this data.
Now a single data object fans out. Each document sees its own row plus
everything outside the list — a period, a company address, a notice that
appears on all of them. Where a name appears in both, the row wins.
This choice belongs to the template and is frozen when you publish, which is what keeps the studio preview, the browser export and the API in agreement about how many documents a payload produces.
What comes back
Section titled “What comes back”One PDF. Each record is paginated independently, so page numbers are
per-document — the second invoice starts again at page 1, and 3 / 4 means
the third page of that invoice. The response reports how many records were
rendered in x-rspdf-records; the total page count is recorded server-side and
read back as pages from GET /v1/renders/{renderId}.
Notes and limits
Section titled “Notes and limits”- Up to 100 records per request with
dataList. Beyond that the request is refused rather than run — split it. dataanddataListare mutually exclusive.- An empty list is refused. A zero-page PDF is not a useful answer, so an empty array is an error rather than an empty file.
- Long jobs should be asynchronous. Synchronous requests are bounded by a
render budget of roughly 25 seconds; add
Prefer: respond-asyncto get a job id instead, and download the result when it is ready. See Render API. - Encryption is not available on async jobs, because the password would have to be stored while the job waits.