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/rendersAuthentication 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 bothsaveDraftandpublish.Authorization: Bearer <session_token>— embed session JWT. Rights come from the session'spermissionsclaims (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 UUIDPython
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 insidebuilderDraftand round-tripped back through the response adapter. Onlyname,layout, andpresetKeyare 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 1–500. |
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. |
statusis filtered in the handler after the page is fetched, so a page may contain fewer thanlimitrows when a status filter is applied. Paginate byoffsetuntilrenderscomes 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 |
Related
- Form submit API — the iframe-side submit + image upload endpoints that produce
formrenders - Embed catalogs — publish the variable catalog a builder session uses
- Embed quickstart — mint a session and load the iframe
- Authentication — bearer token format
Last revised: 2026-06-26