# Forms API

List, create, update, duplicate and delete forms, rotate form keys and send test submissions with the Nisuform API.

Every request needs an [API key](/docs/api/keys) and works in the key's team.

## The form object

```json
{
  "id": "2f6b7c1e-9a4d-4c3e-8b21-7d5e6f4a3b2c",
  "name": "Website contact",
  "type": "contact",
  "emoji": "✉️",
  "key": "k3yz8q2m4n6p0r5t7v9w",
  "endpoint": "https://api.nisuform.com/s/k3yz8q2m4n6p0r5t7v9w",
  "status": "active",
  "closedReason": null,
  "notifyEnabled": true,
  "notifyEmail": null,
  "fields": [
    { "id": "name", "type": "text", "label": "Name", "required": true },
    { "id": "email", "type": "email", "label": "Email", "required": true },
    { "id": "message", "type": "textarea", "label": "Message", "required": true }
  ],
  "createdAt": "2026-09-29T10:00:00.000Z",
  "updatedAt": "2026-09-29T10:00:00.000Z"
}
```

The example is shortened. A form has these fields:

| Field                                  | Description                                                                                                                                                    |
| -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                                   | The form ID, used in API paths.                                                                                                                                |
| `name`                                 | The name shown in the dashboard and in notification emails.                                                                                                    |
| `type`                                 | `contact`, `waitlist`, `feedback`, `rsvp` or `custom`. Picks the analytics summary.                                                                            |
| `emoji`                                | The form's icon in the dashboard, or `null`.                                                                                                                   |
| `key`                                  | The public form key.                                                                                                                                           |
| `endpoint`                             | The URL your form posts to, `https://api.nisuform.com/s/{key}`.                                                                                                |
| `status`                               | `active` or `paused`.                                                                                                                                          |
| `closedReason`                         | Why the form stopped taking submissions: `manual`, `max_submissions`, `auto_close_date`, or `null` while it is open.                                           |
| `maxSubmissions`                       | Close after this many submissions, or `null`.                                                                                                                  |
| `autoCloseAt`                          | Close at this time, or `null`.                                                                                                                                 |
| `closedMessage`                        | Pro. Shown to visitors after the form closed, or `null` for the default.                                                                                       |
| `notifyEnabled`                        | Whether new submissions are emailed.                                                                                                                           |
| `notifyEmail`                          | Pro. Where notifications go, or `null` for the team owner's email.                                                                                             |
| `notifySubject`                        | Subject of notification emails. Supports [placeholders](/docs/submissions/notifications).                                                                      |
| `notifyFromName`                       | Sender name of notification emails.                                                                                                                            |
| `autoreplyEnabled`                     | Pro. Whether people who submit get an [auto-reply](/docs/submissions/auto-replies).                                                                            |
| `autoreplySubject`, `autoreplyBody`    | Pro. The auto-reply email, or `null`.                                                                                                                          |
| `completionMode`                       | What browser posts see after submitting: `default`, `message` or `redirect`. See [After submit](/docs/forms/after-submit).                                     |
| `completionTitle`, `completionMessage` | Pro. Your thank-you page text, or `null`.                                                                                                                      |
| `redirectUrl`                          | Pro. Where browser posts are sent after submitting, or `null`.                                                                                                 |
| `fields`                               | The form's fields, used to validate answers and label them. `null` accepts any fields. See [Fields and validation](/docs/forms/fields).                        |
| `design`                               | Look and copy of the styled embed from the [form builder](/docs/forms/builder): `theme`, `accent`, `title`, `description`, `submitLabel` and `successMessage`. |
| `allowedOrigins`                       | Websites allowed to submit. Empty allows every website. See [Access rules](/docs/protection/access-rules).                                                     |
| `blockedIps`                           | IP addresses whose submissions are dropped.                                                                                                                    |
| `blockedCountries`                     | Two-letter country codes whose submissions are dropped.                                                                                                        |
| `captchaEnabled`                       | Whether a [Turnstile](/docs/protection/turnstile) token is required.                                                                                           |
| `captchaProvider`                      | `turnstile` or `null`.                                                                                                                                         |
| `captchaSiteKey`                       | Your Turnstile site key, or `null`.                                                                                                                            |
| `captchaSecretKeySet`                  | Whether a Turnstile secret key is saved. The secret itself is never returned.                                                                                  |
| `dontStoreIp`                          | Whether visitors' IP addresses are left out of submissions.                                                                                                    |
| `allowUploads`                         | Pro. Whether the form accepts [file uploads](/docs/connect/file-uploads).                                                                                      |
| `uploadLimitMb`                        | Largest file size in MB.                                                                                                                                       |
| `userId`                               | Who created the form.                                                                                                                                          |
| `createdAt`, `updatedAt`               | When the form was created and last changed.                                                                                                                    |

