Skip to content

Forms Endpoint Reference

This page is the complete, exact contract for submitting to an Attri form endpoint. It exists so a developer — or an AI coding assistant building your site — can integrate a form without guessing. If you’re using an assistant like Claude, Cursor, or Copilot, paste this whole page into its context along with your form key.

POST https://form.attri.io/{formKey}

{formKey} is the short identifier shown on the form’s page in Attri (e.g. ofidsmmc). GET is not supported. Cross-origin requests are allowed (Access-Control-Allow-Origin: *; preflight OPTIONS is handled).

All three are equivalent — fields are extracted the same way:

Content typeNotes
multipart/form-dataWhat a native HTML form or FormData sends.
application/x-www-form-urlencodedStandard form encoding.
application/jsonFlat object of string values. Also accepts _turnstile as an alias for the captcha token field.

Every non-reserved field is stored as a submission field, as a string, under the name you sent. There is no fixed schema.

Fields whose names start with _ are control fields — they change behavior and are never stored:

FieldPurpose
_gotchaHoneypot. If non-empty, the submission is silently discarded (the response still reports success, so bots don’t adapt). Add it as a hidden, empty field.
_nextURL to redirect to after a successful native (non-JavaScript) submission. Ignored for AJAX submissions.
cf-turnstile-responseTurnstile captcha token. Required when the form has captcha enabled. Normally set automatically by Attri’s tracking script — see the captcha requirement below.
_attri_eidAttribution event id linking the submission to the visitor’s analytics session. Set automatically by the tracking script; don’t set it manually.

One regular field also gets special treatment: a field named exactly email is format-validated server-side on every submission.

The endpoint returns JSON or redirects depending on how the request was made:

  • AJAX mode (request has Sec-Fetch-Mode: cors — any browser fetch — or a JSON content type): responses are JSON with appropriate status codes.
  • Native mode (regular HTML form navigation): responses are 303 redirects — on success to _next, else the form’s configured redirect URL, else back to the Referer; on validation failure back to the Referer with an _errors query parameter containing URL-encoded JSON of per-field errors.
StatusBodyMeaning
200{"ok": true}Captured (AJAX mode). Also returned for honeypot-flagged spam.
303Success or validation-failure redirect (native mode).
400{"error": "Missing form key"}No form key in the URL.
403{"error": "Form is disabled"}The form exists but is turned off in Attri.
404{"error": "Form not found"}Unknown form key (or the form was deleted).
422{"ok": false, "errors": {"field": "message"}}A required field is missing/empty, or email failed format validation.
422{"ok": false, "error": "Captcha verification required"}Form has captcha enabled and no token was sent.
422{"ok": false, "error": "Captcha verification failed"}Token was sent but invalid or expired.
429{"error": "Too many submissions. Please try again later."}Rate limit: 10 submissions per minute per IP per form. Retry-After: 60 header included.

Required fields are configured per form in the Attri app, not in your markup — the server enforces them regardless of client-side validation.

The captcha constraint (read this before enabling captcha)

Section titled “The captcha constraint (read this before enabling captcha)”

If the form has Turnstile captcha enabled, the page must have Attri’s tracking snippet installed. The snippet injects the captcha widget and fills cf-turnstile-response; there is no standalone embed. A captcha-enabled form on a page without the snippet fails every submission with 422. Details and other gotchas: Spam Protection & Captcha.

The snippet also only scans for forms at page load — a form rendered later by client-side JavaScript won’t be enhanced (no interception, no captcha widget). Render Attri forms in the initial HTML.

Plain HTML (works with or without the tracking snippet):

<form action="https://form.attri.io/YOUR_FORM_KEY" method="POST">
<input type="text" name="name" />
<input type="email" name="email" required />
<textarea name="message"></textarea>
<input type="text" name="_gotcha" style="display:none" tabindex="-1" autocomplete="off" />
<button type="submit">Send</button>
</form>

curl (AJAX mode, via JSON):

Terminal window
curl -X POST https://form.attri.io/YOUR_FORM_KEY \
-H "Content-Type: application/json" \
-d '{"name": "Ada", "email": "ada@example.com", "message": "Hello"}'
# → {"ok":true}

Can I use Attri forms without any JavaScript? Yes — a plain HTML form POST works and redirects on success. You lose the inline success message, session-linked attribution, and captcha (which requires the tracking snippet).

Does this work in Next.js / React / Vue? Yes, with one constraint: the form must be in the DOM when Attri’s script loads (server-rendered or mounted at page load). Client-side-rendered forms that appear later aren’t enhanced by the script — they’ll still capture via native POST, but without interception or captcha.

What field names should I use? Any names you like. Only email is special (format-validated), and _-prefixed names are reserved control fields.

Where do submissions go? The Forms section of your Attri workspace, with attribution (referrer, campaign, device, geo) attached. Notifications go to whatever channels you’ve configured — email, Slack, or webhooks.

Is there an API to read submissions? Yes — the REST API exposes forms and their submissions, including export.