Developers
REST API
Authenticate with an API key, find the full OpenAPI reference, and learn the API's main resources, errors and conventions.
The FormWork REST API lets another system work with your forms, entries, data tables and automations: for reporting, migrations, syncing records or starting an automation from outside FormWork.
For a conversational integration, connect through the MCP server at https://app.useformwork.com/mcp. It is separate from the REST API.
Check the no-code options first. Automations, API connectors, imports and exports and embedding cover many integrations without writing a client.
Full reference
Every endpoint, with its parameters, request bodies and responses, is in the OpenAPI reference. These pages need no sign-in.
| Format | URL |
|---|---|
| Interactive reference | app.useformwork.com/api/v1/openapi |
| OpenAPI JSON | app.useformwork.com/api/v1/openapi.json |
| OpenAPI YAML | app.useformwork.com/api/v1/openapi.yaml |
The specification is the contract: requests that don’t match it are rejected before they reach FormWork.
Base URL
https://app.useformwork.com/api/v1
Requests and responses use JSON.
Authentication
Send an API key as a bearer token, and the ID of its account in X-Account-ID, on every request:
curl "https://app.useformwork.com/api/v1/projects" \
-H "Authorization: Bearer fwk_..." \
-H "X-Account-ID: YOUR_ACCOUNT_ID"
- Keys start with
fwk_. The full key is shown once, when it is created. See API keys for creating and revoking them. X-Account-IDmust be the account the key belongs to. Otherwise the request fails with403.- A key acts as the team member who created it, with their role in the account. It can’t do anything they can’t. See Team members and roles.
- A key is either full access or limited to chosen permissions and projects. Keep it on your server; never put it in browser code. Missing permissions are refused with
403andinsufficient_scope. Account administration endpoints require full access as well as the appropriate role.
Main resources
| Area | Paths | What it covers |
|---|---|---|
| Projects | /projects | Projects, and project secrets used by automations and connected apps |
| Forms | /forms | Forms, their public schema and notification settings |
| Form versions | /form_versions | Drafts and published versions: settings, pages and fields, validation, the process, and publishing |
| Entries | /entries | Listing, creating and updating entries, answers, team fields (metafields in the API), files, history and automation runs |
| Revisions | /entry_revisions | Earlier versions of entries |
| Stages | /entries/{entry_id}/stage_moves, /entries/{entry_id}/owner, /entries/{entry_id}/approvals, /my_work | Moving entries between stages, owners, approvals and each person’s work |
| Automations | /workflows | Automations in a form version, their triggers and steps, and their runs |
| Data tables | /data_tables | Tables, columns and rows |
| Views | /views | Saved entry views and filters |
| Imports and exports | /imports, /exports, /jobs | CSV imports, exports and the background jobs that run them |
| Files | /files | Uploading and downloading files |
| Templates | /templates | Reusable content templates |
| Extensions | /extensions | Installed extensions, API connectors, environments, actions and action tests |
| References | /references | Looking up the values that can be inserted into steps and templates |
| Accounts and users | /accounts, /users, /keys | Account details, team members, groups, invitations and scoped API keys |
| AI connections and audit | /ai_connections, /accounts/{account_id}/ai_connections, /accounts/{account_id}/audit_events | Personal assistant connections, account-level connection management and the admin audit log |
| Billing | /accounts/{account_id}/billing | Plan status, and links to checkout and the billing portal |
The API keeps some older names: automations are workflows and team fields are metafields.
Lists and pagination
Most list endpoints take limit and offset and return the items in data with paging details in meta:
{
"data": [],
"meta": { "limit": 20, "offset": 0, "total": 134 }
}
The default and largest limit differ between endpoints; the reference gives them. Public field-option searches page with a cursor and return a next_cursor. Entry views also support cursor pagination, including sorted views; use the reference for their response shape and pass the returned cursor to continue.
Changing drafts safely
Form versions, their fields, process and automations are changed on the draft, and go live when the draft is published. See Drafts and publishing.
Each change moves the draft on by one revision. Responses about a draft include its current revision, in the ETag header or a revision field. Send it back in If-Match on your next change: if someone else changed the draft in the meantime, nothing is saved and you get 412 with stale_revision and the current revision. Without If-Match the change is saved whatever the revision.
The draft’s changes can be read from GET /form_versions/{version_id}/changes. An isolated preview link can be requested from POST /form_versions/{version_id}/preview_link; it opens the draft without signing in for an hour, suppresses external effects like the builder preview, and its test entries are removed a day after their last change. Use the OpenAPI reference for the exact request and response shapes.
Account audit log
Account admins can read GET /accounts/{account_id}/audit_events using their session or a full-access API key. Results are newest first and support limit (default 50, maximum 200) and offset.
The log records API key creation and revocation, AI connections made, revoked or removed from an account, assistant writes and refusals, and form publications from the builder, API or assistant. It records the actor and assistant where relevant, targets and outcome rather than answer or secret values. It doesn’t cover every read or every ordinary app edit.
Errors
Errors return an HTTP status and a JSON body with a short code and a message:
{
"error": "not_found",
"message": "Entry not found"
}
| Status | Common codes | Meaning |
|---|---|---|
400 | bad_request, invalid_request | The request is missing something or has a wrong value. When the request doesn’t match the specification, the body has a validation object saying which part failed. |
401 | unauthorized, invalid_api_key, api_key_revoked | The key is missing, wrong or revoked. |
402 | billing_inactive | The account’s plan isn’t active, so this change isn’t allowed. |
403 | forbidden, insufficient_scope | The key can’t access this account, project or action, or lacks a required permission. |
404 | not_found | The item doesn’t exist or isn’t in this account. |
409 | conflict | The item isn’t in a state that allows this, for example an automation that is switched off. |
412 | stale_revision | The draft changed since the revision you sent in If-Match. |
429 | rate_limited | Too many requests. Wait and try again. |
5xx | internal_error | Something went wrong in FormWork. Retry later with backoff. |
Use the code, not the message, in your logic. Messages are written for people and may change.
Start an automation from another system
An automation with the API request trigger can be started for an existing entry:
curl -X POST "https://app.useformwork.com/api/v1/workflows/WORKFLOW_ID/runs?form_version_id=VERSION_ID" \
-H "Authorization: Bearer fwk_..." \
-H "X-Account-ID: YOUR_ACCOUNT_ID" \
-H "Content-Type: application/json" \
-d '{"entry_id": "ENTRY_ID"}'
The automation must be switched on. Quick steps run while the request waits. From the first slow step, the rest continues in the background; the response returns the run in its current state. If it reaches a Wait step before returning, the run comes back as waiting and continues later. Run once for each entry can refuse a second trigger-started run with 409. See Triggers.
To check why an entry’s automations did or didn’t run, read GET /entries/{entry_id}/automation_check. It evaluates the current setup without starting a run. See Runs and testing.
Public form endpoints
The form people fill in uses its own endpoints under /entry_sessions. They don’t use an API key:
GET /forms/{form_id}/public_schemaloads a published form before any entry exists.POST /entry_sessionscreates an entry and returns itsentry_key. This is the only time the key is returned.- Every later call for that entry sends the key in the
X-Formwork-Entry-Keyheader: saving answers, adding and removing repeating items, uploading files, button clicks, resume links and submitting.
If the form has a security check (CAPTCHA) turned on, creating an entry needs a valid CAPTCHA proof.
For a normal website, use the web component, which handles all of this for you. Use these endpoints only if you are building and maintaining your own complete form interface. Never use an entry key as account authentication, or an API key in a respondent’s browser.
Webhooks into FormWork
Built-in extensions can receive webhooks from their provider, for example Stripe at /extensions/stripe/webhooks. These endpoints check the provider’s signature instead of an API key. See Stripe.