> ## Documentation Index
> Fetch the complete documentation index at: https://docs.craftkit.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Render lifecycle

> What actually happens after you POST a render: async-only behavior, the authoritative status enum, and how durable the download URL really is.

## Rendering is asynchronous — there is no sync mode

The render request schema defines `options.sync`, but **the render endpoint does not honor it**.
Every render is enqueued and the endpoint returns `202` with `status: "queued"` and
`downloadUrl: null`. Do not wait on the create response for a finished PDF — **poll** the render,
or use a [webhook](/guides/webhooks).

```json theme={null}
// POST /v1/templates/:slug/render  → 202
{ "id": "…", "status": "queued", "pollUrl": "…/v1/renders/…", "downloadUrl": null, "errorMessage": null }
```

<Tip>
  Poll with exponential backoff (250ms → 500ms → 1s → 2s → cap 5s) against a 30s deadline. See the
  loop in the [Quickstart](/quickstart#4-poll-until-complete).
</Tip>

## Status enum

The authoritative render status values are:

| Status      | Meaning                                                                 |
| ----------- | ----------------------------------------------------------------------- |
| `queued`    | accepted, waiting for a worker                                          |
| `rendering` | a worker is producing the PDF                                           |
| `succeeded` | terminal — `downloadUrl` is populated                                   |
| `failed`    | terminal — `errorMessage` explains why                                  |
| `cancelled` | defined in the enum but **never produced** today (no cancellation path) |

Branch on `succeeded` and `failed`. There is no `processing` status — if you saw it in an older
doc, the real intermediate state is `rendering`. You can treat `cancelled` defensively, but it will
not occur.

## `downloadUrl` durability

When a render succeeds, `downloadUrl` is the rendered PDF's object-storage URL:

* It is a **permanent, public, non-presigned URL** (the project's public bucket base + the asset
  key). It does **not** expire on a TTL, and the path is unguessable (keyed by the render's UUID).
* It is `null` until the render succeeds, and also `null` if the deployment has no public bucket
  configured.

<Warning>
  `downloadUrl` is not a time-limited link. If you need expiry or a private (non-public-bucket)
  download, use the partner-key [private download stream](/api-reference/download-render) or mint a
  revokable [share link](/guides/sharing). Presigned/expiring `downloadUrl` support is on the
  roadmap.
</Warning>

## Re-fetch later (offline replay)

[`GET /v1/renders/:id`](/api-reference/get-render) remains valid for your project key
**indefinitely** after completion. This is the durable record for offline-first flows: the render
may fire only on reconnect, and you can re-fetch its result by id at any later time.

```json theme={null}
// GET /v1/renders/:id
{
  "id": "…",
  "status": "succeeded",
  "pollUrl": "…",
  "downloadUrl": "https://…/renders/…/….pdf",
  "errorMessage": null,
  "createdAt": "2026-06-05T10:00:00.000Z",
  "completedAt": "2026-06-05T10:00:03.120Z",
  "durationMs": 3120
}
```
