Docs/Forms/Guides/Submitting data

Submitting data

POST to a form endpoint from a browser, a server, or an agent, and what comes back.

View as Markdown

Once a form is provisioned, anything can post to its endpoint_url. This page is the contract for that endpoint.

The request

text
POST /v1/f/{form_id}

Accepted content types:

  • application/json: a flat object of field values. A non-object body is rejected with 400 invalid_body.
  • application/x-www-form-urlencoded: standard form encoding.
  • multipart/form-data without file parts. A file part returns 415 files_not_supported; there are no uploads in v1. Repeated keys collapse to a list.

A request with no Content-Type is rejected with 415 unsupported_content_type rather than being silently dropped.

curl
curl -X POST https://api.lanes.sh/v1/f/YOUR_FORM_ID \
  -H "Content-Type: application/json" \
  -d '{"email": "visitor@site.com", "message": "Hello!"}'

The response

The endpoint serves both machines and browsers, and it decides which you are from the Accept header:

  • API callers (Accept is not text/html) get 200 with a JSON receipt:

    json
    { "ok": true, "submission_id": "b7c2..." }
  • Browser form posts (Accept includes text/html) get a 303 redirect to /thanks/{form_id}, so a plain HTML form lands the visitor on a thank-you page with no JavaScript.

Fields and validation

The form's schema defines the known fields. On submit, values are split into data (keys that match the schema) and extra (everything else). Extra keys are stored, not rejected, so adding a field to your markup will not break submissions.

Required fields that are absent or empty return 422 missing_required_fields, and the response lists exactly which fields were missing.

The honeypot

Every form reserves a field named _gotcha. It should be present in your markup but hidden from humans and left empty. If it arrives filled, the submission is silently marked as spam and still returns success, so a bot learns nothing. _gotcha is stripped before storage and cannot appear in a form's schema.

html
<input type="text" name="_gotcha" style="display:none" tabindex="-1" />

Limits

GuardLimitError
Body size64 KB413 payload_too_large
Rate limit10 per minute per IP429 submission_rate_limited
Unclaimed cap25 stored submissions429 unclaimed_submission_cap

The unclaimed cap

While a form is unclaimed it stores up to 25 submissions (spam counts toward the total). At the cap, a genuine submission is refused with 429 unclaimed_submission_cap until the owner claims the form. Over-cap spam still returns success but is not stored.

What is stored

Each stored submission keeps the data and extra field values, the request origin, a salted hash of the IP (the raw IP is never stored), a truncated user agent, a spam score, and whether it has been released to the dashboard. Submissions collected while unclaimed are held until a claim releases them.

State gates

If a form has been deleted or has frozen from expiry, its endpoint returns 410. An unknown form_id returns 404 form_not_found.

Next: Integrating a form for ready-made snippets, and Securing your endpoints for origins and keys.