Skip to content
Reference

The forms API

How a signup form takes a POST from any site, as plain HTML or as JSON.

Every signup form has one public address, its hosted page, and the same address takes a POST from anywhere on the internet. A plain HTML form on a static site works with JavaScript off. A fetch call that sends JSON gets JSON back. No key and no CSRF token are needed, because the address only adds someone to your list. Copy a form's address from its page under Money > Forms; below it is written FORM_URL.

Post from an HTML form

<form method="post" action="FORM_URL">
  <label>Email <input type="email" name="email" required></label>
  <label>Name <input type="text" name="name"></label>
  <div style="position:absolute;left:-9999px" aria-hidden="true">
    <input type="text" name="website" tabindex="-1" autocomplete="off">
  </div>
  <button type="submit">Subscribe</button>
</form>

The form's page gives you this snippet ready to paste, with a view pixel included so the form's numbers count views from your site.

  • On success, the reader goes to the form's redirect URL if you set one, otherwise to a thank-you page with the form's success message.
  • On a problem, the reader sees the hosted page again with the problem named and their typing kept.

Post as JSON

Send Content-Type: application/json (or Accept: application/json):

curl -X POST FORM_URL \
  -H "Content-Type: application/json" \
  -d '{"email": "reader@example.com", "name": "Ada"}'

Success is 200:

{"ok": true, "status": "confirm_sent", "message": "..."}

status is confirm_sent when the form uses double opt-in (the reader must click the link in their inbox) and subscribed when it does not. message is the form's success message.

A refusal has "ok": false and an error:

StatusErrorWhen
400invalidThe address is not valid; fields names the problem.
400challenge_requiredThe form requires the challenge, which only its hosted page can carry.
404unknown_formNo form at that address.
410closedThe form is switched off.
429rate_limitedToo many signups. Wait the seconds in Retry-After.

Fields

Only email, name, and website are read. Anything else is dropped.

  • email is required.
  • name is optional.
  • website is the honeypot. Keep it in your markup, hidden from people. A post with anything in it is answered as a success and nobody is added.

Things to know

  • Cross-origin requests are allowed from any site, so a browser fetch works without a proxy.
  • With double opt-in on, nothing is tagged, enrolled, or mailed (apart from the one confirmation) until the reader clicks.
  • One IP address may sign up 5 times in 10 minutes, and one address gets at most 3 confirmation mails a day.
  • With the challenge on, link to the hosted page instead of posting to it.
  • Only the redirect saved on the form is ever followed, never one sent in the request.