Jobs
Check job status, list your jobs, read per-file results, and get download links.
Authentication
Which credential works depends on the route:
| Route | API key | Cognito JWT |
|---|---|---|
GET /jobs/{job_id} | ✅ | ✅ |
GET /jobs/{job_id}/files | ✅ | ✅ |
GET /jobs/{job_id}/download | ✅ | ✅ |
POST /jobs/{job_id}/cancel | ✅ | ✅ |
GET /jobs | ❌ | ✅ |
GET /jobs/{job_id}/webhooks/history | ❌ | ✅ |
API keys are rejected at the gateway on the JWT-only routes. A job that belongs to another account returns 404 JOB_NOT_FOUND.
GET /jobs/{job_id}
Get the status and details of one job.
curl https://api.podpdf.com/jobs/9f0a4b78-2c0c-4d14-9b8b-123456789abc \
-H "X-API-Key: your_api_key_here"
Response
Common fields: job_id, status, job_type, mode, pages, created_at, completed_at, error_message.
- Quick job
- Long job
- Bulk job
{
"job_id": "9f0a4b78-2c0c-4d14-9b8b-123456789abc",
"status": "completed",
"job_type": "quick",
"mode": "html",
"pages": 3,
"truncated": false,
"created_at": "2026-09-12T10:30:00.000Z",
"completed_at": "2026-09-12T10:30:05.000Z",
"timeout_occurred": false,
"s3_url": "https://...pdf?X-Amz-Signature=...",
"s3_url_expires_at": "2026-09-12T11:30:05.000Z",
"error_message": null
}
s3_url is present only when the request used store: true.
{
"job_id": "8e1b5c89-3d1d-5e25-ac9c-234567890def",
"status": "completed",
"job_type": "long",
"mode": "markdown",
"pages": 64,
"created_at": "2026-09-12T10:30:00.000Z",
"completed_at": "2026-09-12T10:32:15.000Z",
"s3_url": "https://...pdf?X-Amz-Signature=...",
"s3_url_expires_at": "2026-09-12T11:32:15.000Z",
"webhook_delivered": true,
"webhook_delivered_at": "2026-09-12T10:32:20.000Z",
"webhook_retry_count": 0,
"error_message": null
}
{
"job_id": "3f5c2a7e-8b1d-4c9e-a2f4-6d7e8f9a0b1c",
"status": "partial_failed",
"job_type": "bulk",
"mode": "html",
"bundle_type": "html",
"file_count": 42,
"processed_count": 42,
"success_count": 40,
"failure_count": 2,
"pages_total": 118,
"s3_url": "https://...-output.zip?X-Amz-Signature=...",
"s3_url_expires_at": "2026-09-12T11:03:12.000Z",
"error_code": null,
"webhook_delivered": false,
"created_at": "2026-09-12T10:00:00.000Z",
"completed_at": "2026-09-12T10:03:12.000Z"
}
file_count is null until the archive has been opened. processed_count climbs while the job runs. error_code is set only when the whole job fails — see POST /bulkjob for the codes.
Status values
| Status | Meaning |
|---|---|
queued | Accepted, waiting to be processed (long and bulk jobs) |
processing | Being rendered |
completed | Finished; for bulk jobs, every file converted |
partial_failed | Bulk jobs only: some files converted, some failed or were skipped |
failed | Nothing was produced; see error_message and error_code |
timeout | Quick jobs only: the 30-second limit was reached |
cancelled | Bulk jobs and mail merge campaigns: cancelled with POST /jobs/{job_id}/cancel. PDFs made before the cancel are in the output; emails sent before it stay sent |
Job types
quick (synchronous), long (asynchronous, one document), bulk (asynchronous, many documents), pdfop (PDF merge and split).
Merge and split jobs also return operation (merge or split), split_mode, input_files, input_pages, outputs_produced, and while a background job runs, output_count and processed_count.
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | UNAUTHORIZED | Missing or invalid credentials |
| 403 | ACCOUNT_NOT_FOUND | No account for this user |
| 404 | JOB_NOT_FOUND | Unknown job, or it belongs to another account |
GET /jobs
List your jobs, newest first. JWT only.
curl "https://api.podpdf.com/jobs?job_type=bulk&limit=20" \
-H "Authorization: Bearer <cognito_id_token>"
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | number | 50 | Between 1 and 100 |
next_token | string | — | Pagination token from a previous response |
status | string | — | draft, queued, processing, completed, partial_failed, failed, timeout, cancelled |
job_type | string | — | quick, long, bulk |
truncated | boolean | — | Kept for backward compatibility; new jobs are always false |
month | string | — | YYYY-MM, filters by creation month (UTC) |
Response
{
"jobs": [
{
"job_id": "3f5c2a7e-8b1d-4c9e-a2f4-6d7e8f9a0b1c",
"status": "completed",
"job_type": "bulk",
"bundle_type": "markdown",
"file_count": 12,
"success_count": 12,
"failure_count": 0,
"pages_total": 36,
"created_at": "2026-09-12T10:00:00.000Z",
"completed_at": "2026-09-12T10:01:40.000Z"
}
],
"next_token": "eyJqb2JfaWQiOiIzZjVjMmE3ZSJ9",
"count": 1
}
Pass next_token back as a query parameter to fetch the next page. It is null on the last page.
POST /jobs/{job_id}/cancel
Cancel a bulk job or a mail merge campaign. Other job types return 409.
curl -X POST https://api.podpdf.com/jobs/9f0a7b1c-4d2e-4c1a-9f7e-2b3c4d5e6f70/cancel \
-H "X-API-Key: your_api_key_here"
What happens depends on how far the job has got:
| Job status | Response | What you get | What is charged |
|---|---|---|---|
queued | 200, status: "cancelled" | Nothing: no file was converted | Nothing |
processing | 202, status: "processing" | The job stops before its next file. PDFs already made stay in its output ZIP; the other files are skipped with error_code: "CANCELLED" | Only the PDFs already made |
| anything else | 409 JOB_NOT_CANCELLABLE | The job had already finished | As usual |
{
"job_id": "9f0a7b1c-4d2e-4c1a-9f7e-2b3c4d5e6f70",
"status": "processing",
"cancel_requested": true,
"message": "The job stops before its next file. PDFs already made are kept in the output and charged."
}
After a 202, keep polling GET /jobs/{job_id}: cancel_requested is true straight away, and status becomes cancelled once the current file finishes. If some PDFs were made, GET /jobs/{job_id}/download returns them. A bulk.job.cancelled webhook is sent in both cases. Cancelling twice is harmless.
A mail merge campaign stops within a few emails of a cancel. Emails already sent are charged; the rest are marked skipped with error_code: "CANCELLED", and a mailmerge.campaign.cancelled webhook follows. A draft cannot be cancelled: delete it with DELETE /mailmerge/campaigns/{id}.
GET /jobs/{job_id}/files
Per-file results of a bulk or merge/split job. Other job types return 400 INVALID_PARAMETER.
For merge and split jobs, each row is one output file and also has page_range, size_bytes and warnings; download a single file with GET /jobs/{job_id}/files/{file_index}/download.
curl https://api.podpdf.com/jobs/3f5c2a7e-8b1d-4c9e-a2f4-6d7e8f9a0b1c/files \
-H "X-API-Key: your_api_key_here"
{
"job_id": "3f5c2a7e-8b1d-4c9e-a2f4-6d7e8f9a0b1c",
"status": "partial_failed",
"bundle_type": "html",
"count": 3,
"files": [
{
"index": 0,
"input": "reports/april.html",
"output": "reports/april.pdf",
"status": "success",
"pages": 3,
"error_code": null,
"error_message": null,
"blocked_requests": 0,
"missing_assets": 0,
"render_timeout": false
},
{
"index": 1,
"input": "reports/huge.html",
"output": null,
"status": "failed",
"pages": 0,
"error_code": "FILE_TOO_LARGE",
"error_message": "File exceeds the 10 MB limit",
"blocked_requests": 0,
"missing_assets": 0,
"render_timeout": false
},
{
"index": 2,
"input": "reports/zeta.html",
"output": null,
"status": "skipped",
"pages": 0,
"error_code": "BULK_PAGE_BUDGET_EXCEEDED",
"error_message": "Skipped: the batch page budget of 200 pages was reached",
"blocked_requests": 0,
"missing_assets": 0,
"render_timeout": false
}
]
}
files is empty until the job finishes.
| Field | Meaning |
|---|---|
status | success, failed, or skipped |
blocked_requests | Requests the page made to local files or private addresses, which were blocked |
missing_assets | References to files that were not in the bundle |
render_timeout | The page never went idle within 20 seconds and was printed as it was |
Only success files are billed. The same list is included as manifest.json in the output ZIP.
GET /jobs/{job_id}/files/{file_index}/download
A fresh download link for one output file of a merge or split job. file_index is the index from /files.
curl https://api.podpdf.com/jobs/5b2d8f40-1e7c-4a8b-9c3d-6e0f1a2b3c4d/files/2/download \
-H "X-API-Key: your_api_key_here"
{
"job_id": "5b2d8f40-1e7c-4a8b-9c3d-6e0f1a2b3c4d",
"file_index": 2,
"name": "payslip-003.pdf",
"download_url": "https://...payslip-003.pdf?X-Amz-Signature=...",
"expires_at": "2026-09-13T11:10:00.000Z",
"expires_in_seconds": 3600,
"content_type": "application/pdf",
"size_bytes": 48210
}
| Status | Code | Meaning |
|---|---|---|
| 400 | INVALID_PARAMETER | The job is not a merge/split job |
| 404 | JOB_NOT_FOUND | Unknown job, or it belongs to another account |
| 404 | FILE_NOT_FOUND | No successful output at that index |
| 409 | JOB_NOT_READY | The job hasn't finished |
| 410 | OUTPUT_EXPIRED | The file was deleted after 7 days |
GET /jobs/{job_id}/download
Get a fresh download link for any job with stored output: bulk jobs, long jobs, merge and split jobs (the merged PDF, or a ZIP of every split file), and quick jobs submitted with store: true.
curl https://api.podpdf.com/jobs/3f5c2a7e-8b1d-4c9e-a2f4-6d7e8f9a0b1c/download \
-H "X-API-Key: your_api_key_here"
{
"job_id": "3f5c2a7e-8b1d-4c9e-a2f4-6d7e8f9a0b1c",
"download_url": "https://...-output.zip?X-Amz-Signature=...",
"expires_at": "2026-09-12T11:10:00.000Z",
"expires_in_seconds": 3600,
"content_type": "application/zip",
"size_bytes": 1843200
}
Links last one hour. Call this endpoint again for a new one — files are kept for 30 days.
Errors
| Status | Code | Meaning |
|---|---|---|
| 404 | JOB_NOT_FOUND | Unknown job, or it belongs to another account |
| 404 | OUTPUT_NOT_AVAILABLE | The job has no stored output (a quick job without store: true) |
| 409 | JOB_NOT_READY | The job has not reached completed or partial_failed yet |
| 410 | OUTPUT_EXPIRED | The file was deleted after 30 days |
GET /jobs/{job_id}/webhooks/history
Delivery history for the webhooks triggered by one job. JWT only. See Webhooks for the record shape and filters.
Next Steps
- POST /bulkjob — convert many files in one job
- Webhooks — get notified instead of polling
- Limits — every limit in one place