Skip to content
Join our Slack

Submissions

A submission is one respondent’s answers to one published version of a form. The Platform API can list submissions, read one submission, or stream every matching submission as a CSV or JSONL attachment.

OperationWhat it does
GET /v1/forms/{form_public_id}/submissionsLists submissions by creation time, newest first by default.
GET /v1/forms/{form_public_id}/submissions/{submission_public_id}Reads one submission, including signed URLs for uploaded files.
GET /v1/forms/{form_public_id}/submissions/exportStreams matching submissions as a CSV or JSONL attachment.

All three operations need forms:read and cost one credit each. A Private Template never collects submissions: its list is empty, its CSV export holds only the metadata header row, its JSONL export is empty, and reading a submission ID through it returns 404 NOT_FOUND.

Every list item is the same object returned by the single-submission operation:

GET /v1/forms/Ev3ntReg01/submissions/Sb01EventReg2026Ab3cDe
{
"public_id": "Sb01EventReg2026Ab3cDe",
"form_public_id": "Ev3ntReg01",
"status": "completed",
"published_version": 3,
"created_at": "2026-10-01T09:00:00.000Z",
"updated_at": "2026-10-01T09:00:00.000Z",
"answers": {
"nNam": "Ada Lovelace",
"nEml": "[email protected]",
"nTck": {
"value": "speaker",
"label": "Speaker"
}
},
"hidden_values": {
"utm_source": "conference-newsletter"
}
}

Every field of a submission is listed in the response of Read a submission.

An unanswered question is absent from answers. Answer shapes follow the question type:

  • Text, number, and yes-or-no answers are JSON strings, numbers, or booleans.
  • A single choice is { "value": "speaker", "label": "Speaker" }; a multiple choice is an array of those objects.
  • A matrix answer is an array of [row_value, selected_option] pairs.
  • A File Upload answer is always an array of file objects, even when the respondent uploaded a single file. A file that has not finished uploading is left out of the array.

Each file object carries type: "file", the file’s public_id, name, size_bytes, mime_type, and a download_url signed for five minutes. Fetch the submission again when a URL has expired, and use public_id, not the URL, as the file’s identity.

GET /v1/forms/{form_public_id}/submissions returns completed submissions, newest first. Use order=asc for the oldest first:

Terminal window
curl -s -H "Authorization: Bearer $FOMR_TOKEN" \
"https://platform.fomr.io/v1/forms/Ev3ntReg01/submissions?limit=100"
GET /v1/forms/Ev3ntReg01/submissions?status=all&limit=2
{
"data": [
{
"public_id": "Sb01EventReg2026Ab3cDf",
"form_public_id": "Ev3ntReg01",
"status": "partial",
"published_version": 3,
"created_at": "2026-10-01T09:00:00.000Z",
"updated_at": "2026-10-01T09:00:00.000Z",
"answers": {
"nNam": "Grace Hopper"
},
"hidden_values": {
"utm_source": "partner"
}
},
{
"public_id": "Sb01EventReg2026Ab3cDe",
"form_public_id": "Ev3ntReg01",
"status": "completed",
"published_version": 3,
"created_at": "2026-10-01T09:00:00.000Z",
"updated_at": "2026-10-01T09:00:00.000Z",
"answers": {
"nNam": "Ada Lovelace",
},
"hidden_values": {
"utm_source": "conference-newsletter"
}
}
],
"next_cursor": "eyJpZCI6IlNiMDFFdmVudFJlZzIwMjZBYjNjRGUiLCJrIjoiMjAyNi0xMC0wMVQwOTowMDowMC4wMDAwMDBaIiwibyI6ImRlc2MiLCJzIjoiY3JlYXRlZF9hdCJ9"
}

The response is { data, next_cursor }. Pass next_cursor back as cursor until it is null, and keep the same order while paging. Submissions created at the same instant are neither skipped nor repeated. Conventions covers cursors in full.

Every filter is listed with the query parameters of List submissions.

since and until are inclusive. You can combine all four filters; for example, this reads completed submissions for version 3 that started during September:

Terminal window
curl -s -H "Authorization: Bearer $FOMR_TOKEN" \
"https://platform.fomr.io/v1/forms/Ev3ntReg01/submissions?published_version=3&since=2026-09-01T00%3A00%3A00.000Z&until=2026-09-30T23%3A59%3A59.999Z"

Completed submissions are available on every plan. status=partial and status=all need the Pro plan’s Partial Response Access feature, and so does reading a Partial Response by its ID. A Free organization gets 402 PRO_FEATURE_REQUIRED with feature: "partial_response_access" and path: "status".

A Partial Response is the same resource as a completed submission, with status: "partial" and only the answers saved so far.

