Craftkitdocs

Session API (mint & refresh)

Mint a signed embed session (POST /v1/embed/sessions) and rotate it with the single-use renew token (POST /v1/embed/sessions/refresh).

Mint a short-lived embed session your front-end can load in an iframe. The partner backend calls this with its project API key; the response carries a signed session JWT, the iframe URL to mount, and a single-use renew token. Refresh rotates that token before the session expires.

POST /v1/embed/sessions
POST /v1/embed/sessions/refresh

Both endpoints authenticate with a project API key (Authorization: Bearer ck_live_…). The key's project must have embed enabled (an embed partner row), or auth fails with invalid_credentials. Sessions live for 4 hours.

Quick Start

curl

curl -X POST https://api.craftkit.dev/v1/embed/sessions \
  -H "Authorization: Bearer $CRAFTKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tenant": { "externalId": "org_123", "displayName": "Acme Corp" },
    "actor":  { "externalId": "usr_456", "displayName": "Jane Smith", "email": "jane@acme.com" },
    "scope":  { "mode": "edit", "templateExternalId": "invoice" },
    "catalogRef": { "name": "my-catalog" },
    "permissions": { "publish": true, "saveDraft": true }
  }'

Node.js

const res = await fetch('https://api.craftkit.dev/v1/embed/sessions', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CRAFTKIT_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    tenant: { externalId: 'org_123', displayName: 'Acme Corp' },
    actor: { externalId: 'usr_456', displayName: 'Jane Smith', email: 'jane@acme.com' },
    scope: { mode: 'edit', templateExternalId: 'invoice' },
    catalogRef: { name: 'my-catalog' },
    permissions: { publish: true, saveDraft: true },
  }),
});

const { iframe_url, renew_token, expires_at } = await res.json();
// Mount iframe_url in an <iframe>; persist renew_token to rotate before expires_at.

Python

import os, requests

res = requests.post(
    "https://api.craftkit.dev/v1/embed/sessions",
    headers={"Authorization": f"Bearer {os.environ['CRAFTKIT_API_KEY']}"},
    json={
        "tenant": {"externalId": "org_123", "displayName": "Acme Corp"},
        "actor": {"externalId": "usr_456", "displayName": "Jane Smith", "email": "jane@acme.com"},
        "scope": {"mode": "edit", "templateExternalId": "invoice"},
        "catalogRef": {"name": "my-catalog"},
        "permissions": {"publish": True, "saveDraft": True},
    },
)
session = res.json()

Request body

{
  "tenant": {
    "externalId": "org_123",
    "displayName": "Acme Corp",
    "branding": { "primaryColor": "#0F62FE" }
  },
  "actor": {
    "externalId": "usr_456",
    "displayName": "Jane Smith",
    "email": "jane@acme.com",
    "avatarUrl": "https://acme.com/avatars/jane.png"
  },
  "scope": { "mode": "edit", "templateExternalId": "invoice", "initialName": "New invoice" },
  "catalogRef": { "name": "my-catalog", "version": 2 },
  "permissions": { "publish": true, "saveDraft": true, "delete": false },
  "permissionsPreset": "editor",
  "branding": { "primaryColor": "#0F62FE", "logoUrl": "https://acme.com/logo.svg" },
  "appearance": { "baseTheme": "light", "variables": { "colorPrimary": "#0F62FE" } },
  "callbacks": { "onPublishedUrl": "https://acme.com/hooks/published" },
  "limits": { "maxPublishes": 10 },
  "form": { "showPreview": true, "prefill": { "customer.name": "Acme Corp" } }
}
Field Type Description Default
tenant object The organization the session belongs to. Upserted on every mint. Required.
tenant.externalId string Your stable id for the org (1–160 chars). Required.
tenant.displayName string Org name shown in the embed (1–200 chars). Required.
tenant.branding object Optional partial branding override scoped to this tenant.
actor object The end-user inside the iframe. Upserted under the tenant. Required.
actor.externalId string Your stable id for the user (1–160 chars). Required.
actor.displayName string User's display name (≤200 chars).
actor.email string User email (validated).
actor.avatarUrl string User avatar URL (validated).
scope object What the session can open. { "mode": "edit" }
scope.mode string edit, create, view, or fill. fill mounts the form route; the others mount the builder. edit
scope.templateExternalId string Your id for the template to load (≤200 chars). Resolved to a Craftkit template id.
scope.initialName string Initial name for new templates (create mode). Ignored when loading an existing template.
variableCatalog object An inline catalog to use for this session only (see Publish a catalog for the shape). Mutually exclusive with catalogRef.
catalogRef object Reference a published catalog by name (+ optional version). Resolves to the current version when version is omitted.
permissions object Partial override of the permission flags (publish, saveDraft, delete, rename, rollback, createCustomVariables, changePageSettings, viewVersionHistory, submitForm, saveFormDraft, shareDocument, emailDocument, viewEngagement). Omitted flags fall back to schema defaults. schema defaults
permissionsPreset string Name of a saved permission preset (≤60 chars). Accepted by the schema; reserved.
branding object Partial branding (primaryColor, logoUrl, fontUrl, locale, ui, support). locale en
appearance object Framework-agnostic styling contract (baseTheme, variables, rules, layout, stylesheetUrl, fontUrl, logoUrl). Supersedes branding when both are present. partner default theme
callbacks object onPublishedUrl / onCloseUrl — partner URLs the embed posts to.
limits object Partial override of maxPublishes (10), maxSaveDrafts (200), maxUploadsBytes (5 MiB). shown defaults
form object Form-fill claims — only meaningful when scope.mode === 'fill': prefill, showPreview (false), showDocumentAfterSubmit (true), redirectUrl.

