# API overview

Create forms, change their settings and work with submissions from your own code with the Nisuform REST API and an API key.

The Nisuform API lets your code do what you do on a form's pages in the dashboard: create and configure forms, page through submissions, mark them read, starred or spam, delete them, download their files and export them as CSV.

You don't need the API to collect submissions. Forms post to the [submit endpoint](/docs/reference/submit-endpoint), which is public and needs no key. Use the API when a script, a server or an automation needs to manage forms or read what came in.

```text
https://api.nisuform.com/v1
```

## Quick start

### Create an API key

In the dashboard, open **Account** from the menu under your picture, go to **API keys** and choose **Create key**. Copy the key. It starts with `nfa_` and is only shown once. See [API keys](/docs/api/keys).

### Check that it works

```sh
curl https://api.nisuform.com/v1/me \
  -H "Authorization: Bearer $NISUFORM_API_KEY"
```

The response shows your account and the team the key works in.

### List your forms and their submissions

```sh
curl https://api.nisuform.com/v1/forms \
  -H "Authorization: Bearer $NISUFORM_API_KEY"

curl "https://api.nisuform.com/v1/forms/FORM_ID/submissions?filter=unread" \
  -H "Authorization: Bearer $NISUFORM_API_KEY"
```

## Authentication

Send the key in the `Authorization` header of every request:

```http
Authorization: Bearer nfa_...
```

A missing, unknown or revoked key gets `401`. API keys are secrets: call the API from a server, a script or an automation tool, never from code that runs in a visitor's browser. The API doesn't accept browser requests from other websites.

## Teams

Every key belongs to one team, picked when you create it. Everything the key does happens in that team:

* `GET /v1/forms` lists that team's forms, and new forms are created there.
* Forms in your other teams answer `404`, as if they didn't exist.
* Plan limits and Pro features follow that team's plan.

Switching teams in the dashboard doesn't change which team a key works in.

## Endpoints

API keys can use these endpoints:

| Method   | Path                                                                                         | Does                                     |
| -------- | -------------------------------------------------------------------------------------------- | ---------------------------------------- |
| `GET`    | [`/v1/me`](/docs/api/account)                                                                | Get your account and the key's team      |
| `GET`    | [`/v1/forms`](/docs/api/forms#list-forms)                                                    | List forms                               |
| `POST`   | [`/v1/forms`](/docs/api/forms#create-a-form)                                                 | Create a form                            |
| `GET`    | [`/v1/forms/{id}`](/docs/api/forms#get-a-form)                                               | Get a form                               |
| `PATCH`  | [`/v1/forms/{id}`](/docs/api/forms#update-a-form)                                            | Update a form                            |
| `DELETE` | [`/v1/forms/{id}`](/docs/api/forms#delete-a-form)                                            | Delete a form and its submissions        |
| `POST`   | [`/v1/forms/{id}/duplicate`](/docs/api/forms#duplicate-a-form)                               | Copy a form                              |
| `POST`   | [`/v1/forms/{id}/rotate-key`](/docs/api/forms#rotate-the-form-key)                           | Give a form a new key                    |
| `POST`   | [`/v1/forms/{id}/test-submissions`](/docs/api/forms#send-a-test-submission)                  | Send a test submission                   |
| `GET`    | [`/v1/forms/{formId}/submissions`](/docs/api/submissions#list-submissions)                   | List submissions                         |
| `GET`    | [`/v1/forms/{formId}/submissions/{id}`](/docs/api/submissions#get-a-submission)              | Get a submission                         |
| `PATCH`  | [`/v1/forms/{formId}/submissions`](/docs/api/submissions#update-submissions)                 | Mark submissions read, starred or spam   |
| `DELETE` | [`/v1/forms/{formId}/submissions`](/docs/api/submissions#delete-submissions)                 | Delete submissions                       |
| `GET`    | [`/v1/forms/{formId}/submissions/{id}/files/{index}`](/docs/api/submissions#download-a-file) | Get a download link for an uploaded file |
| `GET`    | [`/v1/forms/{formId}/export.csv`](/docs/api/submissions#export-as-csv)                       | Export submissions as CSV                |

Everything else stays in the dashboard: teams and members, billing, integrations, analytics, your profile and API keys themselves. API keys get `403` there.

## Requests and responses

* Send request bodies as JSON with `Content-Type: application/json`. `GET` requests take query parameters.
* Responses are JSON, except the CSV export.
* IDs are UUIDs. Times are ISO 8601 strings in UTC, like `2026-09-29T10:00:00.000Z`.
* Every response has an `x-request-id` header. Include it when you contact support about a request.
* New fields can be added to responses at any time, so ignore fields your code doesn't know.

## Errors

Errors answer with a `4xx` or `5xx` status and a JSON body:

```json
{
  "defined": true,
  "code": "NOT_FOUND",
  "status": 404,
  "message": "Form not found"
}
```

Branch on `code`. `message` is written for people and can change.

| Status | Code                    | When                                                                                  |
| ------ | ----------------------- | ------------------------------------------------------------------------------------- |
| `400`  | `BAD_REQUEST`           | The input is invalid. `data.issues` lists each problem with its `path` and `message`. |
| `401`  | `UNAUTHORIZED`          | The key is missing, unknown or revoked.                                               |
| `402`  | `PAYMENT_REQUIRED`      | The change needs the Pro plan, or the team reached its form limit.                    |
| `403`  | `FORBIDDEN`             | API keys can't use this endpoint.                                                     |
| `404`  | `NOT_FOUND`             | The form or submission doesn't exist, or it is in another team.                       |
| `429`  | `TOO_MANY_REQUESTS`     | The key sent too many requests. Wait for the seconds in `Retry-After`.                |
| `500`  | `INTERNAL_SERVER_ERROR` | Something went wrong on our side. Retry later.                                        |

A validation error looks like this:

```json
{
  "defined": true,
  "code": "BAD_REQUEST",
  "status": 400,
  "message": "Input validation failed",
  "data": {
    "issues": [{ "path": ["name"], "message": "Too small: expected string to have >=1 characters" }]
  }
}
```

## Rate limits

Each key can send 120 requests a minute. Past that, requests get `429` with a `Retry-After` header until the minute is over. To pull many submissions, ask for up to 100 per page or use the [CSV export](/docs/api/submissions#export-as-csv).

Rate limits on the submit endpoint are separate. See [Limits](/docs/reference/limits).

## Get new submissions as they arrive

The API is the right tool for reading and managing submissions on demand. To react to each new submission, use a [webhook](/docs/integrations/webhooks) on Pro instead of asking the API every few seconds.