## List forms

```http
GET /v1/forms
```

Returns the team's forms, newest first, in a shorter shape.

```json
{
  "forms": [
    {
      "id": "2f6b7c1e-9a4d-4c3e-8b21-7d5e6f4a3b2c",
      "name": "Website contact",
      "type": "contact",
      "emoji": "✉️",
      "status": "active",
      "key": "k3yz8q2m4n6p0r5t7v9w",
      "endpoint": "https://api.nisuform.com/s/k3yz8q2m4n6p0r5t7v9w",
      "createdAt": "2026-09-29T10:00:00.000Z",
      "submissionCount": 42
    }
  ]
}
```

`submissionCount` leaves out spam and test submissions.

## Create a form

```http
POST /v1/forms
```

| Body field | Required | Description                                                                                                                                |
| ---------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `name`     | Yes      | 1 to 200 characters.                                                                                                                       |
| `type`     | No       | `contact` (default), `waitlist`, `feedback`, `rsvp` or `custom`. Sets the starter fields and embed design. `custom` starts with no fields. |

```sh
curl https://api.nisuform.com/v1/forms \
  -H "Authorization: Bearer $NISUFORM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Beta waitlist","type":"waitlist"}'
```

Answers `201` with the new form. Point your form at its `endpoint`. On the Free plan, a team that already has 3 forms gets `402`.

## Get a form

```http
GET /v1/forms/{id}
```

Returns the full form object, or `404`.

## Update a form

```http
PATCH /v1/forms/{id}
```

Send only the fields you want to change. Leaving a field out keeps it as it is, and `null` clears it. Answers with the updated form.

```sh
curl -X PATCH https://api.nisuform.com/v1/forms/FORM_ID \
  -H "Authorization: Bearer $NISUFORM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status":"paused","notifySubject":"New lead: {{field.name}}"}'
```

| Body field          | Accepts                                                                                                            |
| ------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `name`              | 1 to 200 characters.                                                                                               |
| `type`              | `contact`, `waitlist`, `feedback`, `rsvp` or `custom`. Changing it keeps your fields.                              |
| `status`            | `active` or `paused`. Resuming a form clears `closedReason`.                                                       |
| `emoji`             | Up to 32 characters, or `null`.                                                                                    |
| `maxSubmissions`    | 1 to 1,000,000, or `null`.                                                                                         |
| `autoCloseAt`       | An ISO 8601 time, or `null`.                                                                                       |
| `closedMessage`     | Pro. 1 to 500 characters, or `null`.                                                                               |
| `notifyEnabled`     | `true` or `false`.                                                                                                 |
| `notifyEmail`       | Pro. An email address, or `null`.                                                                                  |
| `notifySubject`     | 1 to 500 characters.                                                                                               |
| `notifyFromName`    | 1 to 100 characters. Pro to change it from the default.                                                            |
| `autoreplyEnabled`  | `true` or `false`. Pro to turn it on.                                                                              |
| `autoreplySubject`  | Pro. 1 to 500 characters, or `null`.                                                                               |
| `autoreplyBody`     | Pro. 1 to 5,000 characters, or `null`.                                                                             |
| `completionMode`    | `default`, `message` or `redirect`.                                                                                |
| `completionTitle`   | Pro. 1 to 200 characters, or `null`.                                                                               |
| `completionMessage` | Pro. 1 to 2,000 characters, or `null`.                                                                             |
| `redirectUrl`       | Pro. An `http` or `https` URL. `null` or `""` removes it.                                                          |
| `fields`            | Up to 30 fields, or `null`. Replaces every field.                                                                  |
| `design`            | The whole design object. Replaces the current design.                                                              |
| `allowedOrigins`    | Up to 50 website addresses. Paths are dropped, so `https://example.com/contact` is saved as `https://example.com`. |
| `blockedIps`        | Up to 1,000 IP addresses.                                                                                          |
| `blockedCountries`  | Up to 300 two-letter country codes.                                                                                |
| `captchaSiteKey`    | Your Turnstile site key, or `null`.                                                                                |
| `captchaSecretKey`  | Your Turnstile secret key, or `null`. Write only.                                                                  |
| `captchaEnabled`    | `true` or `false`. Turning it on needs a site key and a secret key, saved before or in the same request.           |
| `dontStoreIp`       | `true` or `false`.                                                                                                 |
| `allowUploads`      | `true` or `false`. Pro to turn it on.                                                                              |
| `uploadLimitMb`     | 1 to 100. Pro to change it from 10.                                                                                |

