You build forms in the admin under Site → Forms. Every form has a slug, and every question has an id. The form's Integration tab shows you the endpoint, the write key and a snippet that works as is.
POST https://intake.foundationcms.org/v1/<site>/forms/<slug> works from day one
POST https://intake.<your domain>/forms/<slug> once the intake address is set up (no CORS preflight)
Content-Type: application/json
x-foundation-key: <the form's write key>
{ "q_name": "Amara Reid", "q_email": "amara@example.com", "q_message": "…",
"cf-turnstile-response": "<token>" }
Answers are keyed by question id. Only send the form's own ids, because any question the form doesn't have will be rejected.
If the site has spam protection switched on (Settings → Site → Spam protection), you need to render its widget inside your form. That's Cloudflare Turnstile by default, or Google reCAPTCHA. Use the site key shown on that settings page, then send the token the widget produces (cf-turnstile-response or g-recaptcha-response) along with the answers.
Don't submit until the token is there. If the form is sent before the check has finished, it'll be rejected.
Foundation's own sites post forms to their own Worker first. The Worker adds the write key and the visitor's IP address, then forwards the submission, so the key never reaches the browser.
If the key does end up in your page source, the damage is limited: someone could only submit that one form, and the rate limits below still apply. It's still better kept on the server.
| Status | Meaning |
|---|---|
| 201 | { "ok": true }. The submission was saved and sent on to wherever the form delivers. Senders the site has marked as spam get exactly the same response, but their submission is thrown away, so retrying tells them nothing. |
| 403 | One of three reasons: human-check (the token was missing or failed), not-authorised (the key doesn't match this site and form), or form-closed (the form has been switched off in the admin). |
| 404 | There's no form with that slug on this site. |
| 413 | The submission is too large. |
| 422 | { "problems": { "<question id>": "<kind>" } }. Show each problem next to the field it belongs to. |
| 429 | Rate limited. The limits are 20 a minute per form, 60 a minute per site and 30 an hour from a single address. |