REST API
The Formward REST API lets you read and manage your forms and their submissions programmatically. It is a JSON REST API scoped to all organizations the key's user belongs to: a key can reach every form and submission in any organization where its user is a member, not just forms they personally created. Access is governed by per-key scopes — reads need the matching :read scope (forms:read, submissions:read) and writes need a :write scope. Create, update, and delete form operations require forms:write, and managing triage (tags, notes, assignee) requires triage:write. The API requires the Professional plan or above.
Authentication
Create an API key in the dashboard under API keys. The full key is shown once at creation; only a hash is stored, so copy it immediately. Send the key on every request with either header:
Authorization: Bearer fwk_live_<your-key>
# or
X-Api-Key: fwk_live_<your-key>Requests without a valid, non-revoked key return 401. Requests whose user is below the Professional plan return 403. A key inherits its data scope from its user's current organization memberships, so treat a key as granting access to every organization that user belongs to.
Base URL
https://app.formward.eu/api/v1OpenAPI spec
A machine-readable OpenAPI 3.1 description of every endpoint, parameter, and response is served at /api/v1/openapi.json. Use it to generate clients or import the API into tools like Postman or Insomnia.
Rate limits
Each API key is limited to 120 requests per minute (fixed window). Every response carries the current limit state:
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118
X-RateLimit-Reset: 1736500860X-RateLimit-Reset is the epoch time in seconds at which the window resets. When you exceed the limit the API returns 429 with a Retry-After header (seconds to wait) and the standard error body. Limits are enforced per node; on a multi-instance deploy the effective ceiling scales with the number of instances.
Filtering
The submissions endpoint accepts the following query parameters, which can be combined:
limit- number of rows, 1 to 100 (default 50)before- cursor, an ISO timestamp; returns rows strictly older than it (usenextBeforefrom the previous page)status- one ofreceived,processed,spam,held,failedsince- an ISO timestamp; returns rows at or after it
List forms
GET /api/v1/forms returns every form across all organizations the key's user belongs to.
curl https://app.formward.eu/api/v1/forms \
-H "Authorization: Bearer fwk_live_<your-key>"{
"data": [
{
"id": "<formId>",
"name": "Contact form",
"createdAt": "2026-01-01T12:00:00.000Z",
"endpoint": "https://forms.formward.eu/f/<formId>"
}
]
}Get one form
GET /api/v1/forms/<formId> returns a single form's public info, including its submission count. A form your account cannot access returns 404.
curl https://app.formward.eu/api/v1/forms/<formId> \
-H "Authorization: Bearer fwk_live_<your-key>"{
"data": {
"id": "<formId>",
"name": "Contact form",
"createdAt": "2026-01-01T12:00:00.000Z",
"endpoint": "https://forms.formward.eu/f/<formId>",
"submissionCount": 42
}
}List a form's submissions
GET /api/v1/forms/<formId>/submissions returns the form's submissions, newest first. A form that does not belong to your account returns 404.
Pagination uses a cursor. Pass ?limit= (1 to 100, default 50) and ?before= set to the nextBefore value from the previous response to fetch older rows. You can also filter with ?status= and ?since= (see the Filtering section above).
curl "https://app.formward.eu/api/v1/forms/<formId>/submissions?limit=50" \
-H "X-Api-Key: fwk_live_<your-key>"Filtered example, processed submissions since a date:
curl "https://app.formward.eu/api/v1/forms/<formId>/submissions?status=processed&since=2026-01-01" \
-H "X-Api-Key: fwk_live_<your-key>"{
"data": [
{
"id": "<submissionId>",
"payload": { "email": "a@example.com", "message": "Hi" },
"status": "processed",
"createdAt": "2026-01-02T09:30:00.000Z",
"spamScore": 3,
"aiSummary": "New enquiry from a@example.com"
}
],
"nextBefore": "2026-01-02T09:30:00.000Z"
}Get one submission
GET /api/v1/submissions/<id> returns a single submission. A submission that does not belong to your account returns 404.
curl https://app.formward.eu/api/v1/submissions/<submissionId> \
-H "Authorization: Bearer fwk_live_<your-key>"Status codes
200- success400- invalid query parameter (limit,before,status, orsince)401- missing or invalid API key403- plan does not include the API404- resource not found or not owned by your account429- rate limit exceeded (seeRetry-After)
All errors share one JSON shape so they are easy to handle programmatically:
{ "error": { "code": "rate_limited", "message": "Rate limit exceeded. Retry after the window resets." } }