FormFlow
EN
Start free
Docs/ API/ Submission ingest

Submission ingest

POST /v1/f/:slug — content types, file uploads, idempotency, response shapes and quota behaviour.

POST https://api.formflow.cc/v1/f/:slug

The endpoint your forms post to. It is public and CORS-open (Access-Control-Allow-Origin: *), so it works from any site without a proxy. Posting with a ff_sub_ bearer key is optional — if you send one, it must belong to the form’s account, which lets you lock server-to-server pipelines to a revocable credential.

Content types

Three request body formats are accepted:

Content-TypeNotes
application/x-www-form-urlencodedWhat a plain HTML <form> sends.
multipart/form-dataRequired for file uploads.
application/jsonFor programmatic posts. Non-string values are stored JSON-stringified.

Field names are stored as-is, except control fields (the form’s honeypot field and cf-turnstile-response), which are stripped from the stored payload.

# Plain form post
curl https://api.formflow.cc/v1/f/contact-form \
  -d "email=ada@example.com" \
  -d "message=Hello there"

# JSON with an idempotency key
curl https://api.formflow.cc/v1/f/contact-form \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 5d1f8c1e-7c70-4a4e-9e57-2f6b1f3a9b0d" \
  -d '{"email":"ada@example.com","message":"Hello there"}'

File uploads

Send files as multipart/form-data parts. They’re streamed into R2 storage and listed on the submission (the console and webhook payloads link to them).

curl https://api.formflow.cc/v1/f/job-applications \
  -F "email=ada@example.com" \
  -F "resume=@cv.pdf"

Per-file size limits come from your plan (e.g. 25 MB on Pro, 50 MB on Team; the free plan doesn’t include uploads). Oversized files are rejected up front with 413 file-too-large; plans without uploads get 413 uploads-not-allowed. Submissions caught by the spam layer never store their files.

Idempotency

Pass an Idempotency-Key header (any unique string — a UUID is ideal). Keys are scoped per form and remembered for 24 hours. Replaying a known key skips processing entirely and returns:

{ "id": "sub_8f3k2j", "status": "received", "idempotent_replay": true }

This makes client retries (flaky mobile networks, double-clicked submit buttons) safe: at most one submission is created.

Responses

Created — 201

{
  "id": "sub_8f3k2j",
  "received_at": "2026-06-10T10:23:00.000Z",
  "status": "received",
  "files": 1
}

Caught as spam — 200

{ "ok": true, "status": "accepted" }

Honeypot hits get a deliberately benign success response so bots learn nothing — see Anti-spam. The submission is stored with status: "spam" and triggers no emails, webhooks or file storage.

Errors are application/problem+json (overview):

StatusProblemWhen
400bad-bodyBody couldn’t be parsed as form data or JSON.
402quota-exceededMonthly submission quota used up (see below).
403form-not-liveForm is paused or draft.
403invalid-keyA ff_sub_ key was sent but is invalid, revoked, or belongs to another account.
403turnstile-failedTurnstile enabled, token missing or invalid.
404form-not-foundNo form with this slug.
413file-too-large / uploads-not-allowedAttachment limits, per plan.
429rate-limitedPer-IP per-form rate limit (default 60 / 10 min).

Quota — 402

Each plan includes a monthly submission allowance, counted per account over the calendar month (UTC). Once it’s used, ingest responds:

{
  "type": "https://formflow.cc/problems/quota-exceeded",
  "title": "Monthly quota exceeded",
  "status": 402,
  "detail": "This account has used its monthly submission quota. Upgrade the plan to keep receiving submissions.",
  "limit": 500
}

The check runs before any parsing or storage work, so over-quota posts are cheap to reject and nothing is partially stored. Spam-flagged submissions don’t count toward the quota. Current usage is always visible in the console and at GET /v1/usage.

Last updated: June 10, 2026