Skip to content

Render a batch asynchronously

For batches you don’t want to hold an HTTP request open for. The call returns immediately with a jobId; Pagr renders in the background and POSTs a webhook as each document finishes, plus one final callback when the job ends. If you can’t host a public endpoint, poll the job-status endpoint instead — it exposes the same information.

POST /v1/render/{templateId}/async
POST /v1/render/{templateId}/versions/{version}/async
GET /v1/render/jobs/{jobId}

The two POST forms enqueue against the latest published version or a specific one. The GET form polls a job’s status and is scoped to the calling organisation — another tenant’s job returns 404.

Terminal window
# 1. Enqueue
curl -X POST "https://pagr-prd-api-public.azurewebsites.net/v1/render/8bec66ff-6f3d-4c1e-9a2b-1f0e5d7c4a90/async" \
-H "Authorization: Bearer pagr_prod_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"documents": [
{ "Title": "Acme Q3 Invoice", "Amount": 42 },
{ "Title": "Acme Q4 Invoice", "Amount": 58 }
],
"callbackUrl": "https://your-app.example/pagr/callback?token=s3cr3t"
}'
# → 202 { "jobId": "…", "requestedCount": 2, "state": "queued" }
# 2. Poll (or wait for the webhook)
curl "https://pagr-prd-api-public.azurewebsites.net/v1/render/jobs/<jobId>" \
-H "Authorization: Bearer pagr_prod_xxxxxxxx"
Parameter Type Required Description
templateId string (UUID) Yes The template to render.
version integer Only for the specific-version form The template version number to render.
Parameter Type Default Description
persist boolean true Whether the rendered documents are stored.
language string Language variant to render, for multilingual templates.
Field Type Default Description
documents array of objects — (required) One object per document. Same 50 MB / 32-level limits per document as a synchronous render; a test key caps the array at 10 entries.
callbackUrl string — (required) A publicly reachable URL Pagr POSTs the webhooks to.
includeDocument boolean false When true, each progress webhook carries its document’s PDF inline as Base64.
{
"documents": [
{ "Title": "Acme Q3 Invoice", "Amount": 42 },
{ "Title": "Acme Q4 Invoice", "Amount": 58 }
],
"callbackUrl": "https://your-app.example/pagr/callback?token=s3cr3t",
"includeDocument": false
}

202 Accepted:

{
"jobId": "6b1c9f42-0d3e-4a58-b7c1-9e2f8a4d5c30",
"requestedCount": 2,
"state": "queued" // always "queued" here — poll for progression
}
Field Type Description
jobId string (UUID) Use it to poll the job or correlate webhooks.
requestedCount number Documents submitted with the job.
state string Always queued on this response.
GET /v1/render/jobs/{jobId}
Parameter Type Required Description
jobId string (UUID), path Yes The job returned by the enqueue call.

Call it on an interval — every couple of seconds is fine — and stop once state is a terminal value. Response: 200:

{
"jobId": "6b1c9f42-0d3e-4a58-b7c1-9e2f8a4d5c30",
"state": "completed", // lifecycle: pending | completed | failed
"status": "partial", // outcome: ok | partial | failed | insufficient_credit (null while pending)
"renderedCount": 1,
"requestedCount": 2,
"missingCount": 1, // requestedCount − renderedCount
"issues": [
{ "type": "MissingBinding", "severity": "Error", "description": "…", "documentIndex": 1 }
],
"startedAt": "2026-07-24T09:45:58Z",
"completedAt": "2026-07-24T09:46:04Z", // null while pending
"failureReason": null // set only when state == "failed"
}
Field Type Description
jobId string (UUID) The job.
state string The job lifecycle: pending (queued or rendering), completed (finished; documents were produced, including partial and credit-stopped runs), or failed (produced nothing).
status string or null The render outcome, same vocabulary as the sync envelope: ok, partial, failed, insufficient_credit. null while state is pending.
renderedCount number Documents produced so far.
requestedCount number Documents submitted.
missingCount number requestedCount − renderedCount.
issues array Per-document RenderIssue objects, each with its documentIndex.
startedAt string (ISO 8601) When the job started.
completedAt string (ISO 8601) or null When it reached a terminal state; null while pending.
failureReason string or null Human-readable reason, set only when state is failed.
SDK Enqueue Poll once Wait for terminal
Python enqueue_batch_render(template_id, json_data_sets, callback_url, …) get_job_status(job_id) wait_for_job(job_id, poll_interval=2.0, timeout=None)
TypeScript enqueueBatchRender(templateId, dataSets, callbackUrl, options?) getJobStatus(jobId) waitForJob(jobId, options?)
Java enqueueBatchRender(templateId, dataSets, callbackUrl[, RenderOptions]) getJobStatus(jobId) waitForJob(jobId[, pollInterval, timeout])
C# EnqueueBatchRenderAsync(templateId, dataSets, callbackUrl, …) GetJobStatusAsync(jobId) WaitForJobAsync(jobId, pollInterval, timeout, …)
Ruby enqueue_batch_render(template_id, json_data_sets, callback_url, …) job_status(job_id) wait_for_job(job_id, poll_interval:, timeout:)
C++ enqueue_batch_render(template_id, data_sets, callback_url, RenderOptions) get_job_status(job_id) wait_for_job(job_id, poll_interval, timeout)

Every SDK’s wait_for_job treats an unrecognised state as terminal (fail-open), so a state a newer server introduces can never trap the loop in an infinite wait.

Status code When
400 ValidationError A test key submitted more than 10 documents, or an unknown language.
404 EntityNotFound No job with this id exists for your organisation.
413 PayloadTooLarge A document exceeds 50 MB.
503 QueueFull The async render queue is at capacity — back off and retry.

See Errors for the full table.