Responses & redirects
The Formward endpoint (POST /f/:formId) returns 200 for an accepted AJAX submission, 302 for an accepted classic form POST, and 400, 402, 403, 404, 413, 415, 422 or 429 for request errors, and 500 when processing fails on our side. 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>" } |
| 422 | Validation failed: required consent missing, a required field empty, too many fields, a field over its length limit, a field rule not met, or a file over the field's size limit | { ok: false, error: "consent_required" | "too_many_fields" | "field_too_long" | "field_invalid" | "file_too_large" | "file_type_not_allowed", field?, code? } |
| 429 | Per-IP + per-form rate limit exceeded (includes a Retry-After header) | { ok: false, error: "rate limit" } |
| 500 | Processing failed on our side. If the failure happened before the submission was stored, nothing was kept and you can retry. If the submission was stored but could not be queued, it is kept and the worker picks it up on its next sweep, so a retry creates a second submission and counts against the quota again. When in doubt, check the dashboard before re-sending; the status page shows whether a component is down | { ok: false, error: "internal error" } |
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, a short thank-you page in the visitor's language with a link back to your site (JSON{ ok: true }only when the request asks forapplication/json). Set a redirect URL in the form settings, or submit with AJAX and show your own message, to keep visitors on your own site instead.
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.