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
- Start with one accepted create and one completed follow up.
- Persist the accepted envelope and handle retry-aware polling.
- 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, andGET /v1/pdf/jobs/:id/file. - Persist
jobId,statusUrl, andfileUrlfrom 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/pdfreturns202 Accepted. - Use it for both
queuedandprocessingstatus paths after acceptance. - Keep waiting while
willRetryis true, even when the status payload includes anerrorobject. - 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.
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"
}'{
"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"
}{
"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/fileonly after the status iscompleted.
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.
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"
}'{
"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"
}curl "https://api.solidrelay.io/v1/pdf/jobs/8cbf2bde-3e70-46af-84f5-631ccb20fd89" \
-H "X-API-Key: YOUR_API_KEY"{
"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)."
}
}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
queuedandprocessing. - Keep polling when
willRetryistrue, 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.
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"
}'{
"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
templateIdandtemplateVersionId. - When
outputAvailableis true, calldownloadUrlEndpointfor 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, andwillRetryto expose progress in logs and dashboards. - Stop success polling when
deadLetteredis 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
Follow-up contracts
Accepted jobs reference
Status route, retry fields, terminal states, and output readiness rules for async follow up.
Create contract
Single render reference
Request shape, execution modes, accepted envelope, and idempotency behavior for POST /v1/pdf.
Template-backed create
Render from template guide
Use it when your accepted job starts from templateId and optional templateVersionId.
State model
Jobs and lifecycle
Understand state transitions, retry phases, terminal outcomes, and how readiness changes over job lifetime.
Failure model
Errors and retryability
Apply shared error shape and retryability guidance to create, status, and file retrieval failures.