Skip to content

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.

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/forms
Authorization: Bearer nf_...
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",
"value": "[email protected]",
"display": "[email protected]"
}
},
"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.

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.