API
Jobs
GET /v1/jobs/{id} — poll an async generation job.
#GET /v1/jobs/{id}
Returns the current state of a job created by POST /v1/generate. Poll at a 2-second interval until the status reaches a terminal state.
Base URL: https://heydecks.com
#Job statuses
| Status | Meaning |
|---|---|
queued | Job is waiting to start. |
running | Generation is in progress. |
succeeded | Deck created. deck object is present in the response. |
failed | Generation failed. error object explains the cause. Credits were refunded. |
#Request example
curl https://heydecks.com/v1/jobs/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer hd_live_…"#Response — succeeded
| Name | Type | Description |
|---|---|---|
id | string | The job UUID. |
status | string | "succeeded" |
kind | string | Always "generate" for jobs created via POST /v1/generate. |
created_at | string | ISO 8601 timestamp when the job was created. |
updated_at | string | ISO 8601 timestamp of the last status change. |
deck | object | Present only when status is "succeeded". See deck fields below. |
deck.id | string | The new deck id. Use this with POST /v1/decks/{id}/publish. |
deck.url | string | Draft URL at https://heydecks.com/d/{slug}. Not publicly accessible until published. |
deck.pdf_url | string | A ready-to-download PDF link, https://heydecks.com/s/{code}. Minted when the job succeeds. |
deck.pptx_url | string | null | The same for PPTX. null means PPTX export isn't on the account's plan — it is not an error, and the job succeeded. |
deck.export_links_expire_at | string | ISO 8601 expiry shared by both links. Read the expiry from this field rather than assuming a fixed window. |
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "succeeded",
"kind": "generate",
"created_at": "2026-06-12T10:00:00.000Z",
"updated_at": "2026-06-12T10:00:28.000Z",
"deck": {
"id": "deck_abc123",
"url": "https://heydecks.com/d/q3-board-update",
"pdf_url": "https://heydecks.com/s/k3mq7rbn2xa",
"pptx_url": null,
"export_links_expire_at": "2026-06-13T10:00:28.000Z"
}
}#Exports
The links on a finished job need no auth header and work while the deck is still a draft. They are the fast path, not the whole story:
- They expire at
export_links_expire_at. Read a job after that and its links are dead (GET /s/{code}answers410 Gone). - They are always a colour-logo, editable export. To choose the logo treatment, force picture-fidelity PPTX, or set your own lifetime, mint your own.
pptx_urlisnullon plans without PPTX export.
Minting is not rendering. The job writes the links but runs no browser, so the first fetch is where the render happens and it is the slow one. A PDF over 25 slides answers 202 with Retry-After and needs polling; anything shorter, and any PPTX, blocks until the file is ready. Either way a client that saves a 202 body saves an HTML holding page, not a PDF. The full rules and a poll loop are under Following the link.
The link is not a snapshot. It resolves the deck when it is fetched, so edits made between the job succeeding and the download are included.
To get a link on demand — any format, any time, draft or published — call POST /v1/decks/{id}/exports with the deck.id from the job. It is synchronous and free on every plan.
Nothing about exporting requires the dashboard.
#Response — failed
| Name | Type | Description |
|---|---|---|
error | object | Present only when status is "failed". See error fields below. |
error.code | string | Machine-readable failure reason, e.g. "generation_failed". |
error.message | string | Human-readable description of the failure. |
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "failed",
"kind": "generate",
"created_at": "2026-06-12T10:00:00.000Z",
"updated_at": "2026-06-12T10:00:05.000Z",
"error": {
"code": "generation_failed",
"message": "…"
}
}A
404 not_foundis returned if the job id doesn't exist or belongs to a different user.