This tutorial shows how to retrieve submitted answers for a form using paginated requests.
- An API key
- A form
_idfrom Fetching forms
All examples use the base URL https://api.forms.app and the X-Api-Key header.
Submissions are returned in pages:
GET /v1/form/{id}/answer/p/{pageNumber}| Parameter | Description |
|---|---|
id | Form _id |
pageNumber | Page to retrieve, starts at 1, increment by 1 for each next page |
Because a form can have thousands of submissions, the API delivers results in pages rather than all at once.
Replace FORM_ID with your form's _id:
curl -s https://api.forms.app/v1/form/FORM_ID/answer/p/1 \
-H "X-Api-Key: YOUR_API_KEY"{
"success": true,
"data": {
"totalCount": 142,
"result": [
{
"answers": [
{
"q": "q1",
"t": "Jane Doe"
}
],
"createDate": "2024-06-15T09:30:00.000Z",
"publicId": "abc123"
}
],
"searchAfter": [1718443800000]
},
"errors": []
}| Field | Description |
|---|---|
totalCount | Total number of submissions for this form |
result | Array of answer objects on this page |
searchAfter | Cursor pointing to the last record on this page |
Follow this flow to collect every submission:
- Request page
1. - Process each item in
data.result. - Increment
pageNumberand request the next page. - Stop when you have collected
totalCountrecords, or whenresultis empty.
const formId = "FORM_ID";
const apiKey = process.env.FORMS_APP_API_KEY;
let pageNumber = 1;
let collected = 0;
let totalCount = Infinity;
while (collected < totalCount) {
const response = await fetch(
`https://api.forms.app/v1/form/${formId}/answer/p/${pageNumber}`,
{ headers: { "X-Api-Key": apiKey } }
);
const body = await response.json();
if (!body.success) {
throw new Error(body.errors[0]?.errorMessage ?? "Request failed");
}
totalCount = body.data.totalCount;
const page = body.data.result;
if (page.length === 0) break;
for (const answer of page) {
// Process each submission
console.log(answer.createDate, answer.answers);
}
collected += page.length;
pageNumber += 1;
}Rate limits
When syncing large forms, add a short delay between page requests to avoid hitting rate limits.
Each item in result represents one submission:
| Field | Description |
|---|---|
answers | Per-question replies (see below) |
userInfo | Optional respondent info (for example, full name) |
point | Total score when the form uses calculator / quiz scoring |
answerFiles | Metadata for file attachments |
fileToken | Token to download attached files |
publicId | Public-facing submission identifier |
createDate | ISO 8601 timestamp of when the answer was submitted |
Each entry in the answers array uses short field names:
| Field | Meaning |
|---|---|
q | Question ID (matches the question _id from the form) |
t | Text value |
n | Number value |
d | Date value |
b | Boolean value |
c | Choice selections (with text, value, and order) |
fn | Name fields (first, last, middle, prefix, suffix) |
a | Address fields |
ph | Phone fields |
f | Uploaded files |
ac | Acceptance (for example, terms checkbox) |
Match q values against question IDs from Fetching forms to interpret each reply.
| Status | Cause |
|---|---|
401 | Invalid or missing API key |
404 | Form does not exist or belongs to another account |
429 | Rate limit exceeded, wait and retry |
- Fetch the form structure to map question IDs to labels.
- Page through all submissions on a schedule (for example, every 15 minutes).
- Track the last synced
createDateor page to avoid reprocessing. - Store answers in your database or forward them to another system.