Guide
Retry create requests safely without duplicating render work
Use this guide when you are wiring retry loops for create routes. The examples start with the smallest safe replay, then move to the version most teams should ship, then cover the advanced edge where accepted follow-up and multiple conflict meanings change your next step.
POST/v1/pdf
POST/v1/pdf/batch
Implementation path
- Create request payload and idempotency key as one persisted unit.
- Retry only retryable failures with the exact same key and exact same body.
- When the replay becomes accepted work, switch to follow-up routes instead of resending create.
When to use it
Timeouts, dropped connections, and ambiguous create outcomes where the client cannot prove whether work was admitted.
Supported routes
Apply the same retry posture to single render and batch create.
Core rule
Same key plus the same body reuses original work. Same key plus a different body returns a conflict.
Before You Start
Prepare key lifecycle and retry boundaries before coding the loop
For create routes, production clients should usually send the idempotency header on every request where retries are possible. This guide stays implementation-focused: what to persist, what to retry, and when to stop.
- Generate the key before attempt one. Do not generate a new key after a timeout.
- Persist key and request body together so process restarts can replay safely.
- Use the same key only for retries of the same logical create request body.
- Use a new key whenever meaningful request input changes.
- Set a max retry budget and backoff strategy up front so failures do not produce unbounded loops.
For the shared mental model, read the idempotency concept page. Use this guide for route-specific implementation order.
Retry Workflow
Follow one deterministic sequence for every retryable create call
01
Build request and key as one persisted unit
Create and store the request payload and idempotency key together before your first API call.
02
Send the first attempt once
Call the create route and capture response status, body, and correlation data for troubleshooting.
03
Retry only retryable outcomes
Retry on transport failures, 429, and bounded transient 5xx responses. Keep key and body unchanged.
04
Interpret replay outcomes by key and body pairing
Same key plus same body reuses original work. Same key plus a different body is a conflict by contract.
05
Switch to follow-up routes after acceptance
If a replay returns an accepted single render or accepted batch, store the returned identifiers and stop retrying the create route.
Replay outcomes
Same key + same body
safe replay
The API reuses original single render or batch create work instead of admitting duplicate work.
Same key + different body
409 conflict
The API rejects the request because that key is already bound to different create input.
Same key still resolving
short-delay retry
Batch create can return an in-flight conflict while the original keyed request is still being resolved.
Example Ladder
Start with one safe replay, then add accepted and batch-specific decisions
Easiest path
Start with one small single render. If the client loses the first response, retry the exact same request with the exact same key.
curl -X POST "https://api.solidrelay.io/v1/pdf" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Idempotency-Key: receipt-1001" \
-H "Content-Type: application/json" \
-d '{
"html": "<h1>Receipt 1001</h1>",
"output": "binary",
"executionMode": "sync"
}'curl -X POST "https://api.solidrelay.io/v1/pdf" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Idempotency-Key: receipt-1001" \
-H "Content-Type: application/json" \
-d '{
"html": "<h1>Receipt 1001</h1>",
"output": "binary",
"executionMode": "sync"
}'HTTP/1.1 202 Accepted
{
"success": true,
"jobId": "7b6f4c93-34e3-4f6d-a32d-9a2f0b2054d1",
"status": "queued",
"statusUrl": "/v1/pdf/jobs/7b6f4c93-34e3-4f6d-a32d-9a2f0b2054d1",
"fileUrl": "/v1/pdf/jobs/7b6f4c93-34e3-4f6d-a32d-9a2f0b2054d1/file"
}- The second create uses the same key and the same body because it is the same logical operation.
- If the replay returns
202 Accepted, storejobId,statusUrl, andfileUrl, then move to follow-up handling.
Realistic path
This is the version most teams should implement first for batch create: deterministic item IDs, one idempotency key for the whole request, and explicit acceptance handling.
curl -X POST "https://api.solidrelay.io/v1/pdf/batch" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Idempotency-Key: batch-invoices-2026-04-01" \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "id": "invoice_001", "html": "<h1>Invoice 1</h1>" },
{ "id": "invoice_002", "html": "<h1>Invoice 2</h1>" }
]
}'HTTP/1.1 202 Accepted
{
"jobId": "4f93c86f-3b9f-4bd8-9f8e-bba2f8dd760d",
"status": "queued",
"itemCount": 2,
"totalCount": 2,
"statusUrl": "/v1/pdf/batch/4f93c86f-3b9f-4bd8-9f8e-bba2f8dd760d",
"estimatedCompletionTime": "2026-04-10T15:13:00.000Z",
"expiresAt": "2026-04-11T15:10:00.000Z"
}- Use deterministic item IDs so retries, reconciliation, and downstream bookkeeping stay stable.
- For batch create, acceptance means the job exists. Continue on the batch status route instead of resending changed work under the same key.
Advanced path
This is where retry loops need multiple decisions: accepted follow-up for single render, short-delay retries for in-flight batch keys, and a hard stop when the request body changes.
curl "https://api.solidrelay.io/v1/pdf/jobs/7b6f4c93-34e3-4f6d-a32d-9a2f0b2054d1" \
-H "X-API-Key: YOUR_API_KEY"{
"success": true,
"jobId": "7b6f4c93-34e3-4f6d-a32d-9a2f0b2054d1",
"status": "queued",
"inputMode": "html",
"outputMode": "binary",
"createdAt": "2026-04-10T15:00:00.000Z",
"updatedAt": "2026-04-10T15:00:18.000Z",
"completedAt": null,
"outputAvailable": false,
"outputExpiresAt": null,
"downloadUrlEndpoint": "/v1/pdf/jobs/job_01JX8V4Q8S9R6Z0ABCDE12345/download-url",
"fileName": "document.pdf",
"pdfSize": 0,
"pages": 0,
"processingTimeMs": 0,
"attemptCount": 2,
"retryAttempt": 2,
"maxAttempts": 5,
"willRetry": true,
"deadLettered": false,
"error": {
"code": "RETRY_SCHEDULED",
"message": "Transient render failure. Retry scheduled (attempt 2 of 5)."
}
}HTTP/1.1 409 Conflict
{
"statusCode": 409,
"error": "Conflict",
"message": "Idempotency key is currently being processed. Retry with the same key shortly."
}curl -X POST "https://api.solidrelay.io/v1/pdf/batch" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Idempotency-Key: batch-invoices-2026-04-01" \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "id": "invoice_001", "html": "<h1>Invoice 1 - corrected</h1>" },
{ "id": "invoice_002", "html": "<h1>Invoice 2</h1>" }
]
}'HTTP/1.1 409 Conflict
{
"statusCode": 409,
"error": "Conflict",
"message": "Idempotency key has already been used with a different batch request"
}willRetry: trueon accepted job status means keep following that job, not the original create loop.- The batch message "currently being processed" is the one
409case that still wants a short-delay retry with the same key and same body. - A changed-body conflict is a hard stop. Generate a new key only when you intentionally submit new work.
Retry Decisions
Use one decision policy so every create client behaves the same way
Retry now
retryable
Transport failures, rate limits, and bounded transient server failures. Keep the same key and same body.
Stop create and follow accepted work
accepted checkpoint
If create or replay returns accepted work, store identifiers and continue with status and retrieval routes.
Retry shortly with same key
in-flight batch key
If batch create returns "currently being processed," wait briefly and retry with the same key and same body.
Do not retry as-is
non-retryable
Validation, auth, most other 4xx, and changed-body conflicts require a corrected request or a new logical operation.
Failure Handling
Practical retry advice for real clients
- Use capped exponential backoff with jitter for retryable failures to avoid synchronized retry spikes.
- Treat changed-payload conflict as non-retryable; generate a new key only when sending new work on purpose.
- Treat the batch response "Idempotency key is currently being processed" as retryable with a short delay and unchanged request.
- Do not rotate keys between attempts of the same logical create request.
- Log idempotency key, request hash, status code, and correlation IDs to simplify incident debugging.
Reference pages: single render and batch rendering. Use the errors page as the shared retryability contract for rate limits, transient server failures, and non-retryable client responses.
Next Steps
Go to concept and reference pages for exact contracts
Concept
Idempotency
Use the shared mental model for replay behavior, conflict semantics, and route scope.
Reference
Single render
Check request body contract, accepted behavior, and response details for the single render create route.
Reference
Batch rendering
Check create, status, retrieval, and webhook contract details for batch workflows.
Errors
Errors and limits
Apply one retryability policy across client transport failures, API limits, and transient service faults.