Skip to content
Last updated

This tutorial shows how to retrieve submitted answers for a form using paginated requests.

Prerequisites

All examples use the base URL https://api.forms.app and the X-Api-Key header.

Endpoint overview

Submissions are returned in pages:

GET /v1/form/{id}/answer/p/{pageNumber}
ParameterDescription
idForm _id
pageNumberPage 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.

Fetch the first page

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"

Response structure

{
  "success": true,
  "data": {
    "totalCount": 142,
    "result": [
      {
        "answers": [
          {
            "q": "q1",
            "t": "Jane Doe"
          }
        ],
        "createDate": "2024-06-15T09:30:00.000Z",
        "publicId": "abc123"
      }
    ],
    "searchAfter": [1718443800000]
  },
  "errors": []
}
FieldDescription
totalCountTotal number of submissions for this form
resultArray of answer objects on this page
searchAfterCursor pointing to the last record on this page

Walk through all pages

Follow this flow to collect every submission:

  1. Request page 1.
  2. Process each item in data.result.
  3. Increment pageNumber and request the next page.
  4. Stop when you have collected totalCount records, or when result is empty.

Example loop (pseudocode)

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.

Understanding answer objects

Each item in result represents one submission:

FieldDescription
answersPer-question replies (see below)
userInfoOptional respondent info (for example, full name)
pointTotal score when the form uses calculator / quiz scoring
answerFilesMetadata for file attachments
fileTokenToken to download attached files
publicIdPublic-facing submission identifier
createDateISO 8601 timestamp of when the answer was submitted

Answer field shorthand

Each entry in the answers array uses short field names:

FieldMeaning
qQuestion ID (matches the question _id from the form)
tText value
nNumber value
dDate value
bBoolean value
cChoice selections (with text, value, and order)
fnName fields (first, last, middle, prefix, suffix)
aAddress fields
phPhone fields
fUploaded files
acAcceptance (for example, terms checkbox)

Match q values against question IDs from Fetching forms to interpret each reply.

Error handling

StatusCause
401Invalid or missing API key
404Form does not exist or belongs to another account
429Rate limit exceeded, wait and retry

Typical integration pattern

  1. Fetch the form structure to map question IDs to labels.
  2. Page through all submissions on a schedule (for example, every 15 minutes).
  3. Track the last synced createDate or page to avoid reprocessing.
  4. Store answers in your database or forward them to another system.

What's next?