Submission Responses and Error Codes
Maintained by the SplitForms team · Updated
Send Accept: application/json when calling SplitForms with fetch. Check both the HTTP status and success value, then use code to identify a documented failure. A 429 rate_limited response needs a pause; a 429 quota_reached response needs a plan or allowance check. A 200 response alone does not prove inbox delivery.
Request a JSON response
Native HTML posts can navigate to a thank-you page or return a readable error page. JavaScript integrations should request JSON explicitly. The usual successful response contains success: true and message: "Submission received". File accounting, a note or a validated redirect can be included when applicable.
Some internal failures use a general message without a code. Handle that case rather than assuming code is always present. Preserve the visitor’s entries after a failure so they can correct a field or try again.
const response = await fetch("https://splitforms.com/api/submit", {
method: "POST",
headers: { Accept: "application/json" },
body: new FormData(form),
});
const result = await response.json();
if (!response.ok || result.success !== true) {
throw new Error(result.message || "Could not submit the form");
}
// Show a receipt message; inspect result.note for upload warnings.References: SplitForms API reference
Submission error reference
These statuses and codes describe the public /api/submit endpoint. Account APIs and MCP authenticate differently; their 401 responses are not evidence that a public form key needs to be kept on your server.
| HTTP / code | Meaning | Next step |
|---|---|---|
| 400 missing_access_key | No form key in the body | Add a hidden access_key input, or use /f/YOUR_ACCESS_KEY. |
| 400 invalid_body | Malformed or unsupported body shape | Send a JSON object or valid form encoding. |
| 404 invalid_access_key | The form key was not found | Copy the form’s current key, not an MCP or account token. |
| 403 inactive_form | The owner paused the form | Re-enable it in the dashboard. |
| 403 origin_not_allowed | Strict origin protection rejected the site | Add the intended production domain to Allowed Domains. |
| 403 form_unavailable | The form is not accepting submissions | Contact support if this is unexpected. |
| 413 payload_too_large | Request exceeds the body limit | Reduce attachments and check individual file limits. |
| 415 unsupported_content_type | Unsupported request encoding | Use JSON, URL-encoded or multipart data. |
| 429 rate_limited | Too many requests in a short window | Pause; do not retry in a tight loop. |
| 429 quota_reached | Account submission allowance exhausted | Check quota information and billing. |
| 500 profile_lookup_failed or general failure | A server-side operation failed | Keep the input and check the dashboard before retrying. |
References: SplitForms API reference
Why a 200 response may not produce an email
Spam gates may return a generic success response without saving a lead or sending email. This prevents bots from learning which check rejected a request. Do not use automated production submissions as an uptime probe: they can trigger filtering and notifications.
For a legitimate missing lead, inspect the dashboard and Spam view, the allowed domain, verification setup and the actual request payload. For a saved lead with no email, inspect notification recipients and delivery settings. Submission acceptance and email delivery are separate steps.
References: Submission lifecycle and delivery checks
Handle an uncertain network result carefully
A timeout means the browser did not receive a response; it does not prove the server failed to save the submission. Blindly resending can create duplicates. Keep the visitor’s form data, display a useful message and reconcile against the dashboard when diagnosing a failure.
Disable the submit button only while the request is pending, restore it on failure, and show a visible status message. For background workflows, use your own job identifier to deduplicate downstream actions. Do not assume this public endpoint implements an Idempotency-Key header.
References: Duplicate submission guide
Frequently asked questions
Is every 429 a monthly limit?
No. rate_limited is a request-frequency limit. quota_reached is the account’s submission allowance; Free uses a total allowance rather than a monthly reset.
Should fetch use mode: no-cors?
No. An opaque response prevents your code from inspecting success and error details. Use the endpoint’s normal CORS support and request JSON.