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

CodeMeaningBody (AJAX)
200Submission accepted (AJAX mode only){ ok: true, id: "..." }
302Submission accepted (classic form POST) - browser is redirected
400Malformed 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" }
403Request 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 }
404Form ID not found, or form is inactive{ ok: false, error: "form not found" }
402Monthly submission limit reached on the current plan{ ok: false, error: "submission limit reached", upgrade: true }
413A 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" }
415An uploaded file's MIME type is not in the allowed list{ ok: false, error: "file type not allowed: <mime>" }
429Per-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:

  1. _redirect field 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.
  2. Formward redirect URL: the URL configured in your form settings in the dashboard.
  3. Default: /thanks on 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.

Responses and redirects, endpoint status codes | Formward Docs