Skip to main content

Submitting and errors

Submitting​

POST /api/v1/forms/frm_…/submissions
Content-Type: application/json
{
"contact": { "email": "[email protected]", "name": "Sam Taylor" },
"subject": "Can't sign in",
"fields": { "message": "I can't sign in since this morning." },
"locale": "en"
}
FieldRequiredWhat
contact.emailyesThe requester's email address, at most 254 characters.
contact.nameAt most 200 characters. Ignored unless the form asks for a name.
subjectAt most 300 characters. Ignored unless the form asks for a subject.
fieldsAnswers by field key. Unknown keys and hidden (conditional) fields are dropped. File fields can't be sent here.
localeThe requester's language, like en or no.
metaServer 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.
turnstileTokenBrowser mode: the Turnstile widget's token.
startTokenBrowser mode: startToken from the definition.
_hpBrowser mode: the honeypot field. Leave it empty.

The body may be at most 64 kB.

[!TIP] An Idempotency-Key header (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.

ModeWhen
Server modeThe request carries X-Desk-Signature, X-Desk-Timestamp or Authorization: Bearer.
Browser modeOtherwise.

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:

SettingDefault
Requests per address per window5 per 600 seconds
Hourly cap for the whole form200

[!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.

StatusCodeMeaning
404form_not_foundNo form with this id.
403form_disabledThe form is turned off.
503form_pausedThe form is paused after its hourly cap.
403server_mode_disabled, browser_mode_disabledThat mode isn't turned on for the form.
401signature_missingServer mode without both X-Desk-Timestamp and X-Desk-Signature.
401timestamp_invalidThe timestamp isn't Unix seconds.
401timestamp_expiredThe timestamp is more than five minutes off.
401signature_invalidThe signature doesn't match any active secret.
401signature_replayedThis exact signed request was already received. Sign each request anew; to retry safely, send an Idempotency-Key.
401bearer_not_allowed, invalid_keyA bearer key on a form that doesn't accept them, or an unknown key.
403ip_not_allowedThe sender's address isn't on the form's allowlist.
403origin_not_allowedThe website isn't under Allowed websites.
400spam_detectedThe honeypot was filled in.
400start_token_invalid, start_token_expired, start_token_usedThe start token is missing, not from this form, older than 24 hours, or already used 5 times. Reload the form.
400too_fastSent sooner than the minimum fill time.
403turnstile_missing, turnstile_failedNo Turnstile token, or Cloudflare rejected it.
429rate_limitedToo many submissions from this address.
422validation_failedThe body or a field is invalid; details lists { path, message } per problem, like fields.message.
403sign_in_requiredThe unit only takes requests from signed-in contacts; use server mode or the portal.
403not_a_known_contactThe unit only takes requests from registered contacts.

[!TIP] To see why a working setup stops working, check the Submissions tab first, then troubleshooting.