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’sissuesarray. 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.
HTTP errors
Section titled “HTTP errors”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. |
Data limits
Section titled “Data limits”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 |
Rate limits
Section titled “Rate limits”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.
Render issues
Section titled “Render issues”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 and what it blocks
Section titled “Severity and what it blocks”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.
Issue types
Section titled “Issue types”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. |
SDK exceptions
Section titled “SDK exceptions”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.
Related articles
Section titled “Related articles”- Handle errors and retries — which exceptions to catch where, and what the SDKs retry.
- Validate before rendering — turn render issues into feedback before you spend a credit.
- Authentication — the bearer API key, and the difference between
401and403. - API Overview — base URL, rendering shapes, and the response envelope.
