Submitting and errors
Submitting
POST /api/v1/forms/frm_…/submissions
Content-Type: application/json
{
"subject": "Can't sign in",
"fields": { "message": "I can't sign in since this morning." },
"locale": "en"
}
| Field | Required | What |
|---|---|---|
contact.email | yes | The requester's email address, at most 254 characters. |
contact.name | At most 200 characters. Ignored unless the form asks for a name. | |
subject | At most 300 characters. Ignored unless the form asks for a subject. | |
fields | Answers by field key. Unknown keys and hidden (conditional) fields are dropped. File fields can't be sent here. | |
locale | The requester's language, like en or no. | |
meta | Server mode only: up to 30 keys of strings (up to 1,000 characters), numbers, booleans or null, stored with the case. Ignored in browser mode. | |
turnstileToken | Browser mode: the Turnstile widget's token. | |
startToken | Browser mode: startToken from the definition. | |
_hp | Browser mode: the honeypot field. Leave it empty. |
The body may be at most 64 kB.
[!TIP] An
Idempotency-Keyheader (up to 200 characters) makes retries safe: the same key on the same form within 24 hours returns the first answer instead of creating a second case.
| Mode | When |
|---|---|
| Server mode | The request carries X-Desk-Signature, X-Desk-Timestamp or Authorization: Bearer. |
| Browser mode | Otherwise. |
Each mode must be turned on for the form.
Answer
{ "case": { "ref": "ABC-123", "status_url": "https://support.example.com/t/…" } }
201 Created with the case's reference and the requester's own link to the case page.
[!WARNING] The link lets anyone who has it read and answer the case: show it to the requester, but don't log it or share it further.
In browser mode, status_url is null when the email address already belongs to a contact. Anyone can type a
known contact's address into a form, so their case link goes only to their inbox, in the confirmation email. A
browser-mode submission also never changes a known contact's name or language. Server mode vouches for its
senders, so it always gets the link.
The answer is the same when the case is held for an agent to accept (the unit only takes requests from known contacts, and holds the rest).
Rate limits
Both modes share the form's Rate limits:
| Setting | Default |
|---|---|
| Requests per address per window | 5 per 600 seconds |
| Hourly cap for the whole form | 200 |
[!IMPORTANT] Above the cap the form pauses, answers
503 form_paused, and admins get an email; an admin resumes it on the form's page.
Errors
Errors have the shape { "error": { "code": "…", "message": "…", "details": … } }. Every attempt, accepted or
refused, is listed with its reason in the form's Submissions tab.
| Status | Code | Meaning |
|---|---|---|
| 404 | form_not_found | No form with this id. |
| 403 | form_disabled | The form is turned off. |
| 503 | form_paused | The form is paused after its hourly cap. |
| 403 | server_mode_disabled, browser_mode_disabled | That mode isn't turned on for the form. |
| 401 | signature_missing | Server mode without both X-Desk-Timestamp and X-Desk-Signature. |
| 401 | timestamp_invalid | The timestamp isn't Unix seconds. |
| 401 | timestamp_expired | The timestamp is more than five minutes off. |
| 401 | signature_invalid | The signature doesn't match any active secret. |
| 401 | signature_replayed | This exact signed request was already received. Sign each request anew; to retry safely, send an Idempotency-Key. |
| 401 | bearer_not_allowed, invalid_key | A bearer key on a form that doesn't accept them, or an unknown key. |
| 403 | ip_not_allowed | The sender's address isn't on the form's allowlist. |
| 403 | origin_not_allowed | The website isn't under Allowed websites. |
| 400 | spam_detected | The honeypot was filled in. |
| 400 | start_token_invalid, start_token_expired, start_token_used | The start token is missing, not from this form, older than 24 hours, or already used 5 times. Reload the form. |
| 400 | too_fast | Sent sooner than the minimum fill time. |
| 403 | turnstile_missing, turnstile_failed | No Turnstile token, or Cloudflare rejected it. |
| 429 | rate_limited | Too many submissions from this address. |
| 422 | validation_failed | The body or a field is invalid; details lists { path, message } per problem, like fields.message. |
| 403 | sign_in_required | The unit only takes requests from signed-in contacts; use server mode or the portal. |
| 403 | not_a_known_contact | The unit only takes requests from registered contacts. |
[!TIP] To see why a working setup stops working, check the Submissions tab first, then troubleshooting.