Form endpoints
An endpoint is a URL you can point an ordinary HTML form at. Submissions land in a Minion form, where you read them in the Responses tab or export them as CSV.
The point is that your page no longer needs an email address on it. There is nothing for a scraper to pick up — just the endpoint URL, which is useless for sending spam to a person.
What you get, and what you don’t
Section titled “What you get, and what you don’t”| Submissions stored in Minion | Yes |
| Works with no JavaScript | Yes |
| Read them in the UI, or export CSV | Yes |
| Read them from a minion (AI agent) | Yes — the existing Forms API |
| Email notification | Not yet. Check the Responses tab, or have a minion poll it |
| Auto-reply to the sender | Not yet |
| File attachments | Not supported. A submission with a file is rejected |
Setting one up
Section titled “Setting one up”- Open the form in Minion → the Endpoints tab → Create endpoint.
- Fill in Allowed sites with the site the form lives on, and Redirect after submit with a page on that same site.
- Copy the Submit URL, or the whole embed snippet.
The form
Section titled “The form”This is the whole integration. No JavaScript, no build step.
<form action="https://YOUR-HQ/api/public/forms/e/YOUR-TOKEN" method="POST"> <input type="text" name="name"> <input type="email" name="email"> <textarea name="message"></textarea>
<!-- Spam trap. Keep it hidden and leave it empty. --> <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
<button type="submit">Send</button></form>You do not declare the fields in Minion. Whatever name attributes you use become the
columns in the Responses tab. Rename them, add them, drop them — nothing to keep in sync.
The spam trap
Section titled “The spam trap”_gotcha is a field a human never sees and never fills. Bots fill everything they find, so a
submission with a value in it is discarded.
Note that it is not called company — on a contact form “company name” is a field real
people fill in, and using it as a trap would silently throw away real enquiries.
Reserved names
Section titled “Reserved names”Names starting with _ are instructions to the endpoint and are not stored:
| Name | Meaning |
|---|---|
_gotcha | Spam trap (above) |
_next | Override the redirect for this one form (must be an allowed site) |
Avoid _ for your own fields, or they will vanish.
Where the visitor lands
Section titled “Where the visitor lands”Because a plain form POST is a page navigation, the browser ends up on the endpoint URL — your page can’t intercept it. So the endpoint sends the visitor back to your site:
- Normally, to the Redirect after submit URL you configured.
- To
_nextinstead, if you sent one and it points at an allowed site.
If neither is usable, Minion shows a plain “we got your message” page. It works, but it is your visitor sitting on our domain looking at an unstyled page — set a redirect.
Minion has no thank-you page to customize. That page belongs on your site, where your design and your conversion tracking are.
Allowed sites
Section titled “Allowed sites”List the sites the form lives on, one per line (https://example.com). Once the list is
non-empty, submissions from anywhere else are refused.
- Subdomains are not implied.
https://example.comandhttps://www.example.comare two entries. http://andhttps://are different too.- Leave the list empty and any site can submit. That is fine while you’re testing; fill it in before you ship.
Submitting with JavaScript instead
Section titled “Submitting with JavaScript instead”If you’d rather handle the result yourself, post JSON. You get JSON back and no redirect.
const res = await fetch(endpointUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, email, message }),})if (res.ok) { /* 201 { ok: true } */ }Your site’s origin must be in Allowed sites for the browser to accept the response.
Limits
Section titled “Limits”| Fields per submission | 50 |
| Field name length | 64 characters |
| Value length | 10,000 characters |
| Whole submission | 64 KB |
| Per IP | 10 per minute |
| Per endpoint | Your Daily limit (500 by default) |
The daily limit is an abuse cap, not a quota — set it well above what you expect. Submissions past it are refused and not stored.
Errors
Section titled “Errors”JSON requests get { "error": "<code>" }. Plain form posts get a minimal page with the same
code on it.
| Code | Status | What happened |
|---|---|---|
not_found | 404 | Wrong token, or the endpoint was revoked |
origin_not_allowed | 403 | The submitting site isn’t in Allowed sites |
not_accepting | 403 | The form is unpublished, closed, or past its closing date |
rate_limited | 429 | Too many submissions from one IP |
daily_limit_reached | 429 | The endpoint’s daily limit |
empty_submission | 400 | Nothing to store (every field was empty or reserved) |
too_many_fields / value_too_long / payload_too_large | 400 | Over a limit above |
attachments_not_supported | 400 | A file was attached |
Turning one off
Section titled “Turning one off”Revoke the endpoint. Forms already pointing at it stop working immediately, and submissions you’ve already received stay where they are. Revoking is not reversible from the embed’s point of view — a new endpoint gets a new URL, so you’d have to update the page.
One form can have several endpoints. Give each site its own, so you can turn one off without touching the others.