Responses and failure

Most APIs have an errors page listing status codes. This one does not have any to list. The submission endpoint answers 2xx to every outcome it is willing to acknowledge, including the ones that threw your submission away.

Why there are no error codes

The endpoint is public and unauthenticated. Anyone who can read your page’s HTML has your form ID, which means anyone can post to it, and the only thing standing between that and your inbox is a set of checks a bot would very much like to see the results of.

A 4xx tells a bot which part of its payload was wrong. 400 Bad honeypot is a free hint to remove the field and try again; 404 No such form turns the endpoint into an oracle for enumerating valid form IDs. So a submission that is accepted, one whose honeypot was filled, one posted to a form that does not exist, and one from an origin that is not on the allowlist all get the same answer.

What you can actually tell

One signal survives, and only in a browser: whether CORS headers came back.

The handler sends Access-Control-Allow-Origin only when the request’s Origin is on the form’s allowlist. A browser refuses to hand a cross-origin response to your JavaScript without that header, so the fetch rejects. That is the failure you can observe, and it is the one that matters, because an origin that is not allowed is also the one case where nothing is stored at all.

const response = await fetch(action, {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify(fields),
})

// The endpoint answers 2xx for every outcome it is willing to talk about,
// including spam, so there is nothing to branch on beyond the request having
// succeeded at all. A non-2xx here means it never reached the handler, which
// for a browser means CORS.
if (!response.ok) throw new Error(String(response.status))

A bot posting with curl sees none of this, because nothing enforces CORS outside a browser. That asymmetry is the point: the header is a browser mechanism, so it can help you without helping a script.

Debugging a form that looks broken

In rough order of how often each one turns out to be the answer:

  • The origin is not on the allowlist. Scheme and host must match exactly, so https://example.com and https://www.example.com are two different origins, and a preview deployment is a third. Check the browser console for a CORS error.
  • The honeypot field is filled. Something is autofilling it, most often a password manager reacting to a field name that looks meaningful. The submission is stored and marked spam rather than delivered, so it will be in the console. See Spam and origins.
  • The honeypot field name is wrong. If the name in your markup does not match the form’s, the real honeypot is absent, which is fine, and you have added a stray field to every submission.
  • No Origin header at all. A plain form post is a top-level navigation and browsers disagree about whether those carry one. Referer is consulted as a fallback, but only for form-encoded posts, and a strict Referrer-Policy can strip that too. A request with neither is discarded.
  • The body was rejected. Over 100,000 bytes, more than 50 fields, a value over 10,000 characters, a multipart/form-data content type, malformed JSON, or a form-encoded body where every field is blank. See Submitting a form for the limits.

The console is the source of truth throughout. A submission that was stored is visible there whether or not it was delivered, and a submission that is not there was either discarded at the origin check or never arrived.

Confirming a destination is not part of this API. The link in a verification email opens a page in the console, which is where it belongs: the audience is a person with a browser, not a script. Nothing on api.ezform.io answers it.

If a verification link does not work, the console will say why. There is no status code here to reason about, because there is no endpoint here to call.