Submitting a form
ezform has one public endpoint. It takes a flat map of fields, stores it, and queues it for delivery. There is no authentication: the form ID in the URL identifies the form, and the origin allowlist decides whether the submission is kept.
There is no read API. Submissions are read in the console or in the email that gets delivered. Nothing here lists, fetches, or exports them.
Submit a form
Posts one submission. The body is a flat object of field names to values, in either JSON or URL-encoded form.
Field names and values are yours; ezform does not reserve any. There is no _subject, no
_next and no _replyto. A redirect is configured on the form, not passed in the body, and
the notification’s Reply-To is inferred from whichever field looks like an email address.
Body
- Name
<field name>- Type
- string | number | boolean
- Description
Any field name you like, up to 100 characters. Numbers and booleans are accepted and coerced to strings, because everything is ultimately stored and rendered as text.
Limits
- Name
fields- Type
- 50
- Description
At most 50 fields per submission, and at least one field present. A form-encoded body must also have at least one field that is not blank, because a browser posts every input it has and a form where nothing was filled in is not a lead. JSON is taken at its word:
{"email": ""}is one field and is accepted.
- Name
key length- Type
- 100 characters
- Description
Per field name.
- Name
value length- Type
- 10,000 characters
- Description
Per field value.
- Name
body size- Type
- 100,000 bytes
- Description
The whole request body, measured as UTF-8.
These are bounds that stop one request becoming a storage problem, not spam heuristics.
Request
curl https://api.ezform.io/f/YOUR_FORM_ID \
-H "content-type: application/json" \
-H "origin: https://example.com" \
-d '{"email":"someone@example.com","message":"Hello"}'await fetch('https://api.ezform.io/f/YOUR_FORM_ID', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
email: 'someone@example.com',
message: 'Hello',
}),
})<form action="https://api.ezform.io/f/YOUR_FORM_ID" method="POST">
<input type="email" name="email" required />
<textarea name="message" required></textarea>
<button type="submit">Send</button>
</form>{ "ok": true }Content types
Two encodings are supported, and which one you use changes the shape of the reply as well as the parsing.
- Name
application/json- Type
- default
- Description
What
fetchsends. Anything unrecognised, including a request with noContent-Typeat all, is also parsed as JSON, because a failed parse is a clearer error than a guess.
- Name
application/x-www-form-urlencoded- Description
What a plain form posts with no JavaScript. Repeated keys, which is what a checkbox group sends, are joined with a comma rather than overwriting each other.
- Name
multipart/form-data- Type
- refused
- Description
Deliberately not accepted. Browsers only send it when there are file inputs, and accepting it would mean silently dropping the files inside, which is worse than refusing the type.
curl https://api.ezform.io/f/YOUR_FORM_ID \
-H "content-type: application/x-www-form-urlencoded" \
-H "origin: https://example.com" \
-d "email=someone@example.com&message=Hello"What comes back
Three shapes, chosen by what the caller looks like it wants. Accept: application/json wins
outright; failing that, JSON in means JSON out.
- Name
200- Type
- application/json
- Description
A body of
{"ok": true}. Sent when the request was JSON, or when the form post asked for JSON in its Accept header.
- Name
303- Type
- redirect
- Description
A form post with no JavaScript, on a form that has a redirect path configured, from an allowed origin. The target resolves against the origin the request came from, so it is always somewhere the account authorised. A 303 rather than a 302, so the browser follows it with GET and a refresh cannot repost the form.
- Name
200- Type
- text/html
- Description
Any other form post: a built-in page saying the message was received.
Every one of those is a 2xx, and so is every failure. That is not an omission; see Responses and failure.
curl https://api.ezform.io/f/YOUR_FORM_ID \
-H "content-type: application/x-www-form-urlencoded" \
-H "accept: application/json" \
-H "origin: https://example.com" \
-d "email=someone@example.com&message=Hello"Rate limits
There is one throttle, and it is global rather than per-form: 10 requests per second sustained, bursting to 20, shared across every form on the service. None of it is configurable, and per-form limits are not built yet.
In practice the origin allowlist is what stands between a leaked form ID and a flood, because a submission from an unlisted origin is discarded before it is written.