> ## Documentation Index
> Fetch the complete documentation index at: https://docs.craftkit.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Create an embed session

> POST /v1/embed/sessions — mint a signed, short-lived session your front-end mounts in an iframe.

<Info>**POST** `https://api.craftkit.dev/v1/embed/sessions`</Info>

Mint an embed session. Your backend calls this with a project API key; the response carries a
signed EdDSA session JWT, the `iframe_url` to mount, and a single-use `renew_token`. Sessions live
for **4 hours** — rotate the token with [Refresh a session](/api-reference/refresh-session) before
`expires_at`. The key's project must have embed enabled, or auth returns `invalid_credentials`.

## Authorization

<ParamField header="Authorization" type="string" required>
  `Bearer ck_live_…` — a project API key for a project with embed enabled.
</ParamField>

## Body

<ParamField body="tenant" type="object" required>
  The organization this session belongs to. Upserted on every mint.

  <Expandable title="tenant">
    <ParamField body="externalId" type="string" required>Your stable id for the org (1–160 chars).</ParamField>
    <ParamField body="displayName" type="string" required>Org name shown in the embed (1–200 chars).</ParamField>
    <ParamField body="branding" type="object">Optional partial branding override scoped to this tenant.</ParamField>
  </Expandable>
</ParamField>

<ParamField body="actor" type="object" required>
  The end-user inside the iframe. Upserted under the tenant.

  <Expandable title="actor">
    <ParamField body="externalId" type="string" required>Your stable id for the user (1–160 chars).</ParamField>
    <ParamField body="displayName" type="string">User display name (≤200 chars).</ParamField>
    <ParamField body="email" type="string">User email (validated).</ParamField>
    <ParamField body="avatarUrl" type="string">User avatar URL (validated).</ParamField>
  </Expandable>
</ParamField>

<ParamField body="scope" type="object" default="{ mode: 'edit' }">
  What the session can open.

  <Expandable title="scope">
    <ParamField body="mode" type="string" default="edit">
      `edit`, `create`, `view`, or `fill`. `fill` mounts the form route; the others mount the builder.
    </ParamField>

    <ParamField body="templateExternalId" type="string">Your id for the template to load (≤200 chars).</ParamField>
    <ParamField body="initialName" type="string">Initial name for new templates (`create` mode). Ignored when loading an existing template.</ParamField>
  </Expandable>
</ParamField>

<ParamField body="variableCatalog" type="object">
  An **inline** catalog used for this session only (same shape as
  [Create a catalog](/api-reference/create-catalog)). Mutually exclusive with `catalogRef`.
</ParamField>

<ParamField body="catalogRef" type="object">
  Reference a **published** catalog by name. Resolves to the current version when `version` is omitted.

  <Expandable title="catalogRef">
    <ParamField body="name" type="string" required>The published catalog name (1–120 chars).</ParamField>
    <ParamField body="version" type="number">Pin to a specific version. Defaults to current.</ParamField>
  </Expandable>
</ParamField>

<ParamField body="permissions" type="object">
  Partial override of permission flags (`publish`, `saveDraft`, `delete`, `rename`, `rollback`,
  `createCustomVariables`, `changePageSettings`, `viewVersionHistory`, `submitForm`, `saveFormDraft`,
  `shareDocument`, `emailDocument`, `viewEngagement`). Omitted flags use schema defaults.
</ParamField>

<ParamField body="permissionsPreset" type="string">
  Name of a saved permission preset (≤60 chars). Accepted by the schema; reserved.
</ParamField>

<ParamField body="branding" type="object">
  Partial branding (`primaryColor`, `logoUrl`, `fontUrl`, `locale`, `ui`, `support`).
</ParamField>

<ParamField body="appearance" type="object">
  Framework-agnostic styling contract (`baseTheme`, `variables`, `rules`, `layout`, `stylesheetUrl`,
  `fontUrl`, `logoUrl`). Supersedes `branding` when both are present. Falls back to the partner's
  default theme.
</ParamField>

<ParamField body="callbacks" type="object">
  `onPublishedUrl` / `onCloseUrl` — partner URLs the embed posts to.
</ParamField>

<ParamField body="limits" type="object">
  Partial override of `maxPublishes` (10), `maxSaveDrafts` (200), `maxUploadsBytes` (5 MiB).
</ParamField>

<ParamField body="form" type="object">
  Form-fill claims — only meaningful when `scope.mode === 'fill'`: `prefill`, `showPreview` (false),
  `showDocumentAfterSubmit` (true), `redirectUrl`.
</ParamField>

<Note>
  Send **either** `variableCatalog` (inline, one-off) **or** `catalogRef` (a pointer to a published
  catalog), not both. Neither is required — omit both for a session with no catalog. An unknown
  `catalogRef.name` returns `404 catalog_not_found`.
</Note>

## Response

`200` with the minted session.

<ResponseField name="session_id" type="string">Session UUID. Use it to revoke the session server-side.</ResponseField>
<ResponseField name="session_token" type="string">Signed EdDSA JWT, also carried in `iframe_url`.</ResponseField>
<ResponseField name="iframe_url" type="string">URL to mount in your `<iframe>` (builder, or form route in `fill` mode).</ResponseField>
<ResponseField name="expires_at" type="string">ISO-8601 expiry, 4 hours from mint.</ResponseField>
<ResponseField name="renew_token" type="string">Single-use token for the refresh endpoint.</ResponseField>

## Errors

| Status | code                        | Meaning                                                   |
| ------ | --------------------------- | --------------------------------------------------------- |
| 401    | `missing_authorization`     | No `Authorization: Bearer` header.                        |
| 401    | `invalid_credentials`       | Key not found, revoked, or embed not enabled.             |
| 400    | `invalid_json`              | Body is not valid JSON.                                   |
| 422    | `invalid_request`           | Body failed schema validation (`issues` included).        |
| 404    | `catalog_not_found`         | `catalogRef.name` has no current catalog in this project. |
| 500    | `catalog_resolution_failed` | Inline/ref catalog lookup threw — retry.                  |
| 500    | `mint_failed`               | Minting threw (e.g. no active signing key) — retry.       |

```bash cURL theme={null}
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 }
  }'
```

```json 200 theme={null}
{
  "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"
}
```

<Tip>
  Persist `renew_token` and `expires_at` server-side. Before expiry, call
  [`POST /v1/embed/sessions/refresh`](/api-reference/refresh-session) with the **same project's**
  API key to rotate the session.
</Tip>
