Guide

Handle accepted single render work from first 202 to final retrieval

Use this guide for one task: when POST /v1/pdf returns 202 Accepted, keep moving through job follow up until the job is clearly completed or clearly terminally failed. The examples start with the smallest accepted flow, then preserve the fuller production sequence, then show an advanced template-backed path.

Implementation path

  1. Start with one accepted create and one completed follow up.
  2. Persist the accepted envelope and handle retry-aware polling.
  3. Add template-backed accepted work only when the workflow needs that extra complexity.

Start here when

You already have accepted work and need the operational follow-up sequence.

Primary routes

POST /v1/pdf, GET /v1/pdf/jobs/:id, and GET /v1/pdf/jobs/:id/file.

Success looks like

No duplicate create calls, stable polling behavior, and file retrieval only when ready.

Before You Begin

Set up the minimum state needed for a reliable accepted work handler

  • Use an API key that can call POST /v1/pdf, GET /v1/pdf/jobs/:id, and GET /v1/pdf/jobs/:id/file.
  • Persist jobId, statusUrl, and fileUrl from the accepted response so worker restarts can resume safely.
  • Keep one idempotency key per logical create operation so retries do not create duplicate accepted work.
  • Decide a polling cadence and max wait window before implementation so status checks are predictable under load.
  • Do not assume sync mode always returns the final PDF immediately. Requests can still fall back to accepted work when they miss the short wait window.

When To Use It

Apply this workflow every time create returns accepted work

Accepted work is a normal success path, not an exception. Use this guide whenever your single render create request does not return immediate completed output.

  • Use this guide whenever POST /v1/pdf returns 202 Accepted.
  • Use it for both queued and processing status paths after acceptance.
  • Keep waiting while willRetry is true, even when the status payload includes an error object.
  • Use execution model and jobs and lifecycle if your team needs the shared mental model before coding.

Example Ladder

Build the accepted workflow from smallest path to fullest capability

These examples all teach the same job: create once, poll the same jobId, then retrieve only after readiness checks pass.

Easiest path

Start with one tiny async render so you can prove the accepted envelope, one completed status check, and one final file download.

Minimal accepted create
curl -X POST "https://api.solidrelay.io/v1/pdf" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<h1>Hello from accepted render</h1>",
    "output": "binary",
    "executionMode": "async"
  }'
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"
}
Completed status
{
  "success": true,
  "jobId": "7b6f4c93-34e3-4f6d-a32d-9a2f0b2054d1",
  "status": "completed",
  "inputMode": "html",
  "outputMode": "binary",
  "createdAt": "2026-04-10T14:00:00.000Z",
  "updatedAt": "2026-04-10T14:00:04.000Z",
  "completedAt": "2026-04-10T14:00:04.000Z",
  "outputAvailable": true,
  "outputExpiresAt": "2026-04-11T14:00:04.000Z",
  "downloadUrlEndpoint": "/v1/pdf/jobs/7b6f4c93-34e3-4f6d-a32d-9a2f0b2054d1/download-url",
  "fileName": "document.pdf",
  "pdfSize": 245789,
  "pages": 1,
  "processingTimeMs": 742,
  "attemptCount": 1,
  "retryAttempt": null,
  "maxAttempts": 5,
  "willRetry": false,
  "deadLettered": false,
  "error": null
}
  • Persist the accepted envelope before you start polling.
  • Download with GET /v1/pdf/jobs/:id/file only after the status is completed.

Realistic path

This is the production-ready version most teams should copy first: create with idempotency, keep polling while retries are still scheduled, then download after completion.

Accepted create with idempotency
curl -X POST "https://api.solidrelay.io/v1/pdf" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Idempotency-Key: accepted-flow-001" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<h1>Invoice INV-1001</h1><p>Generated asynchronously.</p>",
    "output": "url",
    "executionMode": "async",
    "filename": "invoice-1001.pdf"
  }'
Persist this accepted payload
{
  "success": true,
  "jobId": "8cbf2bde-3e70-46af-84f5-631ccb20fd89",
  "status": "queued",
  "statusUrl": "/v1/pdf/jobs/8cbf2bde-3e70-46af-84f5-631ccb20fd89",
  "fileUrl": "/v1/pdf/jobs/8cbf2bde-3e70-46af-84f5-631ccb20fd89/file"
}
Poll the same jobId
curl "https://api.solidrelay.io/v1/pdf/jobs/8cbf2bde-3e70-46af-84f5-631ccb20fd89" \
  -H "X-API-Key: YOUR_API_KEY"
