Retries and idempotency
The Poli Page Python SDK retries transient failures automatically. Every POST carries an Idempotency-Key that stays the same across the retries of one call. The API does not deduplicate on it, so a retried render can store a second document (see Idempotency).
Default retry policy
Section titled “Default retry policy”network_error,timeout, 5xx responses (such as503 PUBLISH_IN_PROGRESS) and 429 responses (such asRATE_LIMIT_EXCEEDED) are retried.QUOTA_EXCEEDEDandOVERAGE_CAP_EXCEEDED(429) andSTORAGE_ACCESS_DENIED(502) are not retried: they last until the monthly reset or until the bucket access is restored, so the SDK raises them at once andis_retryable()isFalse.- Up to
max_retriesretries (default2— three total attempts); after the last one, the last error is raised. - Exponential backoff starting at
retry_delayseconds (default0.5), doubling each attempt, with a random factor in[0.5, 1.5). - Honors the
Retry-Afterresponse header when present (integer seconds or an HTTP-date), clamped to 0–30 seconds, without jitter.Retry-After-Msis ignored. - Caller cancellation (
asyncio.CancelledError,KeyboardInterrupt) is never retried. The presigned-URL download is never retried. DOWNLOAD_FAILEDis never retried andis_retryable()isFalsefor it, whatever the storage status, a download timeout included.invalid_response(a 2xx whose body is not JSON) andio_error(render_to_filecould not write the file) are never retried andis_retryable()isFalsefor them.
Disabling retries
Section titled “Disabling retries”Set max_retries=0 on the constructor to disable retries entirely.
from poli_page import PoliPage
client = PoliPage(max_retries=0)Idempotency
Section titled “Idempotency”Every POST request (render.*, documents.thumbnails) includes an Idempotency-Key header: a lowercase UUID v4 the SDK generates once per call and sends unchanged on every retry of that call. GET and DELETE requests and the presigned-URL download send none.
The API does not deduplicate on this header. Every POST /v1/render that reaches the renderer stores a new document, so a retry can create a second one when the first attempt was stored but its answer was lost: a 5xx raised after the document was stored, a timeout that fired while the API was still rendering (for example timeout=10 on a 15 s render), or a network_error after the request reached the API. A 503 PUBLISH_IN_PROGRESS is raised before any render work, so retrying it never duplicates. If an extra stored document matters to you, lower max_retries or raise timeout; note that max_retries=0 also stops the always-safe retry of PUBLISH_IN_PROGRESS.
You can supply your own key via idempotency_key on the input, for example to correlate a call in your logs; it is sent as is on every attempt of that call:
pdf = client.render.pdf({ "project": "billing", "template": "invoice", "data": {"invoiceNumber": "INV-001", "total": 1280}, "idempotency_key": "invoice-INV-001-v1",})Two calls with the same key are still two renders: each stores its own document.
Example
Section titled “Example”from poli_page import PoliPage
client = PoliPage(max_retries=4, retry_delay=0.25)
pdf = client.render.pdf({ "project": "billing", "template": "invoice", "data": {"invoiceNumber": "INV-001", "total": 1280},})