POST /api/websites/{website}/forms/{slug}/submit
Records a request and triggers the client's notification email.
| Check | Value |
|---|---|
| Authentication | website signature, X-Website-Signature and X-Website-Timestamp headers — or, from a server, Authorization: Bearer … |
| Origin | the Origin header must match the website's host; not checked with a token |
| Captcha | the website's chain — see The captcha; also with a token |
| Rate limiting | api-submit, per IP and per website; 5 per minute on the default plan, plus 1 000 per hour per website |
| Body size | 256 KB at most, 413 beyond — see Error codes |
Path parameters
| Name | Type | Purpose |
|---|---|---|
website |
UUID | The website the request comes from |
slug |
string | The form identifier — contact, quote… |
Headers
| Header | Required | Value |
|---|---|---|
Content-Type |
yes | application/json |
X-Website-Signature |
yes, without a token | Signature obtained from the signature endpoint |
X-Website-Timestamp |
yes, without a token | Timestamp returned with that signature |
X-Captcha-Provider |
depending on the chain | Key of the chain link that produced the token |
X-Captcha-Token |
depending on the chain | The captcha token |
Body
The form fields, by their field name, plus these context fields, all optional:
| Field | Type | Purpose |
|---|---|---|
page_url |
string | URL of the page the form was submitted from; used to attribute the request to a page |
page_title |
string | Title of that page |
page_locale |
string, 35 max | Language of that page, from <html lang> — en-GB |
form_locale |
string, 10 max | Language the form was rendered in — en; see Languages |
visitor_hash |
string | Fingerprint returned by the tracking endpoint; used for attribution |
captcha_fallback |
string | The fallback_token returned with a captcha_fallback error, when replaying a submission at the link the server designated — see The captcha |
The script sends all of them, visitor_hash once it knows it. The language of the response — validation messages and
confirmation message — is form_locale when the form offers it; otherwise
page_locale (or the website language, when page_locale is missing or not a supported
language) resolved among the form's languages, en-GB giving en; the form's source
language as a last resort. The request records that language.
A field that is sent but not declared in the form is kept with the request: nothing is
silently lost. A field hidden by a display condition is neither validated nor recorded.
A consent field is sent as 1 when checked; a required one that is missing or not
accepted is refused with a 422.
Example
curl -X POST https://api.example.com/api/websites/019f0000-0000-7000-8000-00000000000a/forms/contact/submit \
-H 'Content-Type: application/json' \
-H 'Origin: https://www.example.com' \
-H 'X-Website-Signature: 7c1f…' \
-H 'X-Website-Timestamp: 1789459200' \
-H 'X-Captcha-Provider: recaptcha_v3:12' \
-H 'X-Captcha-Token: 03AGdBq…' \
-d '{
"name": "Smith",
"email": "[email protected]",
"message": "Hello, I would like a quote for a swing gate.",
"page_url": "https://www.example.com/contact",
"page_title": "Contact",
"page_locale": "en-GB",
"form_locale": "en",
"visitor_hash": "a509475f…"
}'
Response 200
{
"success": true,
"message": "Your message has been sent. We will get back to you as soon as possible.",
"lead_id": "019f0000-0000-7000-8000-0000000000ff"
}
message is the confirmation message defined by the client, in the language of the
submission: display it as is. A message overridden per website only applies in that
website's language; in any other language, the form's translated message is returned.
Errors
| Code | Body | Cause |
|---|---|---|
404 |
{"message": "Website not found."} |
Unknown UUID |
404 |
{"message": "Form not found."} |
The form does not exist, is inactive, or is not linked to this website |
403 |
{"error": "origin_not_allowed"} |
The Origin does not match the website's host |
403 |
{"error": "invalid_signature"} |
Signature missing, tampered with or expired |
403 |
{"error": "captcha_required"} |
No captcha token received although the chain requires one |
403 |
{"error": "captcha_failed"} |
Token rejected by the provider, or longer than 4 096 characters |
403 |
{"error": "captcha_provider_not_allowed"} |
The chain link is not one this submission may use — see The captcha |
403 |
{"error": "captcha_fallback", "next": "altcha:7", "fallback_token": "1789459320.4f3b…"} |
The chain link is unavailable: start again with the link given, carrying fallback_token |
413 |
{"error": "payload_too_large"} |
The body exceeds 256 KB |
422 |
{"message": "…", "errors": {…}} |
Validation — see Validation errors |
429 |
— | Too many submissions; see Rate limiting |
From a server
A token from the website's organization replaces the signature and the origin check, not
the captcha: when the website's chain has links, send the token solved on the page in
X-Captcha-Provider and X-Captcha-Token, and the fallback_token of a previous
captcha_fallback in the captcha_fallback field. The PHP library
makes this call with submissions()->submit().
The client's quota never shows up here. A request received beyond their plan is accepted and recorded; only the notification email stops. A visitor never gets an error because of a billing matter.
Retrying properly
Two cases, and only two, deserve an automatic second attempt:
- a
403 invalid_signature: request a new signature, then retry once. This happens when the website key was regenerated while the visitor was filling in the form; - a
403 captcha_fallback: resume solving at thenextchain link, once per link, and send thefallback_tokenback in thecaptcha_fallbackfield of the replay. The token is valid for two minutes and only designates that one link; without it, the server refuses the link withcaptcha_provider_not_allowedunless it is an ALTCHA link.
Any other error is final: retrying the same request will produce the same response.
API