Skip to content

Errors

The Pagr API reports problems in two different places, and it matters which one you’re looking at:

  • HTTP errors — the request itself was rejected. You get a non-2xx status and an error envelope. In the SDKs these become exceptions.
  • Render issues — the request was accepted (200 OK), but a specific document couldn’t render, or rendered with a caveat. These arrive inside the response body’s issues array. In the SDKs these are data, never exceptions.

Getting this split wrong is the most common source of confusion: a document that fails validation is not an error, and an expired API key is not a render issue.

Transport and protocol failures come back as an HTTP error status with a JSON body of the form:

{
"error": {
"code": "TemplateNotFound",
"message": "TemplateModel not found"
}
}

code is a stable, machine-readable string — safe to switch on. message is human-readable and may change; don’t parse it.

Status code Meaning
400 ValidationError The request violates a business rule — e.g. a test-key batch over 10 documents, or a language not defined on the template version.
401 The API key is missing, malformed, or invalid. See Authentication.
401 NotAuthenticated The key is well-formed but has no associated organisation.
403 Forbidden The key is valid, but the authenticated organisation isn’t allowed to perform this action (e.g. a resource it doesn’t own).
404 TemplateNotFound, VersionNotFound, NoPublishedVersion, DocumentNotFound, OrganizationNotFound, EntityNotFound The template, version, document, job, or organisation wasn’t found.
406 NotAcceptable The requested representation can’t be produced — e.g. Accept: application/pdf on a batch of more than one document.
410 PdfDeleted The document’s metadata still exists, but its PDF was purged by your organisation’s retention policy.
413 PayloadTooLarge A single document’s JSON payload exceeds 50 MB.
422 BindingError The request body couldn’t be parsed or bound to the expected shape — malformed JSON, wrong types, missing required fields.
429 Too many requests — see rate limits below.
500 InternalError, RenderTimeout, ExternalServiceError An unexpected server error, or a render that exceeded its 60-second budget.
503 QueueFull The async render queue is at capacity. Only affects async batch jobs — back off and retry.
503 A health-check dependency is down. Only from GET /v1/meta/status, and its body is plain text, not this envelope.

The values behind 413, 422 and the RenderTimeout issue:

Limit Value Exceeded →
Payload size, per document 50 MB of JSON 413 PayloadTooLarge
JSON nesting depth, per document 32 levels 422 BindingError
Render time, per document 60 seconds RenderTimeout render issue (or 500 RenderTimeout)
Batch size, test keys 10 documents per request 400 ValidationError
Batch size, production keys no fixed cap
Issues stored per async job 100 issues is truncated; the counts stay exact

Limits are applied per organisation over a sliding 60-second window, and tracked separately per category — so a burst of renders doesn’t starve your ability to poll a job’s status.

Category Requests per 60s Covers
Read 600 GET endpoints: templates, versions, documents, fonts, org stats, job status
Write 120 Mutating endpoints, e.g. the document-name-template PATCH
Render 300 Every /v1/render* endpoint, including validate and async enqueue

The render limit bounds requests, not documents — one request carrying 500 documents costs one permit.

Every render or validate call can return an issues array — one entry per problem found, each scoped to a documentIndex and, where relevant, an elementId:

{
"type": "MissingBinding",
"severity": "Error",
"description": "No value bound for 'total'.",
"elementId": "total-amount",
"documentIndex": 0
}

severity is Information, Warning, or Error. What blocks a render depends on the environment:

Severity Test / preview render Production render Validate endpoint
Information renders renders valid
Warning renders blocked invalid
Error blocked blocked invalid

This is the single most surprising thing in the error model: a document that renders cleanly with a pagr_test_ key can be blocked with a pagr_prod_ key, because production also blocks on warnings. The validate endpoint reports is_valid using that same production gate — Warning or Error both make it false — so validating with a test key and rendering with a production key is safe by default. If you only ever render with test keys, check errors directly instead; it’s the narrower, Error-only view that matches what test/preview actually blocks.

Grouped by the severity the server assigns them.

Error — blocks every render:

type Meaning
InvalidJson The document’s data payload isn’t valid JSON.
SchemaInvalid The data doesn’t match the shape the template expects.
DangerousContent The data contains content blocked for security reasons (script/HTML injection patterns, embedded executables).
InvalidPageBackground A page background reference couldn’t be used.
RenderTimeout The document exceeded the 60-second render budget.

Warning — blocks production renders, allowed in test/preview:

type Meaning
MissingBinding A variable referenced by the template has no corresponding value in the data.
UnresolvedImage An image reference in the data couldn’t be resolved to an actual image.
UnresolvedFont A font referenced by the template isn’t available — see Fonts.
InvalidColor A colour value couldn’t be parsed.
InvalidCondition A conditional expression in the template failed to evaluate.
DataSourceNotEnumerable A repeating element’s data source isn’t a list/array.
InvalidChartConfig A chart element’s configuration is malformed.
BindingFailedAtRender A binding resolved but failed while rendering (e.g. a formatting function threw).
RenderLayoutDegraded The layout engine had to fall back to a degraded layout to fit the content.

Information — never blocks:

type Meaning
UnformattedValue A value couldn’t be formatted as the template specifies and was rendered as-is.
InvalidLayout A non-blocking layout problem was detected.

Every SDK maps the HTTP statuses above onto the same small tree, and wraps transport failures into it too — so catching the base type catches everything the SDK can produce, and you never see a raw HTTP-library exception.

Cause Python TypeScript Java C# Ruby C++
base type PagrError PagrError PagrException PagrApiException Pagr::Error pagr::PagrApiException
401 AuthenticationError AuthenticationError AuthenticationException PagrAuthenticationException AuthenticationError PagrAuthenticationException
403 ForbiddenError ForbiddenError ForbiddenException PagrForbiddenException ForbiddenError PagrForbiddenException
404 NotFoundError NotFoundError NotFoundException PagrNotFoundException NotFoundError PagrNotFoundException
413 PayloadTooLargeError PayloadTooLargeError PayloadTooLargeException PagrPayloadTooLargeException PayloadTooLargeError PagrPayloadTooLargeException
422 ValidationFailedError ValidationFailedError ValidationFailedException PagrValidationFailedException ValidationFailedError PagrValidationFailedException
429 RateLimitError RateLimitError RateLimitException PagrRateLimitException RateLimitError PagrRateLimitException
any other 4xx/5xx ApiError ApiError ApiException PagrApiException ApiError PagrApiException
timeout PagrTimeoutError PagrTimeoutError PagrTimeoutException PagrTimeoutException PagrTimeoutError PagrTimeoutException
connection / DNS / TLS failure PagrConnectionError PagrConnectionError PagrConnectionException PagrConnectionException PagrConnectionError PagrConnectionException
unparseable success body PagrDecodeError PagrDecodeError PagrDecodeException PagrDecodeException PagrDecodeError PagrDecodeException

Every exception carries the HTTP status_code and the API’s code when the response provided them (both are absent for transport failures). RateLimitError additionally carries retry_after.