Craftkitdocs

Builder & renders API

Server-side embed endpoints: create a template from a builder draft (partner API key OR session JWT), list a project's builder templates (API key only), and list the project's renders for the embedding host (API key only).

Server-side endpoints for the embedded template builder. Create templates in a partner project, list them back, and read the render history that embed sessions produce. These power the host application around the iframe — the iframe itself uses the session JWT, while server-to-server calls use a project API key.

POST /v1/embed/builder/templates
GET  /v1/embed/builder/templates
GET  /v1/embed/renders

Authentication at a glance

Endpoint Accepts
POST /v1/embed/builder/templates Partner API key or embed session JWT
GET /v1/embed/builder/templates Partner API key only
GET /v1/embed/renders Partner API key only

The "partner API key" is an ordinary project API key for a project that has the embed partner enabled. Send it as Authorization: Bearer $CRAFTKIT_API_KEY. The "session JWT" is the short-lived token minted for an iframe — send it as Authorization: Bearer <session_token>. Session JWTs are validated against the iframe request origin, so they only work from inside an allow-listed embed.


POST /v1/embed/builder/templates

Creates a new template in the authenticated partner's project from a builder draft. The draft is stored verbatim; if the caller has publish rights and the layout parses as a render-ready document, a first version is published automatically.

Auth

Accepts either credential:

  • Authorization: Bearer $CRAFTKIT_API_KEY — partner API key. Server-to-server; grants both saveDraft and publish.
  • Authorization: Bearer <session_token> — embed session JWT. Rights come from the session's permissions claims (saveDraft, publish). The request must originate from an allow-listed iframe origin.

A caller without saveDraft rights is rejected with 403 permission_denied. Publishing is attempted only when the caller also has publish.

Quick Start

curl

curl -X POST https://api.craftkit.dev/v1/embed/builder/templates \
  -H "Authorization: Bearer $CRAFTKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "documentTemplate": {
      "name": "Charter Handover",
      "presetKey": "charter-handover",
      "layout": { "sections": [] }
    }
  }'

Node.js

const res = await fetch('https://api.craftkit.dev/v1/embed/builder/templates', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CRAFTKIT_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    documentTemplate: {
      name: 'Charter Handover',
      presetKey: 'charter-handover',
      layout: { sections: [] },
    },
  }),
});

const { documentTemplate } = await res.json();
// documentTemplate._id is the new template's UUID

Python

import os, requests

res = requests.post(
    "https://api.craftkit.dev/v1/embed/builder/templates",
    headers={"Authorization": f"Bearer {os.environ['CRAFTKIT_API_KEY']}"},
    json={
        "documentTemplate": {
            "name": "Charter Handover",
            "presetKey": "charter-handover",
            "layout": {"sections": []},
        }
    },
)
result = res.json()

Request body

{
  "documentTemplate": {
    "name": "Charter Handover",
    "presetKey": "charter-handover",
    "layout": { "sections": [] }
  }
}
Field Type Description Default
documentTemplate object The builder draft to persist. Stored as-is in builderDraft; the whole object is also returned (adapted) in the response. {}
documentTemplate.name string Template display name. The slug is derived from it and suffixed with a short random token to keep it unique. "Untitled template"
documentTemplate.layout object The builder's internal canvas draft (react-email-dnd CanvasDocument). It is not validated on create — publishing parses it as a render-ready layout and silently skips publish if it does not match.
documentTemplate.presetKey string | null Template-type identifier (for example charter-handover). Persisted to presetKey. null

The draft is opaque to this endpoint. Any extra keys the builder sends (category, fields, sections, bindings, design, settings, …) are stored inside builderDraft and round-tripped back through the response adapter. Only name, layout, and presetKey are interpreted server-side.

Response — 200 OK

{
  "documentTemplate": {
    "_id": "0193c2c3-...",
    "organizationId": "proj_01j...",
    "name": "Charter Handover",
    "sD": false,
    "cAt": "2026-06-05T10:00:00.000Z",
    "presetKey": "charter-handover",
    "isCustomTemplate": true,
    "layout": { "sections": [] },
    "design": null
  }
}
Field Type Description
documentTemplate._id string New template UUID. Use it as the templateExternalId when minting a fill session.
documentTemplate.organizationId string The owning project's id (Craftkit's tenant boundary).
documentTemplate.name string Resolved template name.
documentTemplate.sD boolean Soft-delete flag. false for a freshly created template.
documentTemplate.cAt string ISO-8601 creation timestamp.
documentTemplate.presetKey string | null The preset key you supplied.
documentTemplate.layout object | null The persisted builder layout draft.

The full adapted draft (fields, sections, bindings, design, settings, category, …) is included when present in the draft.

Errors

HTTP Code Meaning Fix
400 invalid_json Body wasn't valid JSON Check Content-Type and JSON.stringify
401 missing_or_invalid_authorization No bearer token, or neither an API key nor a valid session token Send a project API key or a valid session JWT from an allow-listed origin
403 permission_denied Authenticated, but the session lacks saveDraft rights Mint the session with permissions.saveDraft = true
500 insert_failed The template row could not be written Retry; if it persists, contact support

GET /v1/embed/builder/templates

