Idempotency and retries
Networks fail mid-request. The dangerous case is a timeout: you don't know whether the server rendered the document. Retry blindly and you may issue it twice; give up and you may not have issued it at all. Idempotency keys remove the dilemma.
How it works
Send an Idempotency-Key header (1–128 printable characters, unique per
logical operation) on any POST:
curl -o notice.pdf "https://api.pdfs.ecourtdate.com/v1/templates/$TEMPLATE_ID/generate" \
-H "x-api-key: $API_KEY" \
-H "Idempotency-Key: notice-2026-CV-1042-r1" \
-H "Content-Type: application/json" \
-d '{ "data": { "client_name": "Jordan Smith", "hearing_date": "2026-08-15" } }'
- Same key, same body → replay. The original result returns with the
Idempotent-Replay: trueheader. Nothing re-executes. - On generation endpoints, a replay returns the originally generated document: a retried timeout can never produce a duplicate.
- Keys are remembered for 24 hours.
Key reuse with a different body
Reusing a key with a different body is rejected with 409
IDEMPOTENCY_KEY_REUSED, a guardrail that catches bugs where a key is
accidentally shared across distinct operations. Derive keys from the logical
operation, not the wall clock:
notice-<case_number>-<hearing_date> good: retries share it, distinct notices don't
notice-<timestamp> bad: every retry is a "new" operation
A retry recipe
- Generate one idempotency key per logical document and persist it with the work item before the first attempt.
- On timeout or
5xx: retry with the same key using exponential backoff with jitter (for example 1 s, 2 s, 4 s, … capped at 60 s). - On
429: same, back off and retry with the same key. - On
4xxother than429: stop; the request itself is wrong (Errors). - Treat
Idempotent-Replay: trueas success: it is the same result the original request would have returned.
Where to send it
Idempotency-Key is honored on the creating POSTs (templates, versions,
copies, assets) and on every generation endpoint:
generate,
/v1/documents, and
generate-batch (where one key protects the entire
200-item job from double submission).
Reads (GETs) and DELETE /v1/jobs/{jobId} are
naturally idempotent: no key needed.