Responses & redirects
The Formward ingestion endpoint (POST /f/:formId) returns different HTTP status codes depending on the outcome of the submission. The response format depends on whether the request was sent in AJAX mode.
Status codes
| Code | Meaning | Body (AJAX) |
|---|---|---|
| 200 | Submission accepted (AJAX mode only) | { ok: true, id: "..." } |
| 302 | Submission accepted (classic form POST) - browser is redirected | — |
| 400 | Malformed request body (a JSON body that is not an object), too many files, or a multipart upload sent to a server with uploads unavailable | { ok: false, error: "invalid request body" } { ok: false, error: "too many files" } { ok: false, error: "uploads not supported" } |
| 403 | Request origin not in the form's allowed-origins list; the Turnstile challenge failed; or file uploads are disabled / not included in the plan | { ok: false, error: "origin not allowed" } { ok: false, error: "captcha failed" } { ok: false, error: "file uploads are disabled for this form" } { ok: false, error: "file uploads require the Professional plan", upgrade: true } |
| 404 | Form ID not found, or form is inactive | { ok: false, error: "form not found" } |
| 402 | Monthly submission limit reached on the current plan | { ok: false, error: "submission limit reached", upgrade: true } |
| 413 | A file exceeds the per-file size limit, the request exceeds the total upload size limit, or the request body is too large | { ok: false, error: "file too large" } { ok: false, error: "upload too large" } { ok: false, error: "submission too large" } |
| 415 | An uploaded file's MIME type is not in the allowed list | { ok: false, error: "file type not allowed: <mime>" } |
| 429 | Per-IP + per-form rate limit exceeded (includes a Retry-After header) | { ok: false, error: "rate limit" } |
Upload-related codes (400, 413, 415) and the captcha/uploads 403 variants only occur on forms that accept files or have Turnstile enabled. See File uploads for the size and type limits that produce them. In classic (non-AJAX) mode these errors render a minimal HTML error page instead of the JSON body shown above.
On success, the AJAX body also includes a files count, e.g. { ok: true, id: "...", files: 2 } for a submission with two stored attachments.
Redirect precedence
For classic (non-AJAX) form posts, the redirect target is resolved in this order:
_redirectfield in the POST body. Must be a relative path or an absolute URL whose origin is in your form's allowed-origins list. Invalid values are silently ignored.- Formward redirect URL: the URL configured in your form settings in the dashboard.
- Default:
/thankson the ingestion server, which returns a plain JSON{ ok: true }response.
In AJAX mode the _redirect field is ignored. The client handles navigation itself after receiving the JSON response.
Honeypot submissions
Submissions where the _gotcha honeypot field is non-empty are treated as spam. To avoid giving bots a signal they can adapt to, the server responds the same way it does for an accepted submission: a 200 with a success-shaped JSON body { ok: true, files: 0 } in AJAX mode, or a 302 redirect (or the HTML thanks page) in classic mode. The outcome is indistinguishable from acceptance, so do not rely on the response alone to confirm a submission was stored. Behind the scenes the submission is recorded with status: spam, and it does not trigger a notification email or consume your monthly quota.
The same indistinguishable-success behaviour applies to senders matched by a form's block list: they receive a success response and are silently stored as spam without consuming quota.
Plan limit (402)
When your account has reached its monthly submission quota, genuine (non-spam) submissions are rejected with 402 Payment Required. The body includes upgrade: true to signal that upgrading the plan would resolve the issue. Spam and rate-limited requests are gated earlier in the pipeline and do not consume quota.