All docs
3 min read

Submissions

Submissions are scoped to a form. All endpoints require a bearer token with the right ability.

Base URL: https://formspring.io/api/v1

Method Path Ability
GET /forms/{form}/submissions submissions:read
POST /forms/{form}/submissions/bulk submissions:write
GET /forms/{form}/submissions/export submissions:export
GET /forms/{form}/submissions/{submission} submissions:read
PUT /forms/{form}/submissions/{submission} submissions:write
DELETE /forms/{form}/submissions/{submission} submissions:write
GET /forms/{form}/submissions/{submission}/files/{file} submissions:read

List submissions

GET /forms/{form}/submissions?folder=inbox&per_page=25

Query parameters:

Param Values Default Notes
folder inbox, spam, all inbox
per_page 1–100 25
page integer 1
category string Filter by AI category
since ISO 8601 Filter to submissions after this timestamp
until ISO 8601 Filter to submissions before this timestamp

Response 200: paginated SubmissionCollection. Each entry includes id, status, category, ai_moderation, payload, received_at, plus files if the submission included uploads.

Show a submission

GET /forms/{form}/submissions/{submission}

{submission} is the submission ID (subm_...). Returns the full SubmissionResource including raw payload, parsed fields, attached files (with signed download URLs valid for 60 seconds), and metadata.

Update a submission

PUT /forms/{form}/submissions/{submission}
Content-Type: application/json
{
  "payload": { "email": "fixed@example.com" },
  "status": "received"
}

status accepts received, spam, processed, failed. Updating status to spam moves the submission to the spam folder; received moves it back to the inbox.

payload does a shallow merge — fields you don't include are left untouched. To clear a field, set it to null.

Response 200: updated SubmissionResource.

Delete a submission

DELETE /forms/{form}/submissions/{submission}

Hard delete. Removes the submission row, attached files, and any audit trail. Cannot be undone.

Response 200: {"ok": true}

Bulk action

POST /forms/{form}/submissions/bulk
Content-Type: application/json
{
  "ids": ["subm_01H...", "subm_02H..."],
  "action": "delete"
}

action is one of delete, mark_spam, mark_not_spam. Up to 500 IDs per call.

Response 200:

{ "ok": true, "affected": 47, "skipped": [] }

skipped lists IDs the caller wasn't allowed to act on (e.g. wrong team).

Export

GET /forms/{form}/submissions/export?format=csv

format is csv or json. Returns a streaming response; for large exports the response is chunked, not paginated.

Format Content-Type
csv text/csv; charset=utf-8
json application/json

The CSV includes one column per known field plus metadata columns (id, received_at, status, category, score). Files are referenced by signed URL in a _files column.

?since= and ?until= query params apply to exports too. Without them, the export covers all submissions in the form.

Download a file

GET /forms/{form}/submissions/{submission}/files/{file}

Returns a 302 Found redirect to a signed URL on object storage. The signed URL is valid for 60 seconds. Follow the redirect to actually download.

{file} is the file ID returned in the files[] array on the submission resource.

This endpoint exists so your code never needs to know the storage backend or hold a long-lived signed URL. Each request mints a fresh one.

Common errors

Status Meaning
400 Bad query params (e.g. invalid folder)
401 Token missing or invalid
403 Token lacks required ability
404 Form, submission, or file not found
413 Bulk action exceeded 500 IDs
429 API rate limit hit

What's next