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

  1. Create request payload and idempotency key as one persisted unit.
  2. Retry only retryable failures with the exact same key and exact same body.
  3. 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.

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.

First attempt
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"
  }'
Safe replay after ambiguous failure
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"
  }'
Replay can still return accepted work
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, store jobId, statusUrl, and fileUrl, 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.

Accepted batch create
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>" }
    ]
  }'
Persist this accepted envelope
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.

Accepted single-render follow-up
curl "https://api.solidrelay.io/v1/pdf/jobs/7b6f4c93-34e3-4f6d-a32d-9a2f0b2054d1" \
  -H "X-API-Key: YOUR_API_KEY"
Retry-aware job status
{
  "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)."
  }
}
In-flight batch-key conflict
HTTP/1.1 409 Conflict
{
  "statusCode": 409,
  "error": "Conflict",
  "message": "Idempotency key is currently being processed. Retry with the same key shortly."
}
Changed-body retry that must stop
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>" }
    ]
  }'
Changed-body conflict
HTTP/1.1 409 Conflict
{
  "statusCode": 409,
  "error": "Conflict",
  "message": "Idempotency key has already been used with a different batch request"
}
  • willRetry: true on accepted job status means keep following that job, not the original create loop.
  • The batch message "currently being processed" is the one 409 case 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.

Next Steps

Go to concept and reference pages for exact contracts