API
Your own programs read a team’s forms and responses through the public API with an API key. To receive responses at your own URL, use the Webhook step in the form’s workflow. Available on every plan.
API keys
Section titled “API keys”Under Settings → Connected apps, create a key and choose what it may do:
| Scope | Allows |
|---|---|
forms:read |
List the team’s forms and their questions |
responses:read |
Read responses, including download links for uploaded files |
hooks:manage |
Register a connection for a form, so a Send to app step can send to it (needs responses:read too). A script that only reads doesn’t need it |
The key is shown once. It belongs to the team: it keeps working when the person who created it leaves, and any editor can revoke it. A key reads, and can register where responses go; nothing about a form, a workflow or billing can be changed with one.
Send it as a bearer token:
GET https://api.nextforms.com/v1/formsAuthorization: Bearer nf_...Endpoints
Section titled “Endpoints”| Call | Returns |
|---|---|
GET /v1/me |
The team the key acts in and its scopes |
GET /v1/forms |
The team’s forms |
GET /v1/forms/{formId}/fields |
The form’s questions, with their ids, labels, short names and types |
GET /v1/forms/{formId}/responses?limit=25 |
The newest responses, up to 100 |
GET /v1/responses/{responseId}/files/{fileId} |
An uploaded file’s bytes, saved under the name it was uploaded with |
Each key (and each Zapier, Make or n8n connection) can make 300 calls a minute, and 60 of them file downloads. Past that, the API answers 429 Too Many Requests; wait a minute and try again.
A response looks like this. Answers are keyed by the question’s id, which stays the same when a label is edited. label is always the full question. A question with a short name (Builder → Short name, the column header your exports use) carries it as shortName. A question with a field key (Builder → Advanced → Field key, the same key URL prefill uses) carries it as key, so your code can find an answer by a name you chose instead of an id:
{ "id": "0199a2b1-1c3e-7d2a-9f10-6c2e5b8a1d00", "number": 12, "createdAt": "2026-09-28T10:15:00Z", "formId": "Ab12Cd34Ef56Gh78", "formTitle": "Contact form", "url": "https://app.nextforms.com/forms/Ab12Cd34Ef56Gh78/responses/0199a2b1-...", "answers": { "3f2b1c44-0000-4000-8000-000000000002": { "label": "What email address can we reach you on?", "shortName": "Email", "key": "email", "type": "email", } }, "payment": { "status": "Paid", "amountMinor": 2500, "currency": "gbp" }}value is the raw answer, display is what a person reads. type is one of a fixed set, and GET /v1/forms/{formId}/fields tells you each question’s type up front:
type |
value |
display |
Also |
|---|---|---|---|
text |
the text as typed | same | |
email |
the address | same | verified: true/false when the form verifies email |
url, phone |
as typed | same | |
choice |
option ids, comma-separated when several | option labels, comma-separated | options: the labels as a list; ids and labels come from fields |
yes_no |
true or false |
Yes / No | |
number |
the number as a string | formatted per the question (decimals, currency, %) | |
rating |
the score as a string | same | |
date |
ISO date (2026-09-28) |
formatted for the team’s locale | |
country |
ISO 3166 code (NL) |
country name | |
address |
the parts as JSON text | one line | address: { country, line1, line2, city, region, postalCode } |
file |
none (see files) |
“2 files” | files: [{ id, name, size, contentType, url }], each url works for a few minutes; the id fetches the file at any time |
calculated |
the computed number as a string | formatted per the question | |
payment |
the payment status | the amount | the top-level payment object has status, amountMinor, currency |
A question the respondent left blank is absent from answers. New types are added to this list, never renamed.
Receiving responses at your own URL
Section titled “Receiving responses at your own URL”Use the Webhook step in the form’s workflow: it takes the URL (and an optional signing secret) from a connection under Integrations, lets you send either the fields you choose under your own keys or the full response (every answer with its label and type, the files, the payment, and the run), signs each request when the connection has a secret, and places the send where you want it in the flow (after an approval, on one branch). No API key needed, and every delivery shows in the workflow’s runs.