Errors
Every failure raised by the SDK is an instance of PoliPageError or one of its subclasses. The code attribute tells you what went wrong; the request_id attribute is what to quote when filing a support ticket.
The error hierarchy
Section titled “The error hierarchy”PoliPageError is the base catch-all. Two transport-level branches (APIConnectionError and its APITimeoutError subclass) and one HTTP-status branch (APIStatusError, with per-status subclasses like RateLimitError, NotFoundError, BadRequestError) cover every failure mode.
from poli_page import PoliPage, PoliPageError
client = PoliPage()
try: client.render.pdf({"project": "billing", "template": "invoice", "data": {}})except PoliPageError as err: print(err.code, err.request_id, err.message)SDK-internal codes
Section titled “SDK-internal codes”These originate inside the SDK before any HTTP call (or in response to a network-layer failure). They are lowercase.
| Code | When | Recovery |
|---|---|---|
invalid_options | The arguments passed to the PoliPage constructor are invalid (missing api_key, malformed base_url, etc.). The message is `apiKey is required`. | Fix the constructor call. Raised synchronously. |
network_error | TCP/TLS-layer failure reaching the API. The message is `Network request failed`; the httpx exception is the `__cause__`. | Automatically retried up to max_retries times. |
timeout | The request did not complete within the configured `timeout`. The message is `Request timed out after <ms>ms`. | Automatically retried up to max_retries times. Raise `timeout` if it keeps tripping. |
DOWNLOAD_FAILED | The S3 second-hop download (after `render.pdf` / `render.pdf_stream`) failed: `Failed to download PDF: HTTP <status>` with the storage `status` (an expired URL gets 403), `Failed to download PDF: timeout` without one when it outlasts `timeout`, or `Failed to download PDF: network error` without one, also when the connection drops while the body is read. | Never retryable, whatever the storage status (`is_retryable()` is `False`). Re-fetch the descriptor via documents.get(id) — presigned URLs expire after 15 minutes. |
invalid_response | A 2xx answer whose body is not JSON, from a method that expects JSON (the render call, `documents.get`, `documents.thumbnails`), usually from a proxy or a captive portal. Raised as `InvalidResponseError` with the real 2xx as `status` and the message `Invalid response: HTTP <status> body is not JSON`; `to_payload()` answers 502. | Not retried. Check the network path to the API (proxy, `base_url`). |
io_error | `render_to_file` / `async_render_to_file` could not write the file (a parent directory, the temporary file, a write or the final rename), or the target exists but is not a writable regular file (`Failed to write PDF: <target>: not a regular file` for a directory or a FIFO). No `status`; the message is `Failed to write PDF: <target>: <reason>`, with the path as you gave it, and the `OSError`, when there is one, is the `__cause__`. The target is left as it was. | Not retried. Fix the path, permissions or free space. |
API codes
Section titled “API codes”Returned by the Poli Page API. Uppercase, snake-case. The SDK forwards the code verbatim.
| Code | When | Recovery |
|---|---|---|
INVALID_API_KEY | The API key is malformed or revoked. | Create a new key in the dashboard. |
MISSING_API_KEY | No API key was provided in the request. | Check the constructor was called with `api_key`. |
PROJECT_NOT_FOUND | No project with this slug exists in the organization. | Check the `project` slug. |
PROJECT_NOT_IN_ENVIRONMENT | The project was never published to the key's environment, or was removed from it. | Publish the project to the environment of the key you render with. |
TEMPLATE_NOT_FOUND | The template is not in the project's copy published in the key's environment. | Check the `template` slug; publish the project again if the template is new. |
DOCUMENT_GONE | The document was deleted, or is a `sandbox` document older than 30 days. | Render it again. |
VALIDATION_ERROR | The request body failed validation (for example thumbnail options out of bounds). | Inspect `err.message` for the failing field. |
MISSING_DATA | The request body lacks a required `data` field. | Pass `data` (`{}` when the template needs none) on every render call. |
QUOTA_EXCEEDED | The organization reached its monthly quota (429). | Not retried: it lasts until the monthly reset. Upgrade the tier for more renders. |
OVERAGE_CAP_EXCEEDED | The organization reached its overage cap (429, paid tiers). | Not retried: it lasts until the monthly reset. Raise the cap in the dashboard. |
STORAGE_ACCESS_DENIED | The organization's own storage bucket refuses access (502). | Not retried: restore the bucket role, then call again. |
RATE_LIMIT_EXCEEDED | Too many render requests for this key in a short window. | Automatically retried; spread the load. |
PUBLISH_IN_PROGRESS | The project is being published into this environment (503 with `Retry-After`). | Automatically retried up to `max_retries` times (default `2`, three attempts in all), each retry waiting `Retry-After` capped at 30 seconds. A publish that outlasts the retries surfaces this error: catch it and retry later. |
ORGANIZATION_PAYMENT_GRACE | The organization has unpaid invoices: it is read-only and renders are refused. | Update the payment method in the dashboard. |
INTERNAL_ERROR | The API returned 5xx. Also the code of an error answer whose body is not JSON (a gateway page), with its real status and the message `HTTP <status>`. | Automatically retried; contact support if it persists. |
The full list of API codes lives in the reference.
Example
Section titled “Example”from poli_page import PoliPage, PoliPageError, RateLimitError
client = PoliPage()
try: pdf = client.render.pdf({ "project": "billing", "template": "invoice", "data": {"invoiceNumber": "INV-001", "total": 1280}, })except RateLimitError: # RATE_LIMIT_EXCEEDED outlasted the retries, or QUOTA_EXCEEDED / OVERAGE_CAP_EXCEEDED, # which the SDK never retries: queue the job for later. pdf = Noneexcept PoliPageError as err: print(f"render failed: {err.code} ({err.request_id})") raise