# Submit a form

Source: https://kirora.io/docs/api/reference/submitForm

`POST /v1/forms/{id}/submissions`

Requires an API key with the `contacts:write` scope. [About keys and scopes](https://kirora.io/docs/api/reference/authentication)

Works with integration and partner keys, not agent keys.

A repeat without a key does it again; send `Idempotency-Key` and a retry within 24 hours replays the first response. [About idempotency](https://kirora.io/docs/api/reference/idempotency)

Submits a double opt-in form for a person your own system signs up: the answers go through the form's fields as a visitor's would, the contact is recorded without consent, and Kirora emails them the form's confirmation link; consent, lists and topics follow when they confirm. Only a published, unarchived double opt-in form with a list takes it: any other answers 409 `CONFLICT` saying which. An address gets at most one confirmation per form per day. Answers are checked as the form checks them (400). Each call is a new sign-up that counts toward the form's limit, so send an `Idempotency-Key` to retry safely. A plain server sign-up with consent goes through `POST /v1/contacts/identify` with a consent `statement`. Agent keys are refused with 403.

## Path parameters

- `id` (string, required, uuid) — The form's id.

## Header parameters

- `Kirora-Workspace` (string, optional, pattern `^[A-Za-z0-9._:-]{1,128}$`) — Partner keys only: the child workspace to act in, by the external id the partner gave it. Without it, a partner key acts in its own workspace. Any other key that sends it is refused with 403, and an unknown workspace is 404.
- `Idempotency-Key` (string, optional, 8–255 characters) — Makes a retry safe: a repeat with the same key and body within 24 hours gets the first response again, with `Idempotency-Replay: true`. The same key with another request is 409 `IDEMPOTENCY_KEY_REUSED`; while the first is still running, 409 `IDEMPOTENCY_KEY_IN_USE`; after an `unsafe` first request failed or never returned, 409 `IDEMPOTENCY_KEY_OUTCOME_UNKNOWN`. Use a new key, such as a UUID, for each new request.

## Request body

- `fields` (map<string, string>, required, values: up to 1,000 characters) — Answers by field `key`. A checkbox field's ticks go in `choices`; a plain HTML form may send one tick here.
- `choices` (map<string, string[]>, optional, values: at most 50 items, default `{}`) — Each checkbox field's ticked option values, by field `key`.

## Returns

Returns `202 Accepted`. It can also return `409 Conflict`, `413 Payload Too Large` or `415 Unsupported Media Type`; see the statuses below. Otherwise returns an error; [Errors](https://kirora.io/docs/api/reference/errors) lists every code and which to retry.

Statuses:

- `202 Accepted`: Recorded; the confirmation email is on its way.
- `409 Conflict`: The request conflicts with current state — a duplicate unique value, or a resource whose status does not allow this operation.
- `413 Payload Too Large`: The body is larger than this operation accepts (`PAYLOAD_TOO_LARGE`).
- `415 Unsupported Media Type`: The body is not JSON, or not the upload the operation takes (`UNSUPPORTED_MEDIA_TYPE`).

Headers on `202 Accepted`:

- `Idempotency-Replay` (string, optional, one of `true`) — `true` when this is the stored response to an earlier request with the same key.

## Example: Sign up a person your system collected; they confirm by email

```bash
curl -X POST https://api.kirora.io/v1/forms/018f2a4e-6b1c-7d3e-9f40-37386ae05a6b/submissions \
  -H "Authorization: Bearer $KIRORA_API_KEY" \
  -H "Idempotency-Key: submitForm-0001" \
  -H "Content-Type: application/json" \
  -d '{
    "fields": {
      "email": "amaka@example.com",
      "firstName": "Amaka"
    }
  }'
```
