---
title: "Forms"
description: "Every operation in this group: what it accepts, what it returns and the errors it can answer with."
url: "https://openemail.uk/docs/api/reference/forms"
area: "API"
category: "Reference"
---

# Forms

Every operation in this group: what it accepts, what it returns and the errors it can answer with.

## Operations

Sign-up forms that add the people who fill them in to your audiences. A form has a draft `document` you edit and a published copy visitors see, so design changes go live only when you publish, while settings apply as soon as they are saved. Share a form by its hosted `url`, embed it with one script tag, or post to its `subscribeUrl` from your own HTML or code.

With double opt-in on, each person gets a confirmation email from the form's sending address and joins once they confirm or you approve them. Submissions keep every answer, and a person who joins becomes a contact in the workspace.

### `GET /forms`

List forms

Every sign-up form in the workspace, newest first, without the document or settings. `stats` is counted at the moment of the read.

An app a member connected lists only the forms that member made, as the console does for them.

Requires the `forms:read` scope.

- Scopes: `forms:read`.

**Query parameters**

- `limit` (`integer`, at least 1, at most 100, default `25`): Rows per page, 1 to 100.
- `cursor` (`string`): The previous page's `nextCursor`, passed back as it came. It is opaque: it holds where the last row sat in this list's order, so a row deleted or edited between pages never breaks the walk, and the next page starts at the first row that sorts after it. A value this list did not hand out is a 400 `invalid_cursor`.

**Returns**

