Errors
Draft. The production host names are not live yet, so the addresses on this page do not work. The table below is generated from the error catalog in the code; the docs_url links point at pages that are not all written yet.
The error body
Refusals from the public form route and the MCP endpoint are built from one catalog. A JSON refusal carries error, message, fix and docs_url and the HTTP status in the table. Three cases differ: a browser form post that fails with a visitor-fixable error is redirected (303) to a hosted error page; an MCP refusal inside a tool call is HTTP 200 with isError: true (see below); and the MCP transport itself answers protocol-level problems in its own JSON-RPC format.
{
"success": false,
"error": "origin_not_allowed",
"message": "This form does not accept posts from that site.",
"fix": "Add the site to allowed domains, or call from that site.",
"docs_url": "https://contactforms.dev/docs/domains"
}
On POST /f/:key a JSON error also carries body.message with the same text, so a client migrated from Web3Forms finds its message where it looks. A browser post that fails with a visitor-fixable error is redirected (303) to a hosted error page instead.
MCP tool failures arrive as a tool result with isError: true and the fields error, message, fix and docs_url as JSON text. A tool that is registered but not built answers with the extra code not_available_yet, which is not in the HTTP catalog:
{
"isError": true,
"content": [
{ "type": "text",
"text": "{\"error\":\"not_available_yet\",\"message\":\"list_submissions is registered but not available yet.\",\"fix\":\"...\",\"docs_url\":\"https://contactforms.dev/docs/mcp\"}" }
]
}
Headers
429 responses carry Retry-After. 503 responses carry it when the server knows a wait time. 405 responses carry Allow. Inside an MCP tool call the wait is in the result as retry_after_seconds, not a header. MCP 401 responses carry WWW-Authenticate: Bearer realm="contactforms".
Error codes (91)
Text in angle brackets, such as <size>, is replaced with a real value when the error is sent.
| Code | Status | Message | Fix |
|---|---|---|---|
| invalid_body | 400 | The request body could not be read. | Send a form-encoded, multipart or JSON body. |
| elementor_secret_invalid | 401 | This webhook address is not valid. | Copy the current Elementor webhook address from Forms > Install, including the secret at the end. |
| invalid_key | 401 | This key is not valid. | Copy a current key from Settings > API keys. |
| key_revoked | 401 | This key was revoked. | This key was revoked. Create a new key in Settings > API keys. |
| missing_credential | 401 | Sign in to continue. | Send your key as `Authorization: Bearer <key>`, or sign in. |
| signature_invalid | 401 | The signature does not match. | Check the signing secret. |
| webhook_signature_invalid | 401 | This webhook request is not signed correctly. | Send it from the provider's webhook, signed with the endpoint's signing secret; do not edit the body. |
| plan_limit_forms | 402 | Your plan does not allow more forms. | Archive a form you no longer use, or move to a plan that holds more forms. |
| plan_limit_sites | 402 | Your plan does not allow more sites. | On Pro or Agency, add a site for USD 9.99 a month. On Base, upgrade to Pro. |
| plan_limit_storage | 402 | Your plan does not have room for the stored leads and files of this site. | Delete leads you no longer need, then try again. Nothing was changed. |
| plan_required | 402 | Your plan does not include this. | This needs the <plan> plan. See Billing for what your plan includes. |
| rules_limit | 402 | Your plan allows 3 routing rules. | Remove a rule. Pro has unlimited rules. |
| account_suspended | 403 | This account is suspended. | Contact support to restore the account. |
| claim_email_mismatch | 403 | This key was created for a different email address. | Sign in with the address the key was created for (<masked>), verified, then open the claim link again. |
| claim_required | 403 | This key is waiting to be claimed. | Open the claim link (<claim_url>) while signed in with the address it was created for. The key, the form URL and the stored leads are kept for 90 days. |
| client_read_only | 403 | You have view-only access. | You have view-only access. Ask the agency that manages this site. |
| consent_required | 403 | This WordPress site has not connected with the owner's consent. | In WordPress, open the ContactForms settings, tick the consent box (and the heartbeat box, for the daily health report) and connect the site. Nothing is accepted from it before that. |
| email_not_verified | 403 | Your email address is not verified. | Verify your email address in your sign-in settings, then try again. |
| form_not_accepting | 403 | This form is not accepting submissions. | The site owner has to renew the subscription to reopen it. Nothing you sent was stored. |
| key_suspended | 403 | This key is suspended. | The key kept making refused or invalid requests, so it was stopped. Ask the site owner to resume it on the Agents page, and fix the loop in your agent first. |
| origin_not_allowed | 403 | This form does not accept posts from that site. | Add the site to allowed domains, or call from that site. |
| role_not_allowed | 403 | Your role cannot do this. | Ask a site owner to do this, or to change your role. |
| scope_missing | 403 | This key cannot do that. | This key can only <scope>. Create a key with the scope you need. |
| upload_grant_invalid | 403 | These files cannot be added to that message. | Send the files from the page that sent the message, right after sending it. |
| uploads_not_enabled | 403 | This form does not take file uploads. | The form's owner adds a file field in the builder (Pro plan). |
| wp_mail_not_allowed | 403 | This WordPress mail is not allowed. | In ContactForms open Settings > WordPress, turn mail repair on and approve the recipient. Only approved, verified recipients can be mailed, one per message. |
| approval_not_found | 404 | No approval was found. | Check the approval link, or open the pending approvals list for this site. |
| destination_not_found | 404 | That destination was not found. | Reload the Destinations page and pick one from the list. |
| form_not_found | 404 | Form not found. | Check the form key (looks like f_8Kq2xLm9). |
| invite_invalid | 404 | This invitation cannot be used. | Sign in with the email address the invitation was sent to, or ask the agency for a new invitation. |
| key_not_found | 404 | Key not found. | Check the key ID in Settings > API keys. |
| route_not_found | 404 | There is no such API route. | Check the path and method against the API reference. |
| site_not_found | 404 | Site not found. | Check the site ID. |
| submission_not_found | 404 | Submission not found. | Check the submission ID. |
| upload_ticket_invalid | 404 | This upload link is not valid. | Ask for a new upload link; each one works once. |
| method_not_allowed | 405 | That method is not supported here. | Use one of the methods listed in the Allow header. |
| approval_changed | 409 | The request changed after it was shown for approval. | The request changed after approval. Ask again with the same arguments. |
| approval_declined | 409 | This approval was declined. | The owner declined this request. Ask again only if the owner agrees. |
| approval_used | 409 | This approval was already used. | This approval was already used. Ask again. |
| claim_pending | 409 | A key for this address is already waiting to be claimed. | Claim it with the link we emailed, or ask for the link again: POST /api/claim/resend with that key. Or create the key with a different address. |
| destination_limit | 409 | This form already has the most destinations it can have. | Disconnect a destination you no longer use, then add this one. |
| destination_paused | 409 | This destination is paused after repeated failures. | Wait until the pause ends, or update the destination's settings to start again. |
| destination_type_unavailable | 409 | That destination type is not available yet. | Use an email or webhook destination for now. |
| export_in_progress | 409 | An export is already being prepared. | Wait for the current export to finish, then start another. |
| export_not_ready | 409 | This export is not ready yet. | Wait until the export shows Download, then try again. |
| fields_in_builder | 409 | This form's fields are edited in the form builder. | Open the form's Build page, change the fields there and publish. |
| form_archived | 409 | This form is archived. | Archived forms cannot be edited. Create a new form instead. |
| id_conflict | 409 | That submission ID was already used with different content. | Use a new _id for a different submission, or resend the original payload. |
| key_limit_reached | 409 | This site already has the maximum number of active keys. | Revoke a key you no longer use, then create the new one. |
| last_owner | 409 | A site needs at least one owner. | Make another member an owner first. |
| references_broken | 409 | Routing rules or workflows use a field you renamed or removed. | Read the list, then publish again and confirm the changes, or put the field back. |
| stale_draft | 409 | This form was changed by someone else since you opened it. | Reload the builder to see the latest version, apply your change again, and save. |
| stale_preview | 409 | The leads waiting for retry changed since you looked. | Look at the new count and confirm again. |
| stale_revision | 409 | These rules were changed by someone else since you opened them. | Reload the rules, apply your change again, and save. |
| stale_workflow | 409 | This workflow was changed by someone else since you opened it. | Reload the workflow, apply your change again, and save. |
| upload_limit_reached | 409 | This message already has the most files it can take. | A message takes at most <count> files. |
| upload_not_ready | 409 | The message is still being checked. | Wait <seconds> and ask for the upload again. |
| upload_ticket_used | 409 | This upload link was already used. | Ask for a new upload link; each one works once. |
| approval_expired | 410 | This approval expired. | The approval expired. Ask again. |
| claim_invalid | 410 | This claim link is not valid. | Ask for a new claim link: POST /api/claim/resend with your key. It is sent only to the address the key was created with. |
| export_expired | 410 | This export has expired. | Start a new export; files are kept for 24 hours. |
| key_gone | 410 | This key was retired. | Unclaimed keys are removed after 90 days and are never re-issued. Create a new key: POST /api/forms with your email. |
| upload_ticket_expired | 410 | This upload link has expired. | Ask for a new upload link; each one lasts ten minutes. |
| file_too_large | 413 | That file is larger than allowed. | Send a file of at most <size>. |
| payload_too_large | 413 | The request is larger than allowed. | Keep the body under <size>. Send files with an upload URL. |
| file_type_not_allowed | 415 | That kind of file is not accepted. | Send a PNG, JPEG, GIF, WebP, PDF, text, CSV, DOCX, XLSX or PPTX file whose name ends the way its contents are. |
| unsupported_media_type | 415 | That content type is not supported. | Use application/x-www-form-urlencoded, multipart/form-data or application/json. |
| bad_order | 422 | That order does not list every rule exactly once. | Reload the rules and move them again. |
| destination_config_invalid | 422 | The destination settings are not valid. | Check the settings against the message shown and save again. |
| fallback_required | 422 | Choose who gets a lead that matches no rule. | Add at least one verified recipient to the fallback, then save. |
| file_upload_unsupported | 422 | This endpoint takes text fields only. | Send files with an upload URL; a plain form post carries text only. |
| invalid_email | 422 | That email address is not valid. | Send a valid email address in <name>. |
| recipient_not_verified | 422 | That address is not a verified recipient of this form. | Verify the address in the form's recipients first, then use it in a rule. |
| routing_invalid | 422 | The routing rules are not valid. | Fix the rule named in the message and save again. |
| upload_failed | 422 | The upload could not be accepted. | Check the file, then ask for a new upload link. |
| validation_failed | 422 | Some fields are not valid. | Fix the fields listed in `fields`. |
| webhook_url_blocked | 422 | That webhook URL is not allowed. | Use a public https URL (private and internal addresses are refused). |
| workflow_invalid | 422 | The workflow is not valid. | Fix the step named in the message and save again. |
| approval_limit | 429 | Too many approvals are waiting. | Ask the owner to approve or decline the pending requests first; an account can have 20 waiting at once. |
| attempt_cap | 429 | This lead has already been tried five times in the last 24 hours. | Try again once the earliest attempt is more than 24 hours old. |
| budget_reached | 429 | A plan or safety limit was reached. | A plan or safety limit was reached (<budget>). It resets <when>. |
| rate_limited | 429 | Too many requests. | Wait <seconds> and retry. |
| unclaimed_cap | 429 | This unclaimed form has used its bounded allowance of held submissions. | Claim the form with the link we emailed. Nothing already stored is lost. Claiming does not by itself start delivery or give a fresh allowance: that needs an active plan or trial. |
| export_failed | 500 | The export could not be prepared. | Start the export again. If it keeps failing, see the status page. |
| store_failed | 500 | Nothing was saved. | Nothing was saved. Retry. If it keeps failing, see the status page. |
| approval_unavailable | 503 | This action cannot be approved yet. | This action is not available in this release. Nothing was changed. |
| capability_paused | 503 | This feature is paused. | Retry later; the pause is lifted by the service owner or the site owner. |
| capacity_reached | 503 | Storage for this account is full. | Delete or export leads you no longer need; new leads are accepted again as soon as space is free (<budget>). |
| model_not_configured | 503 | This feature is not set up yet. | No model is connected yet, so nothing was run and nothing was spent. It works once the service owner connects one. |
| unavailable | 503 | The service is temporarily unavailable. | Try again in a minute. |
| webhook_not_configured | 503 | This webhook endpoint is not set up on this server. | Set the provider's signing secret on the server, then let the provider retry. |