Download a render
Stream a succeeded render's PDF straight from storage, authenticated with your project API key.
Stream a succeeded render's PDF straight from storage, authenticated with your project API key. Use this when you don't want to hand out a public bucket URL — the bytes are served behind your bearer token.
GET /v1/renders/:id/download:id is the render id (UUIDv7) within the authenticated project.
Quick Start
curl
curl https://api.craftkit.dev/v1/renders/0193c2c3/download \
-H "Authorization: Bearer $CRAFTKIT_API_KEY" \
--output render.pdfNode.js
import { writeFile } from 'node:fs/promises';
const res = await fetch('https://api.craftkit.dev/v1/renders/0193c2c3/download', {
headers: { Authorization: `Bearer ${process.env.CRAFTKIT_API_KEY}` },
});
if (!res.ok) throw new Error(`download failed: ${res.status}`);
const bytes = Buffer.from(await res.arrayBuffer());
await writeFile('render.pdf', bytes);Python
import os, requests
res = requests.get(
"https://api.craftkit.dev/v1/renders/0193c2c3/download",
headers={"Authorization": f"Bearer {os.environ['CRAFTKIT_API_KEY']}"},
)
res.raise_for_status()
with open("render.pdf", "wb") as f:
f.write(res.content)Path parameters
| Field | Type | Description | Default |
|---|---|---|---|
id |
string | Render id (UUIDv7). Must belong to the API key's project and have status: "succeeded". |
— |
Response — 200 OK
The response body is the raw PDF. There is no JSON envelope on success.
| Header | Value |
|---|---|
Content-Type |
application/pdf |
Content-Length |
Byte length of the PDF (lets you detect truncation) |
Content-Disposition |
attachment; filename="<id>.pdf" |
The asset is buffered server-side before the response is sent, so a storage read error surfaces as a clean 500 rather than a truncated 200. Always check res.ok (or raise_for_status()) before writing the bytes to disk.
When to use this vs downloadUrl
GET /v1/renders/:id returns a downloadUrl. Its value depends on deployment config:
| Config | downloadUrl returned by GET /v1/renders/:id |
This route |
|---|---|---|
S3_PUBLIC_URL set (e.g. a public R2/MinIO bucket) |
A permanent direct public CDN URL to the object — no auth, no TTL | Still works; the equivalent authenticated fetch if you prefer not to expose the public URL |
S3_PUBLIC_URL not set |
This authenticated route's URL (…/v1/renders/:id/download) |
The only way to fetch the PDF — there is no public object URL |
In short: when no public bucket is configured, downloadUrl points back at this route, and a bearer token is required to fetch the bytes.
Errors
| HTTP | Code | Meaning | Fix |
|---|---|---|---|
| 401 | unauthorized |
Missing, invalid, or revoked API key | Send a valid Authorization: Bearer key |
| 403 | forbidden |
Key's project no longer exists | Use a key from an active project |
| 404 | not_found |
No render with that id in this key's project | Check the id and the API key's project scope |
| 409 | conflict |
Render hasn't succeeded yet, or has no stored asset (message includes the current status) | Poll GET /v1/renders/:id until status is succeeded, then retry |
| 500 | internal |
The PDF could not be read from storage | Transient — retry; if it persists, contact support |
See Errors for the envelope shape and full code list.
Related
- GET /v1/renders/:id — poll the render and read its
downloadUrl - POST /v1/templates/:slug/render — enqueue the render that produces the PDF
- Engagement & analytics — record a
downloadedevent after fetching - Authentication — bearer token format