Endpoint reference
The Forms ingest endpoint contract — accepted content types, field handling, responses, redirects, errors, CORS, and rate limits.
View as MarkdownSend an HTTP POST to https://f.easerix.com/[form-id] with form-encoded,
multipart, or JSON data — no authentication. JSON clients get back
{"ok": true, "id": "..."}; plain HTML posts get a 302 redirect when the form
is configured for one. Requests are rate-limited to 60 per minute per form
and IP.
The endpoint
Every form has one public URL, shown on its page in the app:
https://f.easerix.com/<form-id>| Method | Behavior |
|---|---|
POST | Submit the form — the contract below |
OPTIONS | CORS preflight — 204 with the form's CORS headers |
GET | A friendly JSON hint that the endpoint accepts POST |
No API key, no auth header — the form id in the URL is the only credential. A paused or deleted form responds 404 to POST, indistinguishable from a form that never existed.
Accepted content types
| Content-Type | Notes |
|---|---|
application/x-www-form-urlencoded | What a plain HTML <form method="POST"> sends — works with zero JavaScript |
multipart/form-data | Accepted; file contents are not stored — filenames are joined into a _files field |
application/json | A flat JSON object; body capped at 1 MiB |
Field handling
- Every value is stored as a string. JSON numbers, booleans, and
nullare stringified (nullbecomes an empty string); nested objects and arrays are stored as compact JSON text. - If the same field name is sent more than once (form-encoded/multipart), the last value wins.
- URL query parameters are not stored as fields — only the request body is.
- Name your email field
email: it becomes the notification's Reply-To and the autoresponder recipient.
Reserved field names
| Field | Meaning |
|---|---|
_gotcha, _honey | Honeypot traps — a non-empty value flags the submission as spam; always stripped before storage |
cf-turnstile-response | Cloudflare Turnstile token (the widget adds it automatically); may also be sent as an X-Turnstile-Token header; stripped before storage |
_files | Written by the server when a multipart submission includes files — don't send it yourself |
There is no per-request redirect field (no _next or similar) — the post-submit redirect is configured per form in the app (see below).
Success responses
The endpoint decides between a JSON response and a redirect based on how you called it. A request counts as a JSON client if any of these is true:
- the query string has
?ajax=1, - the
Acceptheader containsapplication/json, - the request body is
application/json.
| Caller | Response |
|---|---|
| JSON client | 200 with {"ok": true, "id": "<submission-id>"} |
| HTML post, form set to Redirect with a URL | 302 — Location is the form's configured redirect URL |
| HTML post otherwise | 200 with {"ok": true} |
Spam-flagged submissions return the same success response as clean ones — deliberately, so bots can't detect the filters.
Error responses
All errors are JSON with a single error field.
| Status | Body | When |
|---|---|---|
400 | {"error": "could not parse submission"} | Body didn't parse as the declared content type |
403 | {"error": "origin not allowed"} | Browser Origin not on the form's allowed-origins list |
404 | {"error": "form not found"} | Unknown form id — or a paused form (indistinguishable) |
429 | {"error": "rate limit exceeded"} | More than 60 requests/minute for this form from your IP |
500 | {"error": "could not save submission"} | Server-side storage failure |
CORS
By default any origin may POST to a form; browser requests get a matching Access-Control-Allow-Origin. To lock a form to your own sites, list them under Settings → Allowed origins in the app (exact origin match, e.g. https://mysite.com) — other origins then get 403. Requests without an Origin header (curl, server-to-server) are always allowed. OPTIONS preflight is handled, allowing POST and the Content-Type header.
Rate limits
60 requests per minute per form + client IP. Exceeding it returns 429; wait and retry.
Submit with JavaScript
const res = await fetch("https://f.easerix.com/YOUR_FORM_ID", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
},
body: JSON.stringify({
email: "[email protected]",
message: "Hello from my site",
}),
});
const data = await res.json(); // { ok: true, id: "..." }
if (!res.ok) {
console.error(data.error);
}The same snippet, pre-filled with your form's endpoint, is on the form's page in the app under Integrate → fetch().
Frequently asked questions
Do I need an API key to submit?
No. The ingest endpoint is public by design — the form id is the only credential. (The management API at /v1/... is a different, authenticated surface.)
Can I make a plain HTML form show JSON instead of redirecting?
Yes — append ?ajax=1 to the endpoint URL in your form's action, or send an Accept: application/json header. The query parameter is not stored as a submission field.
How do redirects after submit work?
Set Settings → Success behavior to Redirect and provide the URL in the app. Plain HTML posts then get a 302 to it. JSON clients never get redirected — they always receive the JSON body.
Last updated on