Use the submission public ID from a list response to read one submission:

Terminal window
curl -s -H "Authorization: Bearer $FOMR_TOKEN" \
"https://platform.fomr.io/v1/forms/Ev3ntReg01/submissions/Sb01EventReg2026Ab3cDe"

The submission must belong to the form in the URL and to your organization. A missing, deleted, or foreign submission returns the same 404 NOT_FOUND.

GET /v1/forms/{form_public_id}/submissions/export streams every matching submission as an attachment. There is no job to poll: rows start arriving immediately. CSV is the default:

Terminal window
curl -sS -H "Authorization: Bearer $FOMR_TOKEN" \
-o submissions-Ev3ntReg01.csv \
"https://platform.fomr.io/v1/forms/Ev3ntReg01/submissions/export"

In CSV:

  • The first six columns are submission_public_id, form_public_id, status, published_version, created_at, and updated_at. Answer columns follow, then Hidden Values as hidden_values.{key}.
  • Answer columns are the columns of the app’s Responses table: the questions of the newest published version in form order, then questions only older versions carry, kept when at least one exported submission answered them.
  • Answers read as they do in the Responses table. A choice answer is its label, with several joined by commas. A Checkbox or Switch answer is Yes or No. Dates and times use the question’s date and time format, and a phone number is written in international format. A File Upload answer is its download URLs, one per line, signed for five minutes.
  • A Matrix question gets one column per row, headed {question label}: {row label}, or {node_public_id}.{row_value} with header=public_id. Each cell holds the label of the option chosen for that row. A row that only an older version carries gets a column when an exported submission answered it.
  • Every value is quoted. A value beginning with =, +, -, @, a tab, or a carriage return gains a leading apostrophe, so spreadsheet software treats respondent input as text.

Ask for JSON Lines with format=jsonl. Each line is one JSON object with the same shape as a submission:

Terminal window
curl -sS -H "Authorization: Bearer $FOMR_TOKEN" \
-o submissions-Ev3ntReg01.jsonl \
"https://platform.fomr.io/v1/forms/Ev3ntReg01/submissions/export?format=jsonl"

The export accepts the same status, since, until, and published_version filters as the list, with the same Pro gate on Partial Responses. Rows come newest published version first, then newest submission first within a version. The response streams until every row is sent or the client disconnects.

An organization can run two exports at a time, counted across the whole organization: every token, member, and parallel job shares the two slots. A third returns 503 SERVICE_UNAVAILABLE with retry_after_seconds and the matching Retry-After header. A slot frees as soon as an export finishes or its client disconnects.

Every query parameter is listed under Export submissions.

header=label, the default, keys CSV answer columns and JSONL answers entries by each question’s label in the newest published version, as the Responses table shows it: a piped answer appears as [Question], and a question without text uses its placeholder. With published_version, it uses that version’s labels instead:

One JSONL row with header=label
{
"public_id": "Sb01EventReg2026Ab3cDe",
"form_public_id": "Ev3ntReg01",
"status": "completed",
"published_version": 3,
"created_at": "2026-10-01T09:00:00.000Z",
"updated_at": "2026-10-01T09:00:00.000Z",
"answers": {
"Full name": "Ada Lovelace",
"Email address": "[email protected]",
"Ticket type": {
"value": "speaker",
"label": "Speaker"
}
},
"hidden_values": {
"utm_source": "conference-newsletter"
}
}

Every field of a row is listed in the response of Export submissions.

Use header=public_id for stable keys such as nNam. It is the safer choice for an automated import, because labels can change. In label mode, a question with neither text nor a placeholder uses its node public ID, and a label that is already taken gets the node public ID appended in brackets, so every heading stays unique.

hidden_values holds the values captured with this response, not the defaults currently set on the form. Keys come from the Form Blueprint’s hidden_values declarations and stay separate from question answers. The submission resource and every export row include them. See Hidden values for declaring keys and defaults.

StatusCodeWhen
400INVALID_INPUTA filter, date range, version, limit, cursor, format, or header is invalid.
402PRO_FEATURE_REQUIREDA Free organization asks for Partial Responses.
403FORBIDDENThe member’s role lacks forms:read.
404NOT_FOUNDThe form or submission is missing, deleted, foreign, or mismatched.
429API_COOLDOWN or TOO_MANY_REQUESTSThe organization lacks the credits for the call, hit its minute cap, or is in API Cooldown. See Rate limits and plans.
503SERVICE_UNAVAILABLEOnly on export: the organization already has two exports running, from any token or member. Retry after retry_after_seconds.

The full error shapes and retry rules are on Errors.

Your next form could be the best-looking thing on your site.

Free forever. Unlimited responses. No signup to start.