- `200` `FormList`: A page of forms.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`forms.list()`](https://openemail.uk/docs/sdk/reference/forms#list), [`forms.listAll()`](https://openemail.uk/docs/sdk/reference/forms#listAll), [`forms.iterate()`](https://openemail.uk/docs/sdk/reference/forms#iterate); CLI [`openemail forms list`](https://openemail.uk/docs/cli/reference/forms#forms-list); MCP [`listForms`](https://openemail.uk/docs/mcp/tools/forms#listForms).

### `POST /forms`

Create a form

Makes a draft from `document`, from a `starter`, or from a one-field email form when neither is sent, and answers with the whole form. Send `publish: true` to put it live in the same call.

Every audience in `settings.audienceIds` and in an audience field has to be one the caller can reach in this workspace. `settings.senderAddress` has to be an address the caller may send as, and a key limited to some addresses may only name ones it holds there and in `settings.notifyAddresses`. A call that turns on `settings.doubleOptIn`, names a `settings.senderAddress` or changes the confirmation email also needs `emails:send`, because the form then sends mail for you.

Share it with `url`, post plain HTML forms to `subscribeUrl`, or embed it with the script `/embed/form.js` served from the web app's domain.

Requires the `forms:write` scope.

- Scopes: `forms:write`.

**Request body**

- `name` (`string`, required, 1 to 120 characters): What the workspace calls the form, trimmed, 1 to 120 characters. Visitors never see it, and it need not be unique.
- `description` (`string`, nullable, up to 500 characters): A note for the workspace, at most 500 characters. Visitors never see it.
- `starter` (`string`, one of `"blank"`, `"newsletter"`, `"waitlist"`, `"event"`, `"early-access"`, `"contact"`): Begin from a starter's fields, copy and style. Ignored when `document` is sent.
- `document` (`object`): Everything a visitor sees: `fields` in order, the `copy` around them and the `style`. Every form has exactly one `email` field, and every input field has a unique `key` that answers are posted under.
  - `fields` (`object[]`, required, 1 to 50 items)
    - `id` (`string`, required, 1 to 64 characters)
    - `type` (`string`, required, one of `"email"`, `"text"`, `"textarea"`, `"number"`, `"phone"`, `"url"`, `"date"`, `"select"`, `"radio"`, `"checkboxes"`, `"checkbox"`, `"consent"`, `"audiences"`, `"hidden"`, `"heading"`, `"paragraph"`, `"divider"`)
    - `key` (`string`, required, up to 40 characters, pattern `^[a-z][a-z0-9_]{0,39}$`)
    - `label` (`string`, required, up to 300 characters)
    - `placeholder` (`string`, required, nullable, up to 200 characters)
    - `help` (`string`, required, nullable, up to 1000 characters)
    - `required` (`boolean`, required)
    - `defaultValue` (`string`, required, nullable, up to 1000 characters)
    - `options` (`object[]`, required, up to 50 items)
      - `id` (`string`, required, 1 to 64 characters)
      - `label` (`string`, required, 1 to 300 characters)
      - `value` (`string`, required, 1 to 1000 characters)
    - `mapsTo` (`string`, required, nullable, one of `"email"`, `"firstName"`, `"lastName"`, `"name"`)
    - `width` (`string`, required, one of `"full"`, `"half"`)
    - `minLength` (`integer`, required, nullable, at least 0, at most 5000)
    - `maxLength` (`integer`, required, nullable, at least 1, at most 5000)
    - `min` (`number`, required, nullable, at least -1000000000000, at most 1000000000000)
    - `max` (`number`, required, nullable, at least -1000000000000, at most 1000000000000)
  - `copy` (`object`, required)
    - `title` (`string`, required, up to 200 characters)
    - `description` (`string`, required, nullable, up to 2000 characters)
    - `submitLabel` (`string`, required, 1 to 60 characters)
    - `successTitle` (`string`, required, up to 200 characters)
    - `successMessage` (`string`, required, up to 2000 characters)
    - `pendingTitle` (`string`, required, up to 200 characters)
    - `pendingMessage` (`string`, required, up to 2000 characters)
    - `closedTitle` (`string`, required, up to 200 characters)
    - `closedMessage` (`string`, required, up to 2000 characters)
    - `footer` (`string`, required, nullable, up to 2000 characters)
  - `style` (`object`, required)
    - `accent` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
    - `background` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
    - `surface` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
    - `text` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
    - `font` (`string`, required, one of `"system"`, `"sans"`, `"serif"`, `"mono"`)
    - `radius` (`string`, required, one of `"none"`, `"sm"`, `"md"`, `"lg"`, `"full"`)
    - `width` (`string`, required, one of `"narrow"`, `"normal"`, `"wide"`)
    - `layout` (`string`, required, one of `"card"`, `"plain"`)
    - `align` (`string`, required, one of `"left"`, `"center"`)
    - `logoUrl` (`string`, required, nullable, up to 2000 characters, format `uri`)
- `settings` (`object`): Any subset of the settings. An omitted field keeps its value.
  - `audienceIds` (`string[]`, up to 20 items)
  - `doubleOptIn` (`boolean`)
  - `senderAddress` (`string`, nullable, up to 2000 characters)
  - `confirmSubject` (`string`, 1 to 200 characters)
  - `confirmMessage` (`string`, up to 2000 characters)
  - `confirmButton` (`string`, 1 to 60 characters)
  - `successAction` (`string`, one of `"message"`, `"redirect"`)
  - `redirectUrl` (`string`, nullable, up to 2000 characters, format `uri`)
  - `notifyAddresses` (`string[]`, up to 10 items)
- `publish` (`boolean`): Publish in the same call, so the form takes sign-ups at once. Nothing is created when the form could not be published.

**Returns**

- `201` `Form`: Created.

**Errors**

- `404`: `audience_not_found` on `settings.audienceIds`.
- `422`: `invalid_parameter` for an unknown `starter` or a value of the wrong shape, `unknown_parameter` for a key the body does not take, `invalid_form` when the document or settings break a rule together, `form_sender_refused` when confirmations cannot go out from `settings.senderAddress`, `capability_unsupported` for an address outside a limited key, or `workspace_limit_reached` when the workspace holds the most forms it may.
- The errors every operation can return: `400`, `401`, `403`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`forms.create()`](https://openemail.uk/docs/sdk/reference/forms#create); CLI [`openemail forms create`](https://openemail.uk/docs/cli/reference/forms#forms-create); MCP [`createForm`](https://openemail.uk/docs/mcp/tools/forms#createForm).

### `GET /forms/starters`

List the form starters

The starting points the console offers when somebody makes a form. Not paginated and not workspace data. The `slug` is what `POST /forms` takes as `starter`.

Requires the `forms:read` scope.

- Scopes: `forms:read`.

**Returns**

- `200` `FormStarterList`: Every starter, documents left out.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`forms.listStarters()`](https://openemail.uk/docs/sdk/reference/forms#listStarters); CLI [`openemail forms list-starters`](https://openemail.uk/docs/cli/reference/forms#forms-list-starters); MCP [`listFormStarters`](https://openemail.uk/docs/mcp/tools/forms#listFormStarters).

### `GET /forms/starters/{slug}`

Retrieve a form starter

Adds the starter `document`, so a client can change it before creating a form from it.

Requires the `forms:read` scope.

- Scopes: `forms:read`.

**Path parameters**

- `slug` (`string`, required, one of `"blank"`, `"newsletter"`, `"waitlist"`, `"event"`, `"early-access"`, `"contact"`): A slug from `GET /forms/starters`.

**Returns**

- `200` `FormStarterDetail`: The starter with its document.

**Errors**

- `404`: `form_starter_not_found` when no starter has that slug.
- The errors every operation can return: `400`, `401`, `403`, `422`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`forms.getStarter()`](https://openemail.uk/docs/sdk/reference/forms#getStarter); CLI [`openemail forms get-starter`](https://openemail.uk/docs/cli/reference/forms#forms-get-starter); MCP [`listFormStarters`](https://openemail.uk/docs/mcp/tools/forms#listFormStarters).

### `GET /forms/{id}`

Retrieve a form

The whole form: the draft `document`, the `publishedDocument` visitors see, `settings`, the named `audiences` and fresh `stats`. A form in another workspace is a 404, never a 403.

Requires the `forms:read` scope.

- Scopes: `forms:read`.

**Path parameters**

- `id` (`string`, required): The form id, `frm_` plus 24 hex.

**Returns**

- `200` `Form`: The form.

**Errors**

- `404`: `form_not_found` when the id names no form you can reach.
- The errors every operation can return: `400`, `401`, `403`, `422`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`forms.get()`](https://openemail.uk/docs/sdk/reference/forms#get); CLI [`openemail forms get`](https://openemail.uk/docs/cli/reference/forms#forms-get); MCP [`getForm`](https://openemail.uk/docs/mcp/tools/forms#getForm).

### `PATCH /forms/{id}`

Update a form

A partial update. `document` replaces the draft whole and a live form keeps showing its published copy until `POST /forms/{id}/publish`. `settings` is merged field by field and takes effect at once. A call that turns on `settings.doubleOptIn`, names a `settings.senderAddress` or changes the confirmation email also needs `emails:send`, because the form then sends mail for you.

Read the form, change it and send `expectedUpdatedAt` with the `updatedAt` you read: if someone saved the form in between, your change is refused rather than written over theirs.

Requires the `forms:write` scope.

- Scopes: `forms:write`.

**Path parameters**

- `id` (`string`, required): The form id, `frm_` plus 24 hex.

**Request body**

- `name` (`string`, 1 to 120 characters): New name, trimmed, 1 to 120 characters.
- `description` (`string`, nullable, up to 500 characters): New note, at most 500 characters. Null clears it.
- `document` (`object`): Everything a visitor sees: `fields` in order, the `copy` around them and the `style`. Every form has exactly one `email` field, and every input field has a unique `key` that answers are posted under.
  - `fields` (`object[]`, required, 1 to 50 items)
    - `id` (`string`, required, 1 to 64 characters)
    - `type` (`string`, required, one of `"email"`, `"text"`, `"textarea"`, `"number"`, `"phone"`, `"url"`, `"date"`, `"select"`, `"radio"`, `"checkboxes"`, `"checkbox"`, `"consent"`, `"audiences"`, `"hidden"`, `"heading"`, `"paragraph"`, `"divider"`)
    - `key` (`string`, required, up to 40 characters, pattern `^[a-z][a-z0-9_]{0,39}$`)
    - `label` (`string`, required, up to 300 characters)
    - `placeholder` (`string`, required, nullable, up to 200 characters)
    - `help` (`string`, required, nullable, up to 1000 characters)
    - `required` (`boolean`, required)
    - `defaultValue` (`string`, required, nullable, up to 1000 characters)
    - `options` (`object[]`, required, up to 50 items)
      - `id` (`string`, required, 1 to 64 characters)
      - `label` (`string`, required, 1 to 300 characters)
      - `value` (`string`, required, 1 to 1000 characters)
    - `mapsTo` (`string`, required, nullable, one of `"email"`, `"firstName"`, `"lastName"`, `"name"`)
    - `width` (`string`, required, one of `"full"`, `"half"`)
    - `minLength` (`integer`, required, nullable, at least 0, at most 5000)
    - `maxLength` (`integer`, required, nullable, at least 1, at most 5000)
    - `min` (`number`, required, nullable, at least -1000000000000, at most 1000000000000)
    - `max` (`number`, required, nullable, at least -1000000000000, at most 1000000000000)
  - `copy` (`object`, required)
    - `title` (`string`, required, up to 200 characters)
    - `description` (`string`, required, nullable, up to 2000 characters)
    - `submitLabel` (`string`, required, 1 to 60 characters)
    - `successTitle` (`string`, required, up to 200 characters)
    - `successMessage` (`string`, required, up to 2000 characters)
    - `pendingTitle` (`string`, required, up to 200 characters)
    - `pendingMessage` (`string`, required, up to 2000 characters)
    - `closedTitle` (`string`, required, up to 200 characters)
    - `closedMessage` (`string`, required, up to 2000 characters)
    - `footer` (`string`, required, nullable, up to 2000 characters)
  - `style` (`object`, required)
    - `accent` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
    - `background` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
    - `surface` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
    - `text` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
    - `font` (`string`, required, one of `"system"`, `"sans"`, `"serif"`, `"mono"`)
    - `radius` (`string`, required, one of `"none"`, `"sm"`, `"md"`, `"lg"`, `"full"`)
    - `width` (`string`, required, one of `"narrow"`, `"normal"`, `"wide"`)
    - `layout` (`string`, required, one of `"card"`, `"plain"`)
    - `align` (`string`, required, one of `"left"`, `"center"`)
    - `logoUrl` (`string`, required, nullable, up to 2000 characters, format `uri`)
- `settings` (`object`): Any subset of the settings. An omitted field keeps its value.
  - `audienceIds` (`string[]`, up to 20 items)
  - `doubleOptIn` (`boolean`)
  - `senderAddress` (`string`, nullable, up to 2000 characters)
  - `confirmSubject` (`string`, 1 to 200 characters)
  - `confirmMessage` (`string`, up to 2000 characters)
  - `confirmButton` (`string`, 1 to 60 characters)
  - `successAction` (`string`, one of `"message"`, `"redirect"`)
  - `redirectUrl` (`string`, nullable, up to 2000 characters, format `uri`)
  - `notifyAddresses` (`string[]`, up to 10 items)
- `expectedUpdatedAt` (`string`, format `date-time`): The `updatedAt` you read. When the form was saved since, nothing is written and the answer is 409 `version_conflict`.

**Returns**

- `200` `Form`: Saved.

**Errors**

- `404`: `form_not_found`, or `audience_not_found` on `settings.audienceIds`.
- `409`: `version_conflict`: the form was saved after `expectedUpdatedAt`. Nothing was written.
- `422`: `invalid_parameter` for a value of the wrong shape, `unknown_parameter` for a key the body does not take, `invalid_form` when the document or settings break a rule together, `form_sender_refused` or `capability_unsupported`, each with `param` naming the field.
- The errors every operation can return: `400`, `401`, `403`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`forms.update()`](https://openemail.uk/docs/sdk/reference/forms#update); CLI [`openemail forms update`](https://openemail.uk/docs/cli/reference/forms#forms-update); MCP [`updateForm`](https://openemail.uk/docs/mcp/tools/forms#updateForm).

### `DELETE /forms/{id}`

Delete a form

Deletes the form and every submission it holds. Its hosted page and embed stop working at once. The people it added stay in your contacts and audiences.

There is no undo.

Requires the `forms:write` scope.

- Scopes: `forms:write`.
- Asks an OAuth access token for a verification code.

**Path parameters**

- `id` (`string`, required): The form id, `frm_` plus 24 hex.

**Returns**

- `200` `DeletedForm`: Deleted.

**Errors**

- `403`: The key lacks the scope, or may not send as that address. `step_up_required`: the call was made with an OAuth access token that has not been verified in the last 60 minutes. Ask for a code with `POST /security/step-up`, send it to `POST /security/step-up/verify`, then repeat the call. The person can also choose Allow changes for 60 minutes on the app in Account settings, Connected apps, on the OpenEmail website. An API key is never asked for a code.
- `404`: `form_not_found` when the id names no form you can reach.
- The errors every operation can return: `400`, `401`, `422`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`forms.delete()`](https://openemail.uk/docs/sdk/reference/forms#delete); CLI [`openemail forms delete`](https://openemail.uk/docs/cli/reference/forms#forms-delete); MCP [`deleteForm`](https://openemail.uk/docs/mcp/tools/forms#deleteForm).

### `POST /forms/{id}/publish`

Publish a form

Copies the draft `document` to `publishedDocument` and sets the form `live`, so the hosted page, the embed and the subscribe endpoint show and take the new version. Publishing a paused form opens it again. No body.

A double opt-in form also needs a `senderAddress` that can send confirmations, and the caller needs `emails:send`.

Requires the `forms:write` scope.

- Scopes: `forms:write`.

**Path parameters**

- `id` (`string`, required): The form id, `frm_` plus 24 hex.

**Returns**

- `200` `Form`: Published.

**Errors**

- `404`: `form_not_found` when the id names no form you can reach.
- `422`: `invalid_form` or `form_sender_refused`, with `param` naming the field, or `capability_unsupported` when the key or app is limited to some addresses and the form's `settings.senderAddress` or `settings.notifyAddresses` falls outside them.
- The errors every operation can return: `400`, `401`, `403`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`forms.publish()`](https://openemail.uk/docs/sdk/reference/forms#publish); CLI [`openemail forms publish`](https://openemail.uk/docs/cli/reference/forms#forms-publish); MCP [`publishForm`](https://openemail.uk/docs/mcp/tools/forms#publishForm).

### `POST /forms/{id}/pause`

Pause a form

Stops a published form taking sign-ups. Its page stays up and shows the closed message from its copy, and a JSON post to its subscribe endpoint answers 409 `form_closed` while a plain HTML form goes to the hosted page. Pausing a paused form changes nothing. No body.

Requires the `forms:write` scope.

- Scopes: `forms:write`.

**Path parameters**

- `id` (`string`, required): The form id, `frm_` plus 24 hex.

**Returns**

- `200` `Form`: Paused.

**Errors**

- `404`: `form_not_found` when the id names no form you can reach.
- `409`: `form_not_published`: a form that was never published cannot be paused.
- The errors every operation can return: `400`, `401`, `403`, `422`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`forms.pause()`](https://openemail.uk/docs/sdk/reference/forms#pause); CLI [`openemail forms pause`](https://openemail.uk/docs/cli/reference/forms#forms-pause); MCP [`setFormStatus`](https://openemail.uk/docs/mcp/tools/forms#setFormStatus).

### `POST /forms/{id}/resume`

Resume a form

Opens a paused form again, with the version that was published last. Resuming a live form changes nothing. No body.

Resuming a double opt-in form also needs `emails:send`.

Requires the `forms:write` scope.

- Scopes: `forms:write`.

**Path parameters**

- `id` (`string`, required): The form id, `frm_` plus 24 hex.

**Returns**

- `200` `Form`: Live.

**Errors**

- `404`: `form_not_found` when the id names no form you can reach.
- `409`: `form_not_published`: publish the form first.
- `422`: `form_sender_refused` when a double opt-in form can no longer send confirmations, `invalid_form` when the published form breaks a rule, or `capability_unsupported` when the key or app is limited to some addresses and the form's `settings.senderAddress` or `settings.notifyAddresses` falls outside them.
- The errors every operation can return: `400`, `401`, `403`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`forms.resume()`](https://openemail.uk/docs/sdk/reference/forms#resume); CLI [`openemail forms resume`](https://openemail.uk/docs/cli/reference/forms#forms-resume); MCP [`setFormStatus`](https://openemail.uk/docs/mcp/tools/forms#setFormStatus).

### `POST /forms/{id}/duplicate`

Duplicate a form

Makes a new draft with the same document and settings and "copy" after the name. Submissions and statistics are not copied. No body.

Requires the `forms:write` scope.

- Scopes: `forms:write`.

**Path parameters**

- `id` (`string`, required): The form id, `frm_` plus 24 hex.

**Returns**

- `201` `Form`: The copy.

**Errors**

- `404`: `form_not_found` when the id names no form you can reach.
- `422`: `workspace_limit_reached` when the workspace holds the most forms it may, or `capability_unsupported` when the key or app is limited to some addresses and the form's `settings.senderAddress` or `settings.notifyAddresses` falls outside them.
- The errors every operation can return: `400`, `401`, `403`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`forms.duplicate()`](https://openemail.uk/docs/sdk/reference/forms#duplicate); CLI [`openemail forms duplicate`](https://openemail.uk/docs/cli/reference/forms#forms-duplicate); MCP [`duplicateForm`](https://openemail.uk/docs/mcp/tools/forms#duplicateForm).

### `GET /forms/{id}/analytics`

Form analytics

Views, submissions and people added over a window, in buckets, with totals and the conversion rate. A view is one load of the hosted page or the embed while the form is live, and views are not deduplicated by visitor.

Requires the `forms:read` scope.

- Scopes: `forms:read`.

**Path parameters**

- `id` (`string`, required): The form id, `frm_` plus 24 hex.

**Query parameters**

- `days` (`integer`, at least 1, at most 1095, default `30`): How far back to look. The oldest bucket is a whole one and the newest is partial.
- `minutes` (`integer`, at least 1, at most 1576800): The window in minutes, which wins over `days` when both are sent.
- `grain` (`string`, one of `"minute"`, `"hour"`, `"day"`, default `"day"`): How wide one bucket is. Views are kept per hour.
- `offsetMinutes` (`integer`, at least -840, at most 840, default `0`): Minutes east of UTC to cut the buckets in, so a day breaks where the reader's day does.

**Returns**

- `200` `FormAnalytics`: The window, the totals and the series.

**Errors**

- `404`: `form_not_found` when the id names no form you can reach.
- The errors every operation can return: `400`, `401`, `403`, `422`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`forms.analytics()`](https://openemail.uk/docs/sdk/reference/forms#analytics); CLI [`openemail forms analytics`](https://openemail.uk/docs/cli/reference/forms#forms-analytics); MCP [`getFormAnalytics`](https://openemail.uk/docs/mcp/tools/forms#getFormAnalytics).

### `GET /forms/{id}/submissions`

List submissions

Newest first. Each row keeps the answers as they were sent, labels included, so it still reads right after the form changes.

Requires the `forms:read` scope.

- Scopes: `forms:read`.

**Path parameters**

- `id` (`string`, required): The form id, `frm_` plus 24 hex.

**Query parameters**

- `limit` (`integer`, at least 1, at most 100, default `25`): Rows per page, 1 to 100.
- `cursor` (`string`): A submission id of this form. Keyset, not offset: pass the previous page's `nextCursor`. One that names nothing in this list is a 400 `invalid_cursor`.
- `q` (`string`, up to 200 characters): Only submissions whose email address contains this text, ignoring case.
- `status` (`string`, one of `"pending"`, `"added"`): Only `pending` or only `added` submissions.

**Returns**

- `200` `FormSubmissionList`: A page of submissions.

**Errors**

- `400`: `invalid_cursor` when the cursor names no submission of this form.
- `404`: `form_not_found` when the id names no form you can reach.
- The errors every operation can return: `401`, `403`, `422`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`forms.listSubmissions()`](https://openemail.uk/docs/sdk/reference/forms#listSubmissions), [`forms.listAllSubmissions()`](https://openemail.uk/docs/sdk/reference/forms#listAllSubmissions), [`forms.iterateSubmissions()`](https://openemail.uk/docs/sdk/reference/forms#iterateSubmissions); CLI [`openemail forms list-submissions`](https://openemail.uk/docs/cli/reference/forms#forms-list-submissions); MCP [`listFormSubmissions`](https://openemail.uk/docs/mcp/tools/forms#listFormSubmissions).

### `POST /forms/{id}/submissions/batch-remove`

Delete submissions

Deletes up to 200 submissions of this form in one call and answers with how many went. A person a submission added stays in your contacts and audiences. A pending one can no longer be confirmed.

Requires the `forms:write` scope.

- Scopes: `forms:write`.

**Path parameters**

- `id` (`string`, required): The form id, `frm_` plus 24 hex.

**Request body**

- `ids` (`string[]`, required, 1 to 200 items): Submission ids of this form. An id that names nothing is skipped.

**Returns**

- `200` `FormSubmissionBatch`: Deleted.

**Errors**

- `404`: `form_not_found` when the id names no form you can reach.
- The errors every operation can return: `400`, `401`, `403`, `422`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`forms.deleteSubmissions()`](https://openemail.uk/docs/sdk/reference/forms#deleteSubmissions); CLI [`openemail forms delete-submissions`](https://openemail.uk/docs/cli/reference/forms#forms-delete-submissions); MCP [`removeFormSubmissions`](https://openemail.uk/docs/mcp/tools/forms#removeFormSubmissions).

### `GET /forms/{id}/submissions/{submissionId}`

Retrieve a submission

One submission with every answer.

Requires the `forms:read` scope.

- Scopes: `forms:read`.

**Path parameters**

- `id` (`string`, required): The form id, `frm_` plus 24 hex.
- `submissionId` (`string`, required): The submission id, `fsb_` plus 24 hex.

**Returns**

- `200` `FormSubmission`: The submission.

**Errors**

- `404`: `form_not_found`, or `form_submission_not_found` when the form has no such submission.
- The errors every operation can return: `400`, `401`, `403`, `422`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`forms.getSubmission()`](https://openemail.uk/docs/sdk/reference/forms#getSubmission); CLI [`openemail forms get-submission`](https://openemail.uk/docs/cli/reference/forms#forms-get-submission); MCP [`getFormSubmission`](https://openemail.uk/docs/mcp/tools/forms#getFormSubmission).

### `DELETE /forms/{id}/submissions/{submissionId}`

Delete a submission

Deletes the submission. The person it added stays in your contacts and audiences, and a pending one can no longer be confirmed.

Requires the `forms:write` scope.

- Scopes: `forms:write`.

**Path parameters**

- `id` (`string`, required): The form id, `frm_` plus 24 hex.
- `submissionId` (`string`, required): The submission id, `fsb_` plus 24 hex.

**Returns**

- `200` `DeletedFormSubmission`: Deleted.

**Errors**

- `404`: `form_not_found` or `form_submission_not_found`.
- The errors every operation can return: `400`, `401`, `403`, `422`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`forms.deleteSubmission()`](https://openemail.uk/docs/sdk/reference/forms#deleteSubmission); CLI [`openemail forms delete-submission`](https://openemail.uk/docs/cli/reference/forms#forms-delete-submission); MCP [`removeFormSubmissions`](https://openemail.uk/docs/mcp/tools/forms#removeFormSubmissions).

### `POST /forms/{id}/submissions/{submissionId}/approve`

Approve a submission

Adds the person of a pending submission to its audiences without waiting for them to confirm, for when you know them. It does not resubscribe someone who unsubscribed from an audience, as their own confirmation would. An added submission is returned as it is. No body.

Requires the `forms:write` and `contacts:write` scopes.

- Scopes: `forms:write`, `contacts:write`.

**Path parameters**

- `id` (`string`, required): The form id, `frm_` plus 24 hex.
- `submissionId` (`string`, required): The submission id, `fsb_` plus 24 hex.

**Returns**

- `200` `FormSubmission`: The submission, now `added`.

**Errors**

- `404`: `form_not_found` or `form_submission_not_found`.
- The errors every operation can return: `400`, `401`, `403`, `422`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`forms.approveSubmission()`](https://openemail.uk/docs/sdk/reference/forms#approveSubmission); CLI [`openemail forms approve-submission`](https://openemail.uk/docs/cli/reference/forms#forms-approve-submission); MCP [`approveFormSubmission`](https://openemail.uk/docs/mcp/tools/forms#approveFormSubmission).

### `POST /forms/{id}/submissions/{submissionId}/resend`

Resend a confirmation

Emails a pending submission a fresh confirmation link from the form's `senderAddress`. To protect the person, one address gets at most one confirmation per form every ten minutes and five a day across the workspace. A call that runs into those limits, or one for an added submission, sends nothing and answers `confirmationSent: false`. No body.

Requires the `forms:write` and `emails:send` scopes.

- Scopes: `forms:write`, `emails:send`.

**Path parameters**

- `id` (`string`, required): The form id, `frm_` plus 24 hex.
- `submissionId` (`string`, required): The submission id, `fsb_` plus 24 hex.

**Returns**

- `200` `object`: The submission.
  - `object` (`string`, one of `"form_submission"`)
  - `id` (`string`): The durable handle, `fsb_` plus 24 hex.
  - `formId` (`string`)
  - `email` (`string`): The address the person signed up with, lower cased.
  - `status` (`string`, one of `"pending"`, `"added"`): `added` once the person is a contact in the audiences. `pending` while a double opt-in form waits for them to confirm.
  - `expired` (`boolean`): True for a `pending` submission whose confirmation link has run out. Approve it, or send a fresh link.
  - `answers` (`FormAnswer[]`): A snapshot of every answer, kept as it was even after the form changes.
  - `audienceIds` (`string[]`)
  - `sourceUrl` (`string`, nullable): The page the form was filled in on, its origin and path only and at most 500 characters, when it was known.
  - `confirmedAt` (`string`, nullable, format `date-time`): When the person joined the audiences. Null while pending.
  - `createdAt` (`string`, format `date-time`)
  - `confirmationSent` (`boolean`): Whether an email went out on this call.

**Errors**

- `404`: `form_not_found` or `form_submission_not_found`.
- `422`: `form_sender_refused` when confirmations cannot go out from the form's address.
- The errors every operation can return: `400`, `401`, `403`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`forms.resendConfirmation()`](https://openemail.uk/docs/sdk/reference/forms#resendConfirmation); CLI [`openemail forms resend-confirmation`](https://openemail.uk/docs/cli/reference/forms#forms-resend-confirmation); MCP [`resendFormConfirmation`](https://openemail.uk/docs/mcp/tools/forms#resendFormConfirmation).

### `POST /subscribe/{formId}`

Sign up through a form

Takes no credential: this is where a published form's visitors post. A browser `<form method="post">` sends its fields form encoded and is redirected with a 303 to the form's thank-you page or to its redirect. A script that sends `Accept: application/json` or a JSON body gets JSON answers and the error codes below instead. Cross-origin requests are allowed.

On a double opt-in form the person is emailed a confirmation link and joins the audiences once they open it, so `outcome` is `pending`. Signing up again before confirming updates that pending submission rather than storing another. Otherwise they join at once and `outcome` is `added`.

A person who unsubscribed from an audience stays unsubscribed unless they confirm through a double opt-in form. Sign-ups are limited to 40 every 10 minutes from one network, and a submission that looks automated gets a normal answer and is dropped.

- Sends no credential.

**Path parameters**

- `formId` (`string`, required): The id of a published form.

**Request body**

Content type: `application/json`, `application/x-www-form-urlencoded`, `multipart/form-data`.

- `email` (`string`): The field keyed `email`, which every form has.
- `values` (`object`): The answers again, for a client that would rather nest them.
- `oe_source` (`string`): The page the form was filled in on. Defaults to the `Referer` header.

**Returns**

- `200` `FormSubscription`: Signed up, for a JSON caller.
- `303`: Signed up or refused, for a browser form: follow the `Location` header.

**Errors**

- `400`: `malformed_json` when a JSON body does not parse or is not an object.
- `404`: `form_not_found` when no published form has that id.
- `409`: `form_closed` when the form is paused.
- `422`: `invalid_form_submission` when an answer is missing or not valid. The error carries `fields`, one entry per problem with the field `key` and the reason.
- `429`: `form_rate_limited` when too many sign-ups came from the same network.
- The errors every operation can return: `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`forms.subscribe()`](https://openemail.uk/docs/sdk/reference/forms#subscribe); CLI [`openemail forms subscribe`](https://openemail.uk/docs/cli/reference/forms#forms-subscribe).

### `POST /forms/design`

Design a form from a brief

A designer builds a new sign-up form from a written brief, as Create with AI does on the Forms page of the app: it picks the fields, writes the copy and sets the look, then saves the form as a draft. Publish it with `POST /forms/{id}/publish`. It spends one AI action and can take up to half a minute.

Requires the `forms:write` scope.

- Scopes: `forms:write`.

**Request body**

- `brief` (`string`, required, 1 to 4000 characters): Everything the form must ask and say, up to 4,000 characters.
- `name` (`string`, 1 to 120 characters): A short name for the form, up to 120 characters. Left out, the designer names it.

**Returns**

- `201` `object`: The new form, a draft.
  - `object` (`string`, one of `"form"`)
  - `id` (`string`): The durable handle, `frm_` plus 24 hex.
  - `name` (`string`): What the workspace calls the form. Visitors never see it.
  - `description` (`string`, nullable)
  - `status` (`string`, one of `"draft"`, `"live"`, `"paused"`): `draft` until the first publish, then `live` while it takes sign-ups and `paused` while it does not. A form never goes back to `draft`.
  - `url` (`string`): The hosted page that shows the published form. Share it as a link, or add `?embed=1` for the frame the embed script loads.
  - `subscribeUrl` (`string`): Where a plain HTML `<form method="post">` sends its fields, and where a script posts JSON for a JSON answer.
  - `audienceIds` (`string[]`): The audiences every sign-up joins, besides the default audience that holds every contact. An audiences field adds the ones the person ticks.
  - `doubleOptIn` (`boolean`): Whether a sign-up waits for the person to confirm their address by email.
  - `hasUnpublishedChanges` (`boolean`): True when the draft `document` differs from what the live form shows. Always false before the first publish.
  - `stats` (`FormStats`)
  - `publishedAt` (`string`, nullable, format `date-time`)
  - `createdAt` (`string`, format `date-time`)
  - `updatedAt` (`string`, format `date-time`): Send it back as `expectedUpdatedAt` on `PATCH /forms/{id}` to refuse a save that would overwrite someone else's.
  - `document` (`object`): Everything a visitor sees: `fields` in order, the `copy` around them and the `style`. Every form has exactly one `email` field, and every input field has a unique `key` that answers are posted under.
    - `fields` (`object[]`, required, 1 to 50 items)
      - `id` (`string`, required, 1 to 64 characters)
      - `type` (`string`, required, one of `"email"`, `"text"`, `"textarea"`, `"number"`, `"phone"`, `"url"`, `"date"`, `"select"`, `"radio"`, `"checkboxes"`, `"checkbox"`, `"consent"`, `"audiences"`, `"hidden"`, `"heading"`, `"paragraph"`, `"divider"`)
      - `key` (`string`, required, up to 40 characters, pattern `^[a-z][a-z0-9_]{0,39}$`)
      - `label` (`string`, required, up to 300 characters)
      - `placeholder` (`string`, required, nullable, up to 200 characters)
      - `help` (`string`, required, nullable, up to 1000 characters)
      - `required` (`boolean`, required)
      - `defaultValue` (`string`, required, nullable, up to 1000 characters)
      - `options` (`object[]`, required, up to 50 items)
        - `id` (`string`, required, 1 to 64 characters)
        - `label` (`string`, required, 1 to 300 characters)
        - `value` (`string`, required, 1 to 1000 characters)
      - `mapsTo` (`string`, required, nullable, one of `"email"`, `"firstName"`, `"lastName"`, `"name"`)
      - `width` (`string`, required, one of `"full"`, `"half"`)
      - `minLength` (`integer`, required, nullable, at least 0, at most 5000)
      - `maxLength` (`integer`, required, nullable, at least 1, at most 5000)
      - `min` (`number`, required, nullable, at least -1000000000000, at most 1000000000000)
      - `max` (`number`, required, nullable, at least -1000000000000, at most 1000000000000)
    - `copy` (`object`, required)
      - `title` (`string`, required, up to 200 characters)
      - `description` (`string`, required, nullable, up to 2000 characters)
      - `submitLabel` (`string`, required, 1 to 60 characters)
      - `successTitle` (`string`, required, up to 200 characters)
      - `successMessage` (`string`, required, up to 2000 characters)
      - `pendingTitle` (`string`, required, up to 200 characters)
      - `pendingMessage` (`string`, required, up to 2000 characters)
      - `closedTitle` (`string`, required, up to 200 characters)
      - `closedMessage` (`string`, required, up to 2000 characters)
      - `footer` (`string`, required, nullable, up to 2000 characters)
    - `style` (`object`, required)
      - `accent` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
      - `background` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
      - `surface` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
      - `text` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
      - `font` (`string`, required, one of `"system"`, `"sans"`, `"serif"`, `"mono"`)
      - `radius` (`string`, required, one of `"none"`, `"sm"`, `"md"`, `"lg"`, `"full"`)
      - `width` (`string`, required, one of `"narrow"`, `"normal"`, `"wide"`)
      - `layout` (`string`, required, one of `"card"`, `"plain"`)
      - `align` (`string`, required, one of `"left"`, `"center"`)
      - `logoUrl` (`string`, required, nullable, up to 2000 characters, format `uri`)
  - `publishedDocument` (`object`, nullable): Everything a visitor sees: `fields` in order, the `copy` around them and the `style`. Every form has exactly one `email` field, and every input field has a unique `key` that answers are posted under.
    - `fields` (`object[]`, required, 1 to 50 items)
      - `id` (`string`, required, 1 to 64 characters)
      - `type` (`string`, required, one of `"email"`, `"text"`, `"textarea"`, `"number"`, `"phone"`, `"url"`, `"date"`, `"select"`, `"radio"`, `"checkboxes"`, `"checkbox"`, `"consent"`, `"audiences"`, `"hidden"`, `"heading"`, `"paragraph"`, `"divider"`)
      - `key` (`string`, required, up to 40 characters, pattern `^[a-z][a-z0-9_]{0,39}$`)
      - `label` (`string`, required, up to 300 characters)
      - `placeholder` (`string`, required, nullable, up to 200 characters)
      - `help` (`string`, required, nullable, up to 1000 characters)
      - `required` (`boolean`, required)
      - `defaultValue` (`string`, required, nullable, up to 1000 characters)
      - `options` (`object[]`, required, up to 50 items)
        - `id` (`string`, required, 1 to 64 characters)
        - `label` (`string`, required, 1 to 300 characters)
        - `value` (`string`, required, 1 to 1000 characters)
      - `mapsTo` (`string`, required, nullable, one of `"email"`, `"firstName"`, `"lastName"`, `"name"`)
      - `width` (`string`, required, one of `"full"`, `"half"`)
      - `minLength` (`integer`, required, nullable, at least 0, at most 5000)
      - `maxLength` (`integer`, required, nullable, at least 1, at most 5000)
      - `min` (`number`, required, nullable, at least -1000000000000, at most 1000000000000)
      - `max` (`number`, required, nullable, at least -1000000000000, at most 1000000000000)
    - `copy` (`object`, required)
      - `title` (`string`, required, up to 200 characters)
      - `description` (`string`, required, nullable, up to 2000 characters)
      - `submitLabel` (`string`, required, 1 to 60 characters)
      - `successTitle` (`string`, required, up to 200 characters)
      - `successMessage` (`string`, required, up to 2000 characters)
      - `pendingTitle` (`string`, required, up to 200 characters)
      - `pendingMessage` (`string`, required, up to 2000 characters)
      - `closedTitle` (`string`, required, up to 200 characters)
      - `closedMessage` (`string`, required, up to 2000 characters)
      - `footer` (`string`, required, nullable, up to 2000 characters)
    - `style` (`object`, required)
      - `accent` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
      - `background` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
      - `surface` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
      - `text` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
      - `font` (`string`, required, one of `"system"`, `"sans"`, `"serif"`, `"mono"`)
      - `radius` (`string`, required, one of `"none"`, `"sm"`, `"md"`, `"lg"`, `"full"`)
      - `width` (`string`, required, one of `"narrow"`, `"normal"`, `"wide"`)
      - `layout` (`string`, required, one of `"card"`, `"plain"`)
      - `align` (`string`, required, one of `"left"`, `"center"`)
      - `logoUrl` (`string`, required, nullable, up to 2000 characters, format `uri`)
  - `settings` (`object`): Where sign-ups go and what happens after one.
    - `audienceIds` (`string[]`, required, up to 20 items)
    - `doubleOptIn` (`boolean`, required)
    - `senderAddress` (`string`, required, nullable, up to 2000 characters)
    - `confirmSubject` (`string`, required, 1 to 200 characters)
    - `confirmMessage` (`string`, required, up to 2000 characters)
    - `confirmButton` (`string`, required, 1 to 60 characters)
    - `successAction` (`string`, required, one of `"message"`, `"redirect"`)
    - `redirectUrl` (`string`, required, nullable, up to 2000 characters, format `uri`)
    - `notifyAddresses` (`string[]`, required, up to 10 items)
  - `audiences` (`FormAudience[]`): The audiences in `audienceIds` with their names.
  - `senderIssue` (`string`, nullable, one of `"missing"`, `"not_sendable"`, `"not_allowed"`): Why confirmation emails cannot go out right now, for a form with `doubleOptIn` on. `missing` means no `senderAddress` is set, `not_sendable` that the address cannot send, `not_allowed` that the form's maker may not send as it. Null when they can go out, and always null with `doubleOptIn` off.
  - `senderProblem` (`string`, nullable): The same problem in a sentence, or null.
  - `design` (`object`)
    - `notes` (`string[]`): What the designer adjusted or left out while making the design valid.

**Errors**

- `409`: `ai_not_configured`: this server has no model to design with. `version_conflict`: the form was saved since `expectedUpdatedAt`, or while the designer worked, and nothing was written.
- `422`: `invalid_form` when the designer could not make a valid form, so nothing was created or changed. `workspace_limit_reached` when the workspace already holds as many forms as it may. `invalid_parameter` for a body that does not validate.
- `429`: `ai_quota_exceeded`: the workspace has used its AI actions for today.
- The errors every operation can return: `400`, `401`, `403`, `404`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`forms.design()`](https://openemail.uk/docs/sdk/reference/forms#design); CLI [`openemail forms design`](https://openemail.uk/docs/cli/reference/forms#forms-design); MCP [`designForm`](https://openemail.uk/docs/mcp/tools/forms#designForm).

### `POST /forms/{id}/redesign`

Change a form’s design from instructions

A designer applies written instructions to the draft of the form and leaves the rest alone, as Ask AI does in the form builder of the app: add, remove or reorder fields, rewrite or translate the copy, change colours or fonts. The change is saved to the draft, and a live form keeps showing its published version until `POST /forms/{id}/publish`. It spends one AI action and can take up to half a minute.

Requires the `forms:write` scope.

- Scopes: `forms:write`.

**Path parameters**

- `id` (`string`, required): Form id such as `frm_8d2f6a1c9b3e47d0a5f1c2e9`.

**Request body**

- `instructions` (`string`, required, 1 to 4000 characters): What to change, with every detail, up to 4,000 characters.
- `expectedUpdatedAt` (`string`, format `date-time`): The `updatedAt` you read, as it came. When the form was saved since, nothing is written and the answer is 409 `version_conflict`.

**Returns**

- `200` `object`: The form, with the change in its draft.
  - `object` (`string`, one of `"form"`)
  - `id` (`string`): The durable handle, `frm_` plus 24 hex.
  - `name` (`string`): What the workspace calls the form. Visitors never see it.
  - `description` (`string`, nullable)
  - `status` (`string`, one of `"draft"`, `"live"`, `"paused"`): `draft` until the first publish, then `live` while it takes sign-ups and `paused` while it does not. A form never goes back to `draft`.
  - `url` (`string`): The hosted page that shows the published form. Share it as a link, or add `?embed=1` for the frame the embed script loads.
  - `subscribeUrl` (`string`): Where a plain HTML `<form method="post">` sends its fields, and where a script posts JSON for a JSON answer.
  - `audienceIds` (`string[]`): The audiences every sign-up joins, besides the default audience that holds every contact. An audiences field adds the ones the person ticks.
  - `doubleOptIn` (`boolean`): Whether a sign-up waits for the person to confirm their address by email.
  - `hasUnpublishedChanges` (`boolean`): True when the draft `document` differs from what the live form shows. Always false before the first publish.
  - `stats` (`FormStats`)
  - `publishedAt` (`string`, nullable, format `date-time`)
  - `createdAt` (`string`, format `date-time`)
  - `updatedAt` (`string`, format `date-time`): Send it back as `expectedUpdatedAt` on `PATCH /forms/{id}` to refuse a save that would overwrite someone else's.
  - `document` (`object`): Everything a visitor sees: `fields` in order, the `copy` around them and the `style`. Every form has exactly one `email` field, and every input field has a unique `key` that answers are posted under.
    - `fields` (`object[]`, required, 1 to 50 items)
      - `id` (`string`, required, 1 to 64 characters)
      - `type` (`string`, required, one of `"email"`, `"text"`, `"textarea"`, `"number"`, `"phone"`, `"url"`, `"date"`, `"select"`, `"radio"`, `"checkboxes"`, `"checkbox"`, `"consent"`, `"audiences"`, `"hidden"`, `"heading"`, `"paragraph"`, `"divider"`)
      - `key` (`string`, required, up to 40 characters, pattern `^[a-z][a-z0-9_]{0,39}$`)
      - `label` (`string`, required, up to 300 characters)
      - `placeholder` (`string`, required, nullable, up to 200 characters)
      - `help` (`string`, required, nullable, up to 1000 characters)
      - `required` (`boolean`, required)
      - `defaultValue` (`string`, required, nullable, up to 1000 characters)
      - `options` (`object[]`, required, up to 50 items)
        - `id` (`string`, required, 1 to 64 characters)
        - `label` (`string`, required, 1 to 300 characters)
        - `value` (`string`, required, 1 to 1000 characters)
      - `mapsTo` (`string`, required, nullable, one of `"email"`, `"firstName"`, `"lastName"`, `"name"`)
      - `width` (`string`, required, one of `"full"`, `"half"`)
      - `minLength` (`integer`, required, nullable, at least 0, at most 5000)
      - `maxLength` (`integer`, required, nullable, at least 1, at most 5000)
      - `min` (`number`, required, nullable, at least -1000000000000, at most 1000000000000)
      - `max` (`number`, required, nullable, at least -1000000000000, at most 1000000000000)
    - `copy` (`object`, required)
      - `title` (`string`, required, up to 200 characters)
      - `description` (`string`, required, nullable, up to 2000 characters)
      - `submitLabel` (`string`, required, 1 to 60 characters)
      - `successTitle` (`string`, required, up to 200 characters)
      - `successMessage` (`string`, required, up to 2000 characters)
      - `pendingTitle` (`string`, required, up to 200 characters)
      - `pendingMessage` (`string`, required, up to 2000 characters)
      - `closedTitle` (`string`, required, up to 200 characters)
      - `closedMessage` (`string`, required, up to 2000 characters)
      - `footer` (`string`, required, nullable, up to 2000 characters)
    - `style` (`object`, required)
      - `accent` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
      - `background` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
      - `surface` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
      - `text` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
      - `font` (`string`, required, one of `"system"`, `"sans"`, `"serif"`, `"mono"`)
      - `radius` (`string`, required, one of `"none"`, `"sm"`, `"md"`, `"lg"`, `"full"`)
      - `width` (`string`, required, one of `"narrow"`, `"normal"`, `"wide"`)
      - `layout` (`string`, required, one of `"card"`, `"plain"`)
      - `align` (`string`, required, one of `"left"`, `"center"`)
      - `logoUrl` (`string`, required, nullable, up to 2000 characters, format `uri`)
  - `publishedDocument` (`object`, nullable): Everything a visitor sees: `fields` in order, the `copy` around them and the `style`. Every form has exactly one `email` field, and every input field has a unique `key` that answers are posted under.
    - `fields` (`object[]`, required, 1 to 50 items)
      - `id` (`string`, required, 1 to 64 characters)
      - `type` (`string`, required, one of `"email"`, `"text"`, `"textarea"`, `"number"`, `"phone"`, `"url"`, `"date"`, `"select"`, `"radio"`, `"checkboxes"`, `"checkbox"`, `"consent"`, `"audiences"`, `"hidden"`, `"heading"`, `"paragraph"`, `"divider"`)
      - `key` (`string`, required, up to 40 characters, pattern `^[a-z][a-z0-9_]{0,39}$`)
      - `label` (`string`, required, up to 300 characters)
      - `placeholder` (`string`, required, nullable, up to 200 characters)
      - `help` (`string`, required, nullable, up to 1000 characters)
      - `required` (`boolean`, required)
      - `defaultValue` (`string`, required, nullable, up to 1000 characters)
      - `options` (`object[]`, required, up to 50 items)
        - `id` (`string`, required, 1 to 64 characters)
        - `label` (`string`, required, 1 to 300 characters)
        - `value` (`string`, required, 1 to 1000 characters)
      - `mapsTo` (`string`, required, nullable, one of `"email"`, `"firstName"`, `"lastName"`, `"name"`)
      - `width` (`string`, required, one of `"full"`, `"half"`)
      - `minLength` (`integer`, required, nullable, at least 0, at most 5000)
      - `maxLength` (`integer`, required, nullable, at least 1, at most 5000)
      - `min` (`number`, required, nullable, at least -1000000000000, at most 1000000000000)
      - `max` (`number`, required, nullable, at least -1000000000000, at most 1000000000000)
    - `copy` (`object`, required)
      - `title` (`string`, required, up to 200 characters)
      - `description` (`string`, required, nullable, up to 2000 characters)
      - `submitLabel` (`string`, required, 1 to 60 characters)
      - `successTitle` (`string`, required, up to 200 characters)
      - `successMessage` (`string`, required, up to 2000 characters)
      - `pendingTitle` (`string`, required, up to 200 characters)
      - `pendingMessage` (`string`, required, up to 2000 characters)
      - `closedTitle` (`string`, required, up to 200 characters)
      - `closedMessage` (`string`, required, up to 2000 characters)
      - `footer` (`string`, required, nullable, up to 2000 characters)
    - `style` (`object`, required)
      - `accent` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
      - `background` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
      - `surface` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
      - `text` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
      - `font` (`string`, required, one of `"system"`, `"sans"`, `"serif"`, `"mono"`)
      - `radius` (`string`, required, one of `"none"`, `"sm"`, `"md"`, `"lg"`, `"full"`)
      - `width` (`string`, required, one of `"narrow"`, `"normal"`, `"wide"`)
      - `layout` (`string`, required, one of `"card"`, `"plain"`)
      - `align` (`string`, required, one of `"left"`, `"center"`)
      - `logoUrl` (`string`, required, nullable, up to 2000 characters, format `uri`)
  - `settings` (`object`): Where sign-ups go and what happens after one.
    - `audienceIds` (`string[]`, required, up to 20 items)
    - `doubleOptIn` (`boolean`, required)
    - `senderAddress` (`string`, required, nullable, up to 2000 characters)
    - `confirmSubject` (`string`, required, 1 to 200 characters)
    - `confirmMessage` (`string`, required, up to 2000 characters)
    - `confirmButton` (`string`, required, 1 to 60 characters)
    - `successAction` (`string`, required, one of `"message"`, `"redirect"`)
    - `redirectUrl` (`string`, required, nullable, up to 2000 characters, format `uri`)
    - `notifyAddresses` (`string[]`, required, up to 10 items)
  - `audiences` (`FormAudience[]`): The audiences in `audienceIds` with their names.
  - `senderIssue` (`string`, nullable, one of `"missing"`, `"not_sendable"`, `"not_allowed"`): Why confirmation emails cannot go out right now, for a form with `doubleOptIn` on. `missing` means no `senderAddress` is set, `not_sendable` that the address cannot send, `not_allowed` that the form's maker may not send as it. Null when they can go out, and always null with `doubleOptIn` off.
  - `senderProblem` (`string`, nullable): The same problem in a sentence, or null.
  - `design` (`object`)
    - `notes` (`string[]`): What the designer adjusted or left out while making the design valid.

**Errors**

- `409`: `ai_not_configured`: this server has no model to design with. `version_conflict`: the form was saved since `expectedUpdatedAt`, or while the designer worked, and nothing was written.
- `422`: `invalid_form` when the designer could not make a valid form, so nothing was created or changed. `workspace_limit_reached` when the workspace already holds as many forms as it may. `invalid_parameter` for a body that does not validate.
- `429`: `ai_quota_exceeded`: the workspace has used its AI actions for today.
- The errors every operation can return: `400`, `401`, `403`, `404`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`forms.redesign()`](https://openemail.uk/docs/sdk/reference/forms#redesign); CLI [`openemail forms redesign`](https://openemail.uk/docs/cli/reference/forms#forms-redesign); MCP [`editFormDesign`](https://openemail.uk/docs/mcp/tools/forms#editFormDesign).

### Objects

#### `DeletedForm`

`object`

- `object` (`string`, one of `"form"`)
- `id` (`string`)
- `deleted` (`boolean`, one of `true`)

#### `DeletedFormSubmission`

`object`

- `object` (`string`, one of `"form_submission"`)
- `id` (`string`)
- `formId` (`string`)
- `deleted` (`boolean`, one of `true`)

#### `Form`

`object`

- `object` (`string`, one of `"form"`)
- `id` (`string`): The durable handle, `frm_` plus 24 hex.
- `name` (`string`): What the workspace calls the form. Visitors never see it.
- `description` (`string`, nullable)
- `status` (`string`, one of `"draft"`, `"live"`, `"paused"`): `draft` until the first publish, then `live` while it takes sign-ups and `paused` while it does not. A form never goes back to `draft`.
- `url` (`string`): The hosted page that shows the published form. Share it as a link, or add `?embed=1` for the frame the embed script loads.
- `subscribeUrl` (`string`): Where a plain HTML `<form method="post">` sends its fields, and where a script posts JSON for a JSON answer.
- `audienceIds` (`string[]`): The audiences every sign-up joins, besides the default audience that holds every contact. An audiences field adds the ones the person ticks.
- `doubleOptIn` (`boolean`): Whether a sign-up waits for the person to confirm their address by email.
- `hasUnpublishedChanges` (`boolean`): True when the draft `document` differs from what the live form shows. Always false before the first publish.
- `stats` (`FormStats`)
- `publishedAt` (`string`, nullable, format `date-time`)
- `createdAt` (`string`, format `date-time`)
- `updatedAt` (`string`, format `date-time`): Send it back as `expectedUpdatedAt` on `PATCH /forms/{id}` to refuse a save that would overwrite someone else's.
- `document` (`object`): Everything a visitor sees: `fields` in order, the `copy` around them and the `style`. Every form has exactly one `email` field, and every input field has a unique `key` that answers are posted under.
  - `fields` (`object[]`, required, 1 to 50 items)
    - `id` (`string`, required, 1 to 64 characters)
    - `type` (`string`, required, one of `"email"`, `"text"`, `"textarea"`, `"number"`, `"phone"`, `"url"`, `"date"`, `"select"`, `"radio"`, `"checkboxes"`, `"checkbox"`, `"consent"`, `"audiences"`, `"hidden"`, `"heading"`, `"paragraph"`, `"divider"`)
    - `key` (`string`, required, up to 40 characters, pattern `^[a-z][a-z0-9_]{0,39}$`)
    - `label` (`string`, required, up to 300 characters)
    - `placeholder` (`string`, required, nullable, up to 200 characters)
    - `help` (`string`, required, nullable, up to 1000 characters)
    - `required` (`boolean`, required)
    - `defaultValue` (`string`, required, nullable, up to 1000 characters)
    - `options` (`object[]`, required, up to 50 items)
      - `id` (`string`, required, 1 to 64 characters)
      - `label` (`string`, required, 1 to 300 characters)
      - `value` (`string`, required, 1 to 1000 characters)
    - `mapsTo` (`string`, required, nullable, one of `"email"`, `"firstName"`, `"lastName"`, `"name"`)
    - `width` (`string`, required, one of `"full"`, `"half"`)
    - `minLength` (`integer`, required, nullable, at least 0, at most 5000)
    - `maxLength` (`integer`, required, nullable, at least 1, at most 5000)
    - `min` (`number`, required, nullable, at least -1000000000000, at most 1000000000000)
    - `max` (`number`, required, nullable, at least -1000000000000, at most 1000000000000)
  - `copy` (`object`, required)
    - `title` (`string`, required, up to 200 characters)
    - `description` (`string`, required, nullable, up to 2000 characters)
    - `submitLabel` (`string`, required, 1 to 60 characters)
    - `successTitle` (`string`, required, up to 200 characters)
    - `successMessage` (`string`, required, up to 2000 characters)
    - `pendingTitle` (`string`, required, up to 200 characters)
    - `pendingMessage` (`string`, required, up to 2000 characters)
    - `closedTitle` (`string`, required, up to 200 characters)
    - `closedMessage` (`string`, required, up to 2000 characters)
    - `footer` (`string`, required, nullable, up to 2000 characters)
  - `style` (`object`, required)
    - `accent` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
    - `background` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
    - `surface` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
    - `text` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
    - `font` (`string`, required, one of `"system"`, `"sans"`, `"serif"`, `"mono"`)
    - `radius` (`string`, required, one of `"none"`, `"sm"`, `"md"`, `"lg"`, `"full"`)
    - `width` (`string`, required, one of `"narrow"`, `"normal"`, `"wide"`)
    - `layout` (`string`, required, one of `"card"`, `"plain"`)
    - `align` (`string`, required, one of `"left"`, `"center"`)
    - `logoUrl` (`string`, required, nullable, up to 2000 characters, format `uri`)
- `publishedDocument` (`object`, nullable): Everything a visitor sees: `fields` in order, the `copy` around them and the `style`. Every form has exactly one `email` field, and every input field has a unique `key` that answers are posted under.
  - `fields` (`object[]`, required, 1 to 50 items)
    - `id` (`string`, required, 1 to 64 characters)
    - `type` (`string`, required, one of `"email"`, `"text"`, `"textarea"`, `"number"`, `"phone"`, `"url"`, `"date"`, `"select"`, `"radio"`, `"checkboxes"`, `"checkbox"`, `"consent"`, `"audiences"`, `"hidden"`, `"heading"`, `"paragraph"`, `"divider"`)
    - `key` (`string`, required, up to 40 characters, pattern `^[a-z][a-z0-9_]{0,39}$`)
    - `label` (`string`, required, up to 300 characters)
    - `placeholder` (`string`, required, nullable, up to 200 characters)
    - `help` (`string`, required, nullable, up to 1000 characters)
    - `required` (`boolean`, required)
    - `defaultValue` (`string`, required, nullable, up to 1000 characters)
    - `options` (`object[]`, required, up to 50 items)
      - `id` (`string`, required, 1 to 64 characters)
      - `label` (`string`, required, 1 to 300 characters)
      - `value` (`string`, required, 1 to 1000 characters)
    - `mapsTo` (`string`, required, nullable, one of `"email"`, `"firstName"`, `"lastName"`, `"name"`)
    - `width` (`string`, required, one of `"full"`, `"half"`)
    - `minLength` (`integer`, required, nullable, at least 0, at most 5000)
    - `maxLength` (`integer`, required, nullable, at least 1, at most 5000)
    - `min` (`number`, required, nullable, at least -1000000000000, at most 1000000000000)
    - `max` (`number`, required, nullable, at least -1000000000000, at most 1000000000000)
  - `copy` (`object`, required)
    - `title` (`string`, required, up to 200 characters)
    - `description` (`string`, required, nullable, up to 2000 characters)
    - `submitLabel` (`string`, required, 1 to 60 characters)
    - `successTitle` (`string`, required, up to 200 characters)
    - `successMessage` (`string`, required, up to 2000 characters)
    - `pendingTitle` (`string`, required, up to 200 characters)
    - `pendingMessage` (`string`, required, up to 2000 characters)
    - `closedTitle` (`string`, required, up to 200 characters)
    - `closedMessage` (`string`, required, up to 2000 characters)
    - `footer` (`string`, required, nullable, up to 2000 characters)
  - `style` (`object`, required)
    - `accent` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
    - `background` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
    - `surface` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
    - `text` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
    - `font` (`string`, required, one of `"system"`, `"sans"`, `"serif"`, `"mono"`)
    - `radius` (`string`, required, one of `"none"`, `"sm"`, `"md"`, `"lg"`, `"full"`)
    - `width` (`string`, required, one of `"narrow"`, `"normal"`, `"wide"`)
    - `layout` (`string`, required, one of `"card"`, `"plain"`)
    - `align` (`string`, required, one of `"left"`, `"center"`)
    - `logoUrl` (`string`, required, nullable, up to 2000 characters, format `uri`)
- `settings` (`object`): Where sign-ups go and what happens after one.
  - `audienceIds` (`string[]`, required, up to 20 items)
  - `doubleOptIn` (`boolean`, required)
  - `senderAddress` (`string`, required, nullable, up to 2000 characters)
  - `confirmSubject` (`string`, required, 1 to 200 characters)
  - `confirmMessage` (`string`, required, up to 2000 characters)
  - `confirmButton` (`string`, required, 1 to 60 characters)
  - `successAction` (`string`, required, one of `"message"`, `"redirect"`)
  - `redirectUrl` (`string`, required, nullable, up to 2000 characters, format `uri`)
  - `notifyAddresses` (`string[]`, required, up to 10 items)
- `audiences` (`FormAudience[]`): The audiences in `audienceIds` with their names.
- `senderIssue` (`string`, nullable, one of `"missing"`, `"not_sendable"`, `"not_allowed"`): Why confirmation emails cannot go out right now, for a form with `doubleOptIn` on. `missing` means no `senderAddress` is set, `not_sendable` that the address cannot send, `not_allowed` that the form's maker may not send as it. Null when they can go out, and always null with `doubleOptIn` off.
- `senderProblem` (`string`, nullable): The same problem in a sentence, or null.

#### `FormAnalytics`

`object`

- `object` (`string`, one of `"form_analytics"`)
- `formId` (`string`)
- `since` (`string`, format `date-time`)
- `until` (`string`, format `date-time`)
- `grain` (`string`, one of `"minute"`, `"hour"`, `"day"`)
- `offsetMinutes` (`integer`)
- `totals` (`object`)
  - `views` (`integer`): Times the hosted page or the embed was opened while the form was live.
  - `submissions` (`integer`): Every submission stored, waiting or added.
  - `added` (`integer`): Submissions whose person is in the audiences.
  - `pending` (`integer`): Submissions still waiting for the person to confirm their address.
  - `lastSubmittedAt` (`string`, nullable, format `date-time`)
  - `conversion` (`number`, nullable): Submissions divided by views over the window, at most 1. Null with no views.
- `series` (`FormAnalyticsPoint[]`): Sparse: a bucket with nothing in it is left out.

#### `FormAnalyticsPoint`

`object`

- `bucket` (`string`): `YYYY-MM-DD` for a day, `YYYY-MM-DDTHH` for an hour, `YYYY-MM-DDTHH:MM` for a minute.
- `views` (`integer`)
- `submissions` (`integer`)
- `added` (`integer`): People who joined the audiences in the bucket, dated when they confirmed.

#### `FormAnswer`

`object`

- `fieldId` (`string`)
- `key` (`string`): The field key the value was posted under.
- `label` (`string`): The field label when the person signed up.
- `type` (`string`, one of `"email"`, `"text"`, `"textarea"`, `"number"`, `"phone"`, `"url"`, `"date"`, `"select"`, `"radio"`, `"checkboxes"`, `"checkbox"`, `"consent"`, `"audiences"`, `"hidden"`, `"heading"`, `"paragraph"`, `"divider"`)
- `value` (`string | string[] | boolean`): Text for most fields, a list for multiple choice, true or false for a checkbox.
- `display` (`string`): The value as a person reads it, option labels included.

#### `FormAudience`

`object`

- `id` (`string`)
- `name` (`string`)
- `builtin` (`boolean`): True for the default audience every contact joins.

#### `FormDocument`

`object`

Everything a visitor sees: `fields` in order, the `copy` around them and the `style`. Every form has exactly one `email` field, and every input field has a unique `key` that answers are posted under.

- `fields` (`object[]`, required, 1 to 50 items)
  - `id` (`string`, required, 1 to 64 characters)
  - `type` (`string`, required, one of `"email"`, `"text"`, `"textarea"`, `"number"`, `"phone"`, `"url"`, `"date"`, `"select"`, `"radio"`, `"checkboxes"`, `"checkbox"`, `"consent"`, `"audiences"`, `"hidden"`, `"heading"`, `"paragraph"`, `"divider"`)
  - `key` (`string`, required, up to 40 characters, pattern `^[a-z][a-z0-9_]{0,39}$`)
  - `label` (`string`, required, up to 300 characters)
  - `placeholder` (`string`, required, nullable, up to 200 characters)
  - `help` (`string`, required, nullable, up to 1000 characters)
  - `required` (`boolean`, required)
  - `defaultValue` (`string`, required, nullable, up to 1000 characters)
  - `options` (`object[]`, required, up to 50 items)
    - `id` (`string`, required, 1 to 64 characters)
    - `label` (`string`, required, 1 to 300 characters)
    - `value` (`string`, required, 1 to 1000 characters)
  - `mapsTo` (`string`, required, nullable, one of `"email"`, `"firstName"`, `"lastName"`, `"name"`)
  - `width` (`string`, required, one of `"full"`, `"half"`)
  - `minLength` (`integer`, required, nullable, at least 0, at most 5000)
  - `maxLength` (`integer`, required, nullable, at least 1, at most 5000)
  - `min` (`number`, required, nullable, at least -1000000000000, at most 1000000000000)
  - `max` (`number`, required, nullable, at least -1000000000000, at most 1000000000000)
- `copy` (`object`, required)
  - `title` (`string`, required, up to 200 characters)
  - `description` (`string`, required, nullable, up to 2000 characters)
  - `submitLabel` (`string`, required, 1 to 60 characters)
  - `successTitle` (`string`, required, up to 200 characters)
  - `successMessage` (`string`, required, up to 2000 characters)
  - `pendingTitle` (`string`, required, up to 200 characters)
  - `pendingMessage` (`string`, required, up to 2000 characters)
  - `closedTitle` (`string`, required, up to 200 characters)
  - `closedMessage` (`string`, required, up to 2000 characters)
  - `footer` (`string`, required, nullable, up to 2000 characters)
- `style` (`object`, required)
  - `accent` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
  - `background` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
  - `surface` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
  - `text` (`string`, required, pattern `^#[0-9a-fA-F]{6}$`)
  - `font` (`string`, required, one of `"system"`, `"sans"`, `"serif"`, `"mono"`)
  - `radius` (`string`, required, one of `"none"`, `"sm"`, `"md"`, `"lg"`, `"full"`)
  - `width` (`string`, required, one of `"narrow"`, `"normal"`, `"wide"`)
  - `layout` (`string`, required, one of `"card"`, `"plain"`)
  - `align` (`string`, required, one of `"left"`, `"center"`)
  - `logoUrl` (`string`, required, nullable, up to 2000 characters, format `uri`)

#### `FormList`

`object`

- `object` (`string`, one of `"list"`)
- `data` (`FormSummary[]`)
- `hasMore` (`boolean`): True when another page follows. Pass `nextCursor` back as `cursor` to read it.
- `nextCursor` (`string`, nullable): An opaque cursor for the next page, or null on the last page. Pass it back unchanged.

#### `FormStarter`

`object`

- `object` (`string`, one of `"form_starter"`)
- `slug` (`string`, one of `"blank"`, `"newsletter"`, `"waitlist"`, `"event"`, `"early-access"`, `"contact"`)
- `name` (`string`)
- `description` (`string`)

#### `FormStarterDetail`

`object`

- `object` (`string`, one of `"form_starter"`)
- `slug` (`string`, one of `"blank"`, `"newsletter"`, `"waitlist"`, `"event"`, `"early-access"`, `"contact"`)
- `name` (`string`)
- `description` (`string`)
- `document` (`FormDocument`)

#### `FormStarterList`

`object`

- `object` (`string`, one of `"list"`)
- `data` (`FormStarter[]`)

#### `FormStats`

`object`

- `views` (`integer`): Times the hosted page or the embed was opened while the form was live.
- `submissions` (`integer`): Every submission stored, waiting or added.
- `added` (`integer`): Submissions whose person is in the audiences.
- `pending` (`integer`): Submissions still waiting for the person to confirm their address.
- `lastSubmittedAt` (`string`, nullable, format `date-time`)

#### `FormSubmission`

`object`

- `object` (`string`, one of `"form_submission"`)
- `id` (`string`): The durable handle, `fsb_` plus 24 hex.
- `formId` (`string`)
- `email` (`string`): The address the person signed up with, lower cased.
- `status` (`string`, one of `"pending"`, `"added"`): `added` once the person is a contact in the audiences. `pending` while a double opt-in form waits for them to confirm.
- `expired` (`boolean`): True for a `pending` submission whose confirmation link has run out. Approve it, or send a fresh link.
- `answers` (`FormAnswer[]`): A snapshot of every answer, kept as it was even after the form changes.
- `audienceIds` (`string[]`)
- `sourceUrl` (`string`, nullable): The page the form was filled in on, its origin and path only and at most 500 characters, when it was known.
- `confirmedAt` (`string`, nullable, format `date-time`): When the person joined the audiences. Null while pending.
- `createdAt` (`string`, format `date-time`)

#### `FormSubmissionBatch`

`object`

- `object` (`string`, one of `"form_submission_batch"`)
- `formId` (`string`)
- `deleted` (`integer`): How many submissions were deleted.

#### `FormSubmissionList`

`object`

- `object` (`string`, one of `"list"`)
- `data` (`FormSubmission[]`)
- `hasMore` (`boolean`)
- `nextCursor` (`string`, nullable)
- `formId` (`string`)

#### `FormSubscription`

`object`

- `object` (`string`, one of `"form_subscription"`)
- `formId` (`string`)
- `outcome` (`string`, one of `"added"`, `"pending"`): `added` when the person is in the audiences now. `pending` on a double opt-in form: they join once they confirm by email, and that email goes out after this answer unless this form emailed the address in the last ten minutes or the address has had five from this workspace today.
- `redirectUrl` (`string`, nullable): Where the form sends people after a sign-up, when it is set to redirect.

#### `FormSummary`

`object`

- `object` (`string`, one of `"form"`)
- `id` (`string`): The durable handle, `frm_` plus 24 hex.
- `name` (`string`): What the workspace calls the form. Visitors never see it.
- `description` (`string`, nullable)
- `status` (`string`, one of `"draft"`, `"live"`, `"paused"`): `draft` until the first publish, then `live` while it takes sign-ups and `paused` while it does not. A form never goes back to `draft`.
- `url` (`string`): The hosted page that shows the published form. Share it as a link, or add `?embed=1` for the frame the embed script loads.
- `subscribeUrl` (`string`): Where a plain HTML `<form method="post">` sends its fields, and where a script posts JSON for a JSON answer.
- `audienceIds` (`string[]`): The audiences every sign-up joins, besides the default audience that holds every contact. An audiences field adds the ones the person ticks.
- `doubleOptIn` (`boolean`): Whether a sign-up waits for the person to confirm their address by email.
- `hasUnpublishedChanges` (`boolean`): True when the draft `document` differs from what the live form shows. Always false before the first publish.
- `stats` (`FormStats`)
- `publishedAt` (`string`, nullable, format `date-time`)
- `createdAt` (`string`, format `date-time`)
- `updatedAt` (`string`, format `date-time`): Send it back as `expectedUpdatedAt` on `PATCH /forms/{id}` to refuse a save that would overwrite someone else's.
