Skip to main content

Prerequisites

  • A Craftkit project with a published template and its slug.
  • A project API key (ck_live_…) for the environment you are calling.
Create a key in Dashboard → your project → API keys. Copy it immediately — it is shown once. Keys are environment-specific; see Authentication.

1. Verify connectivity

curl https://api.craftkit.dev/health
# → { "status": "ok" }

2. Read the template manifest

Confirm the template exists and learn exactly which keys it expects.
curl https://api.craftkit.dev/v1/templates/invoice \
  -H "Authorization: Bearer $CRAFTKIT_API_KEY"
{
  "slug": "invoice",
  "published": true,
  "currentVersionNumber": 3,
  "manifest": {
    "variables": [
      { "key": "customer.name", "dataType": "text", "required": true },
      { "key": "invoice.total", "dataType": "currency", "required": true }
    ],
    "loops": []
  },
  "jsonSchema": { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "...": "..." }
}

3. Enqueue a render

curl -X POST https://api.craftkit.dev/v1/templates/invoice/render \
  -H "Authorization: Bearer $CRAFTKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "customer": { "name": "Ada Lovelace" },
      "invoice": { "total": 4200 }
    }
  }'
{
  "id": "…",
  "status": "queued",
  "pollUrl": "https://api.craftkit.dev/v1/renders/…",
  "downloadUrl": null,
  "errorMessage": null,
  "createdAt": "2026-06-05T10:00:00.000Z"
}
Rendering is asynchronous. The response is 202 with status: "queued" and downloadUrl: null. There is no synchronous mode — you poll (or use a webhook). See Render lifecycle.

4. Poll until complete

async function waitForRender(renderId: string, timeoutMs = 30_000) {
  const deadline = Date.now() + timeoutMs;
  let delay = 250;
  const headers = { Authorization: `Bearer ${process.env.CRAFTKIT_API_KEY}` };
  while (Date.now() < deadline) {
    const r = await fetch(`https://api.craftkit.dev/v1/renders/${renderId}`, { headers });
    const render = await r.json();
    if (render.status === 'succeeded') return render.downloadUrl as string;
    if (render.status === 'failed') throw new Error(render.errorMessage ?? 'render failed');
    await new Promise((res) => setTimeout(res, delay));
    delay = Math.min(delay * 2, 5_000); // 250ms → 500ms → 1s → 2s → cap 5s
  }
  throw new Error('render timed out');
}

5. Use the PDF

When status is succeeded, downloadUrl points at the rendered PDF. For a durable, guest-shareable link, mint a share link instead.

Go deeper: server-to-server integration

Idempotency, manifest binding, pipelines, images, and webhooks for a production backend.