A Pro setting on a Free team answers `402` and nothing is saved.

### Fields

Each field has an `id`, which is the `name` your form sends, a `type`, a `label` and `required`. Types are `text`, `email`, `textarea`, `number`, `date`, `select`, `radio`, `checkbox`, `rating`, `tel`, `url` and `file`.

```json
{
  "fields": [
    { "id": "email", "type": "email", "label": "Email", "required": true },
    { "id": "plan", "type": "select", "label": "Plan", "required": false, "options": ["Starter", "Team"] },
    { "id": "score", "type": "rating", "label": "How likely are you to recommend us?", "required": true, "max": 10 }
  ]
}
```

| Field option             | Types             | Description                                  |
| ------------------------ | ----------------- | -------------------------------------------- |
| `placeholder`            | All               | Up to 200 characters.                        |
| `helpText`               | All               | Up to 500 characters.                        |
| `minLength`, `maxLength` | `text`            | Answer length. `textarea` takes `maxLength`. |
| `pattern`                | `text`            | A regular expression the answer must match.  |
| `min`, `max`             | `number`          | Smallest and largest allowed value.          |
| `options`                | `select`, `radio` | 1 to 100 choices.                            |
| `max`                    | `rating`          | Number of stars, 3 to 10. Defaults to 5.     |

Field IDs start with a letter and use letters, digits, `-` or `_`, up to 64 characters, and must be unique. See [Fields and validation](/docs/forms/fields) for how answers are checked.

## Delete a form

```http
DELETE /v1/forms/{id}
```

Deletes the form with all its submissions and uploaded files, and answers `{ "ok": true }`. Its endpoint stops accepting submissions right away. This can't be undone.

## Duplicate a form

```http
POST /v1/forms/{id}/duplicate
```

| Body field   | Required | Description                                      |
| ------------ | -------- | ------------------------------------------------ |
| `nameSuffix` | No       | Added to the copy's name. Defaults to `" copy"`. |

Copies the form's settings, fields, design and integrations into a new form with a new key. Google Sheets isn't copied, and a copied webhook gets its own signing secret. The copy starts open, without a submission limit or close date, and without submissions. Answers `201` with the copy. It counts toward the Free plan's form limit.

## Rotate the form key

```http
POST /v1/forms/{id}/rotate-key
```

Gives the form a new key and answers with the updated form. The old endpoint stops accepting submissions right away, so update your website with the new `endpoint` first.

## Send a test submission

```http
POST /v1/forms/{id}/test-submissions
```

Stores a sample submission marked as a test and runs the form's notifications and integrations, so you can check your setup. Answers `201` with `{ "submissionId": "..." }`. Test submissions don't count toward your monthly limit.