Catalog: inline vs reference. Send either variableCatalog (a one-off inline catalog) or catalogRef (a pointer to a published catalog). If you send neither, the session has no catalog. catalogRef.name must already be published to this project — an unknown name returns 404 catalog_not_found. See Publish a catalog.

Response

{
  "session_id": "0193c2c3-1a2b-7c3d-8e4f-aabbccddeeff",
  "session_token": "eyJhbGciOiJFZERTQS...",
  "iframe_url": "https://embed.craftkit.dev/embed/builder?session_token=eyJhbGciOiJFZERTQS...",
  "expires_at": "2026-06-05T14:00:00.000Z",
  "renew_token": "ert_8sR2...Xq"
}
Field Type Description
session_id string Session UUID. Use it to revoke the session server-side.
session_token string Signed EdDSA JWT. Carried as ?session_token= in iframe_url; do not expose it to the wrong origin.
iframe_url string The URL to mount in your <iframe>. Points at the builder (or the form route in fill mode).
expires_at string ISO-8601 expiry, 4 hours from mint. Call refresh before this.
renew_token string Single-use token for POST /v1/embed/sessions/refresh. Each refresh returns a new one; the old one is invalidated.

Refresh — POST /v1/embed/sessions/refresh

Rotate a session's token before it expires. The renewToken is single-use: each call mints a fresh token, extends expires_at by 4 hours, and returns a new renew_token (the old one stops working).

curl -X POST https://api.craftkit.dev/v1/embed/sessions/refresh \
  -H "Authorization: Bearer $CRAFTKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "renewToken": "ert_8sR2...Xq" }'
Field Type Description Default
renewToken string The renew_token from the last mint or refresh (min 8 chars). Required.

The response shape is identical to the mint response (session_id, session_token, iframe_url, expires_at, renew_token).

Same project's key required. Refresh looks the session up by (partnerId, renewToken). You must call it with an API key from the same project that minted the session — a valid key from a different project will not find the session and returns 401 refresh_failed. A renew token that was already rotated, or a session that is no longer active, also returns 401 refresh_failed.

Errors

HTTP Code Meaning Fix
401 missing_authorization No Authorization: Bearer header Send the project API key
401 invalid_credentials Key not found, revoked, or embed not enabled for the project Check the key and that embed is enabled
400 invalid_json Body wasn't valid JSON Check Content-Type and JSON.stringify
422 invalid_request Body failed schema validation (mint includes issues) See the request body table above
404 catalog_not_found catalogRef.name has no current catalog in this project Publish the catalog first, or fix the name
401 refresh_failed (refresh) Renew token invalid, already rotated, session inactive, or wrong project's key Re-mint, or use the minting project's key
500 catalog_resolution_failed Inline catalog or catalogRef lookup threw server-side Retry; check the catalog payload
500 mint_failed Session minting threw (e.g. partner has no active signing key) Retry; contact support if it persists