Form ID

How it works

Every check a submission goes through, in order, and what each one can return.

Each post to /f/<formId> runs through the same pipeline. Cheap checks run first, so a flood or an unknown form costs almost nothing. The first check that fails decides the response.

The checks a submission goes through, in order

Before the body is read

  1. 1Flood guard600 a minute per IP, across every form429 rate_limited
  2. 2Form lookupUnknown, deleted or disabled forms404 not_found
  3. 3Form rate limitsThe form's preset, per visitor and per form429 rate_limited
  4. 4PausedThe form is paused in the dashboard403 form_paused

Reading the post

  1. 5Parseurlencoded, multipart or JSON, UTF-8, size limits400, 413, 415 or 422
  2. 6API keyOnly when an Authorization header is sent403 forbidden
  3. 7Allowed domainsOrigin or Referer against the form's domainsSpam, or 403
  4. 8Human checkTurnstile token, when the check is on403, or a check page

Judging it

  1. 9ValidateAgainst the published fields, if any422 validation_failed
  2. 10ScoreHoneypot, timing, links, keywords, duplicatesSpam at 5 or more
  3. 11IdempotencyA repeated key replays the first answerReplay, 409 or 422
  4. 12Monthly usageFar over the workspace limit429 quota_exceeded

Stored

Submission saved

201 for code, 303 for browsers

  • WebhooksSigned, retried for about 3 days
  • Notification emailTo the form's recipients
  • Dashboard and APILive, right away

Spam is stored too, and answered like a success, but sends nothing.

What each stage does

Rate limits before anything else

Rate limits run before the body is read. The flood guard (600 a minute per IP) runs even before the form lookup, so a flood never reaches the database. The form's own rules run right after the lookup. Rejected requests don't use up the allowance. Exact numbers are on Rate limits.

Parsing

The body is parsed as urlencoded, multipart or JSON. Text is decoded as UTF-8, normalized to NFC, CRLF becomes LF and control characters are removed. Names that start with _ are reserved instructions, such as _redirect and _subject, and are never stored as answers. See HTML forms.

Who sent it

An Authorization: Bearer header is optional. A secret key (ik_sk_) with the submissions:write scope skips the allowed-domains check and the Ikiform human check. A publishable key (ik_pub_) only works from the domains listed on the key. Without a key, the post is judged by its Origin.

Spam

Spam is never an error. A post that fails the allowed-domains policy in spam mode, or scores 5 or more, is stored with status spam and gets the same success response as a real one, so bots learn nothing. Spam sends no webhooks or emails and doesn't count toward usage. See Spam protection.

Store and deliver

The submission, its webhook deliveries and its notification email are written in one database batch, then handed to a queue. Webhook delivery and email never slow down the response, and a submission is never stored without its deliveries.

JSON or a redirect

The same endpoint answers browsers with pages and code with JSON. Ikiform decides per request:

How Ikiform picks a JSON answer or a redirect, checked from top to bottom
  1. 1Accept includes application/jsonJSON
  2. 2The body is JSONJSON
  3. 3Sec-Fetch-Mode: navigateRedirect or page
  4. 4Any other Sec-Fetch-Mode, such as corsJSON
  5. 5No Sec-Fetch-Mode, Accept includes text/htmlRedirect or page
  6. 6Anything else, such as curlJSON

The first rule that matches wins.

In JSON mode success is 201 { "ok": true, "id": "sub_..." }. In page mode success is a 303 redirect, and errors render a hosted error page that lists field problems by label.

After a submission is stored

WhatWhenDetails
WebhooksSeconds later, retried for about 3 daysWebhooks
Notification emailSeconds later, if notifications are on for the formCapped at 50 emails per form per hour (10 for workspaces younger than 7 days), then one hourly digest
DashboardImmediately, liveSpam shows under the spam filter
REST APIImmediatelyREST API
Forms whose submissions contain password, card or seed-phrase style field names are flagged and their notification emails held for review. Don't collect credentials with a form backend.

Ids

IdLooks likeNotes
Formfrm_xxxxxxxxxxxxxxxxfrm_ plus 16 random characters. Public: it is in your form's action URL.
Submissionsub_01k7v2c9t3e8m5q4x6z0a1b2c3sub_ plus 26 characters, ordered by time.
Webhook deliverydlv_...Sent as webhook-id; stays the same across retries.
Requestreq_...Every response has an X-Request-Id header. Quote it when you contact support.
{ "ok": true, "id": "sub_01k7v2c9t3e8m5q4x6z0a1b2c3" }