Skip to main content

Errors

All API errors return a consistent JSON response body. There are two error formats depending on the error source.

Validation errors​

Returned with HTTP 400 when the document definition is refused before anything is rendered or billed: text that is not JSON, a key or a value the schema does not name, a required member missing, or a limit exceeded. The definition is read exactly as the published schema states it: a key in another case, a retired spelling or a definition without its root is refused, never guessed at.

{
"code": "SCHEMA",
"path": "document.content.0",
"message": "\"tabel\" is not in this schema; this object takes: source, break, p, th, td, h1, h2, h3, h4, h5, h6, title, lang, id, ol, ul, table, image, hr, columns, svg, toc, style, header, and 8 more"
}
FieldTypeDescription
codestringMachine-readable error code
pathstringWhere in the definition, as dotted keys and indexes (document.content.0)
messagestringWhat is wrong, and what the place takes instead

Validation error codes​

CodeDescription
INVALID_JSONThe request body is not JSON
SCHEMAA key the schema does not name, a value of the wrong kind, a required member missing, or a definition with no document or component root
EMPTY_CONTENTA document whose content array is empty
UNRESOLVED_COMPONENTA use names a component the tenant does not have

A definition past one of the engine's resource limits (too many pages, a table or an image too large, nesting too deep) is refused with a code naming the limit, such as TOO_MANY_PAGES or NESTING_TOO_DEEP.

Examples​

Typo in property name, for { "document": { "content": [{ "tabel": {} }] } }: the answer above.

Wrong type, for { "document": { "pageSetup": { "margin": true }, "content": [{ "p": "Hi" }] } }:

{
"code": "SCHEMA",
"path": "document.pageSetup.margin",
"message": "Expected a number, CSS's spacing shorthand (\"20mm\", \"10mm 20mm\") or a list of four lengths - numbers in points or unit strings (\"0.5in\", \"12.7mm\", \"48px\", \"36pt\")."
}

Empty content, for { "document": { "content": [] } }:

{
"code": "EMPTY_CONTENT",
"path": "document.content",
"message": "the content array is empty, so the document draws nothing; give it at least one node (a component may be config-only, a document may not)"
}

The root key is required​

A definition is written inside document, or a component inside component. A bare { "content": [...] } is refused with SCHEMA, and the message says where to put it:

{
"code": "SCHEMA",
"path": "$",
"message": "\"content\" is not in this schema; this object takes: $schema, document, component; a definition is written inside \"document\" ({\"document\": ...}), a component inside \"component\""
}

Server errors​

Returned by the global exception handler for infrastructure, authorization, and runtime errors. Uses the RFC 7807 Problem Details format.

{
"status": 503,
"title": "Service Unavailable",
"detail": "Database service is temporarily unavailable.",
"instance": "/v1/{tenantId}/documents/generate-from-payload",
"traceId": "00-abc123def456-789012-00",
"errorCode": "DATABASE_UNAVAILABLE",
"isRetryable": true,
"retryAfterSeconds": 30
}
FieldTypeDescription
statusintegerHTTP status code
titlestringStandard HTTP reason phrase
detailstringHuman-readable description
instancestringRequest path that triggered the error
traceIdstringUnique trace ID for support requests
errorCodestringMachine-readable error code
isRetryablebooleanWhether the request can be retried
retryAfterSecondsintegerSeconds to wait before retrying (when retryable)

HTTP status codes​

StatusWhenRetryable
400Invalid JSON, schema errors, empty content, bad request parametersNo
401Missing, invalid or expired access token, or wrong client credentialsNo
403Insufficient permissions or tenant mismatchNo
404Template or document not foundNo
429Rate limit exceededYes — wait the seconds in the Retry-After header
500PDF generation failure or unexpected server errorMaybe — retry once
503Database, storage, or external service temporarily unavailableYes — check retryAfterSeconds

Server error codes​

CodeStatusDescription
VALIDATION_ERROR400Invalid request parameters
INVALID_OPERATION400Operation not valid in current state
ACCESS_DENIED401Authentication failed
TENANT_CLAIM_MISSING403Tenant information missing from token
TENANT_MISMATCH403Accessing a resource from a different tenant
FILE_NOT_FOUND404Requested file does not exist
REQUEST_CANCELLED400Client cancelled the request
DATABASE_UNAVAILABLE503Database temporarily unavailable (retryable)
AZURE_SERVICE_ERROR503Storage service temporarily unavailable (retryable)
EXTERNAL_SERVICE_ERROR503External dependency temporarily unavailable (retryable)
NETWORK_ERROR503Network connectivity issue (retryable)
SERVICE_TIMEOUT503Request timed out
INTERNAL_ERROR500Unexpected server error

Rate limiting​

A request over a rate limit is answered 429 Too Many Requests. The Retry-After header gives the seconds to wait before the limit resets:

HTTP/1.1 429 Too Many Requests
Retry-After: 37

{"error": "rate_limit_exceeded", "message": "Too many requests. Please try again later."}

The limits themselves are listed under Rate limits.

Best practices​

  1. Check the HTTP status code first before parsing the response body.
  2. Use code for programmatic handling — match on error codes, not message strings.
  3. Use path to locate the issue in validation errors — it points to the exact JSON property.
  4. Retry on 429 and 503 with backoff. Use retryAfterSeconds or the Retry-After header to determine wait time.
  5. Do not retry on 400, 401, 403, or 404 — fix the request first.
  6. Log traceId for server errors — include it when contacting support.
  7. Validate locally with the JSON Schema to catch errors before sending to the API.