API
The same API the dashboard runs on. Read submissions into your own systems, create forms from a script, or wire up a CI check.
API keys
Section titled “API keys”Create a key at Settings, API keys (organization owners and admins). Choose its permissions (Read only, Full access, or custom per area), which workspaces it reaches (all, or a list), and when it expires. The secret starts with fw_live_ and is shown once; what you see afterwards is its name and prefix. Revoke a key from the same page and it stops working at once.
Every call a key makes is recorded in the workspace’s audit log with the key’s name.
Base URL and authentication
Section titled “Base URL and authentication”All paths below are relative to https://api.formwire.com/v1. Send the key as a bearer token on every request. Request and response bodies are JSON.
curl https://api.formwire.com/v1/forms \ -H "Authorization: Bearer fw_live_…"{ "data": [ { "object": "form", "id": "2a9d1f3e-5b6c-4d7e-8f90-a1b2c3d4e5f6", "workspace_id": "5d0c8e1f-…", "name": "Contact", "status": "active", "access_key": "a3f1c2d4-…", "website_url": "https://example.com", "version": 3, "settings": { "…": "…" }, "recipients": [ "…" ], "unread_count": 2, "created_at": "2026-10-01T09:12:44.000Z" } ], "has_more": false, "next_cursor": null}A key that reaches one workspace needs nothing more. A key that reaches several names the workspace on each request with an Fw-Workspace header; leave it out and the error’s details lists the workspaces the key can reach.
curl https://api.formwire.com/v1/forms \ -H "Authorization: Bearer fw_live_…" \ -H "Fw-Workspace: 5d0c8e1f-…"A read-only key in the address
Section titled “A read-only key in the address”For a link that should work anywhere, such as a spreadsheet import or a browser tab, a read-only key can go in the address as ?api_key= on GET requests instead of the header. Add &workspace= when the key reaches more than one workspace. Addresses end up in browser history and logs, so prefer the header from code.
curl "https://api.formwire.com/v1/forms/2a9d1f3e-…/submissions?api_key=fw_live_…"List submissions
Section titled “List submissions”Submissions are listed per form, one folder at a time, newest first, with every field value as stored.
curl "https://api.formwire.com/v1/forms/2a9d1f3e-5b6c-4d7e-8f90-a1b2c3d4e5f6/submissions?folder=inbox&limit=50" \ -H "Authorization: Bearer fw_live_…"{ "data": [ { "object": "submission", "id": "7c1e9b2a-4f3d-4e8a-9b1c-2d5f6a7b8c9d", "form_id": "2a9d1f3e-5b6c-4d7e-8f90-a1b2c3d4e5f6", "source": "form", "folder": "inbox", "spam_reason": null, "fields": { "name": "Ada Lovelace", "email": "ada@example.com", "message": "Hello" }, "submitter_email": "ada@example.com", "submitter_name": "Ada Lovelace", "subject": "New submission to Contact", "page_url": "https://example.com/contact", "ip_address": "203.0.113.7", "user_agent": "Mozilla/5.0 …", "attachments": [], "read_at": null, "starred_at": null, "created_at": "2026-10-14T13:53:19.000Z" } ], "has_more": true, "next_cursor": "7c1e9b2a-4f3d-4e8a-9b1c-2d5f6a7b8c9d"}Filters
Section titled “Filters”| Parameter | Meaning |
|---|---|
folder |
inbox (default), spam or archive. |
q |
Full-text search over field values, up to 200 characters. |
submitter_email |
Only submissions from this address. |
since |
Only submissions after this ISO 8601 time. |
unread |
true for unread only. |
starred |
true for starred only. |
deleted |
true lists the form’s trash instead. |
preview_chars |
Shorten each field value to about this many characters (20 to 2000). |
limit |
1 to 250 per page; 50 by default. |
cursor |
The next_cursor from the previous page. |
curl "https://api.formwire.com/v1/forms/2a9d1f3e-…/submissions?q=invoice&since=2026-10-01T00:00:00Z&unread=true" \ -H "Authorization: Bearer fw_live_…"GET /forms/{form_id}/submissions/count takes the same filters and returns how many match.
Pagination
Section titled “Pagination”Every list returns data, has_more and next_cursor. While has_more is true, send next_cursor back as cursor to get the next page. IDs are time-ordered, so a page stays stable while new submissions arrive. Lists that are always returned whole, such as a form’s webhooks, come back with has_more: false.
curl "https://api.formwire.com/v1/forms/2a9d1f3e-…/submissions?cursor=7c1e9b2a-4f3d-4e8a-9b1c-2d5f6a7b8c9d" \ -H "Authorization: Bearer fw_live_…"Read a submission
Section titled “Read a submission”curl https://api.formwire.com/v1/submissions/7c1e9b2a-4f3d-4e8a-9b1c-2d5f6a7b8c9d \ -H "Authorization: Bearer fw_live_…"The response is one submission object as above. Uploaded files are listed in attachments, each with a signed download_url that works for an hour; request the submission again for a fresh link. Reading a submission doesn’t mark it read. To mark it read, star it or move it to another folder:
curl -X PATCH https://api.formwire.com/v1/submissions/7c1e9b2a-4f3d-4e8a-9b1c-2d5f6a7b8c9d \ -H "Authorization: Bearer fw_live_…" \ -H "Content-Type: application/json" \ -d '{ "read": true, "starred": true, "folder": "archive" }'Export
Section titled “Export”The same filters as the list, as a CSV file with a column per field, newest first, up to 50,000 rows.
curl "https://api.formwire.com/v1/forms/2a9d1f3e-…/submissions/export?folder=inbox" \ -H "Authorization: Bearer fw_live_…" -o contact.csvCreate a form
Section titled “Create a form”A form needs a name. Recipients are optional: recipient_ids are the workspace’s linked emails (see GET /linked_emails); pending ones start receiving once confirmed. The response includes the access key, and GET /forms/{form_id}/snippet returns ready-to-paste code with it filled in.
curl -X POST https://api.formwire.com/v1/forms \ -H "Authorization: Bearer fw_live_…" \ -H "Content-Type: application/json" \ -d '{ "name": "Newsletter signup", "website_url": "https://example.com" }'Change a form
Section titled “Change a form”Send the version you read with If-Match. If someone changed the form since, nothing changes and you get 412 precondition_failed; read it again and retry.
curl -X PATCH https://api.formwire.com/v1/forms/2a9d1f3e-… \ -H "Authorization: Bearer fw_live_…" \ -H "Content-Type: application/json" \ -H 'If-Match: "3"' \ -d '{ "status": "paused" }'recipient_ids, cc_recipient_ids and bcc_recipient_ids each replace one list of the form’s notification recipients (To, CC and BCC) and leave the others as they are. An address can be in only one list, and CC and BCC are only sent while the form has a confirmed To recipient. settings.retention_days sets how many days submissions are kept, up to 365. An API key can lengthen it; shortening it deletes older submissions, so that’s done in the dashboard.
Endpoints
Section titled “Endpoints”The most used ones. The full list, with every parameter and response, is the OpenAPI document at api.formwire.com/v1/openapi.json.
| Method | Path | What it does |
|---|---|---|
GET |
/workspace |
The current workspace: plan, region and this month’s usage. |
GET |
/workspace/usage |
This month’s usage against the plan’s limits. |
GET |
/workspaces |
The workspaces the key reaches. |
GET |
/forms |
List forms. ?deleted=true lists the trash. |
POST |
/forms |
Create a form. |
GET |
/forms/{form_id} |
One form, with settings, recipients, observed fields and version. |
PATCH |
/forms/{form_id} |
Change a form. Send its version in If-Match. |
DELETE |
/forms/{form_id} |
Move a form to the trash. |
POST |
/forms/{form_id}/restore |
Restore it. |
POST |
/forms/{form_id}/access_key |
Rotate the access key; the old one works for seven more days. |
GET |
/forms/{form_id}/snippet |
Embed code (?framework=html, javascript, react, astro, vue or svelte). |
GET |
/forms/{form_id}/fields |
The fields defined on the form’s Fields tab. |
POST |
/forms/{form_id}/test_submissions |
Send a sample submission that emails the recipients and calls the webhooks. |
GET |
/forms/{form_id}/origins |
Sites that have posted to the form, with accepted and blocked counts. |
GET |
/forms/{form_id}/submissions |
List a form’s submissions (filters above). |
GET |
/forms/{form_id}/submissions/count |
How many submissions match the filters. |
GET |
/forms/{form_id}/submissions/export |
CSV of the same. |
GET |
/submissions/recent |
The newest submissions across the workspace. |
GET |
/submissions/{submission_id} |
One submission with full fields and attachments. |
PATCH |
/submissions/{submission_id} |
Mark read or unread, star or unstar, move to a folder. |
DELETE |
/submissions/{submission_id} |
Move to the trash. |
POST |
/submissions/{submission_id}/restore |
Restore it. |
POST |
/submissions/bulk |
One action on up to 100 submissions. |
GET |
/submissions/{submission_id}/deliveries |
Email and webhook attempts for a submission. |
POST |
/deliveries/{delivery_id}/retry |
Send a delivery again. |
GET |
/forms/{form_id}/webhooks |
The form’s webhooks. |
POST |
/forms/{form_id}/webhooks |
Add a webhook (unconfirmed until confirmed in the dashboard). |
PATCH |
/webhooks/{webhook_id} |
Switch a webhook off or on, or change its URL. |
DELETE |
/webhooks/{webhook_id} |
Remove a webhook. |
POST |
/webhooks/{webhook_id}/tests |
Send a test delivery to it. |
GET |
/linked_emails |
Addresses the workspace can send to and whether each is confirmed. |
POST |
/linked_emails |
Add one; it receives mail once its owner clicks the confirmation link. |
GET |
/api_keys |
The organization’s keys (names and prefixes, never the secret). |
GET |
/audit_events |
The organization’s audit log. |
Some things stay in the dashboard on purpose: creating API keys, confirming a webhook added by a key, and inviting people. See the MCP page for why.
Errors
Section titled “Errors”Errors use one envelope. Branch on error.code; message is safe to show a person. Codes you’ll meet: unauthenticated (401, bad or missing key), forbidden (403, the key lacks the permission), not_found (404, including IDs from another workspace), precondition_failed (412, the version in If-Match is out of date), invalid_request (400, with field details) and rate_limited (429).
{ "error": { "code": "not_found", "message": "Not found.", "details": null }}Rate limits and retries
Section titled “Rate limits and retries”1200 requests a minute per key. Over that, requests get 429 rate_limited; wait and retry. Writes that could create something twice accept an Idempotency-Key header: a retry with the same key returns the first result instead of repeating the write.