Lists all non-deleted templates in the authenticated partner's project, newest-updated first. Lightweight — it returns identifiers and the draft category, not the full draft.

Auth

Partner API key only: Authorization: Bearer $CRAFTKIT_API_KEY. A session JWT is not accepted here.

Quick Start

curl

curl https://api.craftkit.dev/v1/embed/builder/templates \
  -H "Authorization: Bearer $CRAFTKIT_API_KEY"

Node.js

const res = await fetch('https://api.craftkit.dev/v1/embed/builder/templates', {
  headers: { Authorization: `Bearer ${process.env.CRAFTKIT_API_KEY}` },
});
const { templates } = await res.json();

Python

import os, requests

res = requests.get(
    "https://api.craftkit.dev/v1/embed/builder/templates",
    headers={"Authorization": f"Bearer {os.environ['CRAFTKIT_API_KEY']}"},
)
templates = res.json()["templates"]

Response — 200 OK

{
  "templates": [
    {
      "id": "0193c2c3-...",
      "name": "Charter Handover",
      "slug": "charter-handover-1a2b3c4d",
      "createdAt": "2026-05-01T09:00:00.000Z",
      "updatedAt": "2026-06-01T12:00:00.000Z",
      "category": "charter"
    }
  ]
}
Field Type Description
templates array Templates in the project, ordered by updatedAt descending.
templates[].id string Template UUID.
templates[].name string Template name.
templates[].slug string URL-safe slug (name-derived, random-suffixed).
templates[].createdAt string ISO-8601 creation timestamp.
templates[].updatedAt string ISO-8601 last-update timestamp.
templates[].category string | null The draft's category, if set; otherwise null.

Errors

HTTP Code Meaning Fix
401 missing_authorization No Authorization header Send Authorization: Bearer $CRAFTKIT_API_KEY
401 invalid_credentials API key not found, revoked, or embed not enabled for the project Check the key and that the project has an embed partner

GET /v1/embed/renders

Lists renders (form submissions and other PDF instances) for the partner's project so an embedding host can show a document history without a Craftkit dashboard session. Internal preview and dashboard renders are always excluded.

Auth

Partner API key only: Authorization: Bearer $CRAFTKIT_API_KEY. A session JWT is not accepted here.

Quick Start

curl

curl "https://api.craftkit.dev/v1/embed/renders?status=succeeded&limit=50" \
  -H "Authorization: Bearer $CRAFTKIT_API_KEY"

Node.js

const params = new URLSearchParams({ status: 'succeeded', limit: '50' });
const res = await fetch(`https://api.craftkit.dev/v1/embed/renders?${params}`, {
  headers: { Authorization: `Bearer ${process.env.CRAFTKIT_API_KEY}` },
});
const { renders, limit, offset } = await res.json();

Python

import os, requests

res = requests.get(
    "https://api.craftkit.dev/v1/embed/renders",
    headers={"Authorization": f"Bearer {os.environ['CRAFTKIT_API_KEY']}"},
    params={"status": "succeeded", "limit": 50},
)
result = res.json()

Query parameters

Field Type Description Default
limit integer Page size. Clamped to 1500. 100
offset integer Number of rows to skip (clamped to >= 0). 0
templateId string Filter to a single Craftkit template UUID.
status string Filter by render status: queued, rendering, succeeded, failed, or cancelled.

Response — 200 OK

{
  "renders": [
    {
      "id": "0193c2c3-...",
      "status": "succeeded",
      "source": "form",
      "templateId": "0193c2c3-...",
      "templateName": "Charter Handover",
      "inputData": { "customer": { "name": "Acme Corp" } },
      "downloadUrl": "https://cdn.craftkit.dev/renders/0193c2c3-....pdf",
      "errorMessage": null,
      "durationMs": 1820,
      "createdAt": "2026-06-05T10:00:00.000Z",
      "completedAt": "2026-06-05T10:00:02.000Z"
    }
  ],
  "limit": 50,
  "offset": 0
}
Field Type Description
renders array Matching renders, newest-created first.
renders[].id string Render UUID.
renders[].status string queued, rendering, succeeded, failed, or cancelled.
renders[].source string Origin of the render. preview and dashboard are never returned.
renders[].templateId string The render's template UUID.
renders[].templateName string | null Template name, or null if the template was deleted.
renders[].inputData object The variable data the render was produced from.
renders[].downloadUrl string | null Public PDF URL once the render succeeds; null until the asset exists.
renders[].errorMessage string | null Failure detail, if the render failed.
renders[].durationMs number | null Render time in milliseconds, when available.
renders[].createdAt string ISO-8601 enqueue timestamp.
renders[].completedAt string | null ISO-8601 completion timestamp, or null if not finished.
limit number The effective (clamped) page size.
offset number The effective offset.

status is filtered in the handler after the page is fetched, so a page may contain fewer than limit rows when a status filter is applied. Paginate by offset until renders comes back empty.

Errors

HTTP Code Meaning Fix
401 missing_authorization No Authorization header Send Authorization: Bearer $CRAFTKIT_API_KEY
401 invalid_credentials API key not found, revoked, or embed not enabled for the project Check the key and that the project has an embed partner

Last revised: 2026-06-26