Retry-aware polling response
{
  "success": true,
  "jobId": "8cbf2bde-3e70-46af-84f5-631ccb20fd89",
  "status": "queued",
  "inputMode": "html",
  "outputMode": "url",
  "createdAt": "2026-04-10T14:03:15.000Z",
  "updatedAt": "2026-04-10T14:03:48.000Z",
  "completedAt": null,
  "outputAvailable": false,
  "outputExpiresAt": null,
  "downloadUrlEndpoint": "/v1/pdf/jobs/8cbf2bde-3e70-46af-84f5-631ccb20fd89/download-url",
  "fileName": "invoice-1001.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)."
  }
}
Download after completion
curl "https://api.solidrelay.io/v1/pdf/jobs/8cbf2bde-3e70-46af-84f5-631ccb20fd89/file" \
  -H "X-API-Key: YOUR_API_KEY" \
  --output invoice-1001.pdf
  • Keep polling for queued and processing.
  • Keep polling when willRetry is true, even if an error is present.
  • Do not send a duplicate create just because one poll shows transient retry state.

Advanced path

Accepted work also applies to richer template-backed renders. This is where version pinning, larger business payloads, and stricter audit requirements join the same accepted job flow.

Template-backed accepted create
curl -X POST "https://api.solidrelay.io/v1/pdf" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Idempotency-Key: invoice-2026-0042-v7" \
  -H "Content-Type: application/json" \
  -d '{
    "templateId": "2a2bc604-feb7-41b5-8e2c-2e2784ecb67d",
    "templateVersionId": "1b7f6842-48bc-47da-bf7c-bf8fa3f74637",
    "data": {
      "invoice": {
        "number": "INV-2026-0042"
      },
      "customer": {
        "name": "Acme Corp"
      },
      "totals": {
        "grand": "245.00"
      }
    },
    "output": "url",
    "filename": "invoice-2026-0042-approved.pdf",
    "executionMode": "async"
  }'
Completed status with explicit retrieval
{
  "success": true,
  "jobId": "8cbf2bde-3e70-46af-84f5-631ccb20fd89",
  "status": "completed",
  "inputMode": "template",
  "outputMode": "url",
  "createdAt": "2026-04-10T14:03:15.000Z",
  "updatedAt": "2026-04-10T14:04:01.000Z",
  "completedAt": "2026-04-10T14:04:01.000Z",
  "outputAvailable": true,
  "outputExpiresAt": "2026-04-11T14:04:01.000Z",
  "downloadUrlEndpoint": "/v1/pdf/jobs/8cbf2bde-3e70-46af-84f5-631ccb20fd89/download-url",
  "fileName": "invoice-2026-0042-approved.pdf",
  "pdfSize": 382144,
  "pages": 3,
  "processingTimeMs": 1842,
  "attemptCount": 3,
  "retryAttempt": null,
  "maxAttempts": 5,
  "willRetry": false,
  "deadLettered": false,
  "error": null
}
  • The accepted follow-up routes stay the same even when the original create request used templateId and templateVersionId.
  • When outputAvailable is true, call downloadUrlEndpoint for URL handoff or use the file route for direct download handling.
  • Use the render-from-template guide when you need the full template creation and versioning setup before this follow-up flow.

01

Create once and persist the accepted envelope

Persist jobId, statusUrl, and fileUrl immediately so restarts resume with the same job record.

02

Classify every poll result before taking action

Treat each response as in-progress, terminal success, or terminal failure. This keeps retries and retrieval logic predictable.

03

Retrieve only after readiness checks pass

Call the file route only after completed, and re-check status first if retrieval fails.

04

Offer the signed-in dashboard recovery path when users work in the app

Accepted single renders created from Explorer can be revisited from dashboard render history. That history and its detail route let users refresh status, inspect structured errors, and download completed PDFs after page reloads or later sessions.

Failure Handling

Recover by failure type, not with one generic retry

Separate these three failures in your client so you can recover safely without introducing duplicate work.

Create failure

before accepted response

Fix auth, payload, or validation issues first. Do not invent a jobId, and do not proceed to polling until create actually succeeds.

Status failure

after accepted response

Retry status checks using the stored jobId. Issue a new create request only when you have confirmed terminal failure and need a new render operation.

File retrieval failure

after completion checks

Re-check status first. GET /v1/pdf/jobs/:id/file returns 404 when the job is not completed, not downloadable, or outside retention.

  • Track retryAttempt, maxAttempts, and willRetry to expose progress in logs and dashboards.
  • Stop success polling when deadLettered is true and surface the terminal error clearly.
  • Use the shared errors page for response shape and retryability policy across create, status, and file retrieval routes.

Next Steps

Use jobs, concepts, and errors pages as canonical detail