> ## 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.

# Submit a form

> POST /v1/embed/form-submit/:sessionId — validate filled form data and enqueue a render. Embed session JWT only.

<Info>**POST** `https://api.craftkit.dev/v1/embed/form-submit/:sessionId`</Info>

Validates the submitted `data` against the template version's manifest, merges JWT-claimed
prefill **under** the user-supplied data (user wins), and enqueues a render with `source = "form"`.
Returns `202` with a poll URL. Rendering is **asynchronous**.

## Authorization

Embed session JWT **only** — never a project API key.

<ParamField header="Authorization" type="string" required>
  `Bearer <session_token>` — a **fill**-mode session JWT with `permissions.submitForm = true`. The
  request origin must be allow-listed (used as the token audience), and `:sessionId` must match the
  token's session.
</ParamField>

## Path parameters

<ParamField path="sessionId" type="string" required>
  The embed session id. Must match the bearer token's session.
</ParamField>

## Body

<ParamField body="data" type="object" required>
  Variable values to render. May use flat dot-keys (`"customer.name"`) — they are expanded to
  nested objects before manifest validation. Validated against the template version's manifest.
  Reserved property names (`__proto__`, `constructor`, `prototype`) in keys are stripped.
</ParamField>

<ParamField body="datasetSelection" type="object">
  Optional string→string map persisted on the render row (for example, which source records the
  form was filled from).
</ParamField>

## Response

`202` when the render is queued.

<ResponseField name="id" type="string">Render id (UUID).</ResponseField>
<ResponseField name="status" type="string">`queued` initially.</ResponseField>
<ResponseField name="pollUrl" type="string">Embed-scoped poll URL (`/v1/embed/renders/:id`). Poll with the **session JWT**.</ResponseField>
<ResponseField name="downloadUrl" type="string | null">`null` until the render succeeds.</ResponseField>

<ResponseField name="errorMessage" type="string | null" />

<ResponseField name="createdAt" type="string" />

## Errors

| Status | code                    | Meaning                                                                 |
| ------ | ----------------------- | ----------------------------------------------------------------------- |
| 400    | `bad_request`           | Request origin is not in the allowed origins list.                      |
| 400    | `invalid_json`          | Body is not valid JSON.                                                 |
| 400    | `invalid_request`       | Body did not match `{ data, datasetSelection? }` (`issues` included).   |
| 400    | `invalid_input_data`    | `data` did not match the manifest (`issues` included).                  |
| 401    | `unauthorized`          | Missing/malformed header, or the session token failed verification.     |
| 403    | `wrong_mode`            | Session is not `scope.mode = "fill"`.                                   |
| 403    | `permission_denied`     | Session lacks the `submitForm` permission.                              |
| 404    | `session_not_found`     | `:sessionId` does not match the bearer token.                           |
| 404    | `template_not_resolved` | Session scope carries no template id, or the external id is not a UUID. |
| 404    | `template_not_found`    | No such template in this project.                                       |
| 404    | `unpublished_template`  | The template has no published version.                                  |
| 503    | `queue_unavailable`     | Render queue temporarily unreachable — retry.                           |
| 500    | `internal`              | The render could not be written or enqueued.                            |

```bash cURL theme={null}
curl -X POST https://api.craftkit.dev/v1/embed/form-submit/$SESSION_ID \
  -H "Authorization: Bearer $SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "customer.name": "Acme Corp",
      "customer.email": "hello@acme.com"
    }
  }'
```

```json 202 theme={null}
{
  "id": "0193c2c3-...",
  "status": "queued",
  "pollUrl": "https://api.craftkit.dev/v1/embed/renders/0193c2c3-...",
  "downloadUrl": null,
  "errorMessage": null,
  "createdAt": "2026-06-05T10:00:00.000Z"
}
```
