API overview
Base URL, authentication with submission and management keys, and how errors are shaped.
The FormFlow API is a plain REST API served from Cloudflare’s edge.
https://api.formflow.cc
Two surfaces share that host:
- Ingest —
POST /v1/f/:slug, the public endpoint your forms post to. See Submission ingest. - Management —
/v1/forms,/v1/webhooks,/v1/api-keys, … — everything the console does, available to scripts and CI.
The OpenAPI 3.1 schema is published at
/openapi, and GET /v1/health answers with a
plain status object if you want something to point a monitor at.
Authentication
API keys are created in the console under API keys. The full key is shown exactly once at creation — store it in your secret manager; only a prefix is kept on our side. Keys can be revoked at any time.
There are two key types, distinguishable by prefix:
| Prefix | Type | Use it for |
|---|---|---|
ff_sub_ | Submission key | Server-side posts to the ingest endpoint. Optional — public forms accept anonymous posts and rely on the anti-spam layer. |
ff_mgmt_ | Management key | The management API: forms, submissions, webhooks, keys, usage. |
Send the key as a bearer token:
curl https://api.formflow.cc/v1/forms \
-H "Authorization: Bearer ff_mgmt_..."
Keep the two concerns separate: a submission key embedded in a backend that posts forms can’t read your data or change configuration, and you can rotate each independently.
Errors
Every error is an RFC 7807
application/problem+json document:
{
"type": "https://formflow.cc/problems/rate-limited",
"title": "Too many submissions",
"status": 429,
"detail": "Rate limit exceeded. Try again shortly."
}
type is a stable URL-shaped identifier — branch on it (or on the trailing slug)
rather than on title/detail, which are human-readable and may change. Some
problems carry extra fields, e.g. quota-exceeded includes your plan’s limit.
Codes you’ll meet most often:
| Status | Problem | When |
|---|---|---|
400 | bad-body | Body isn’t parseable as form data or JSON. |
402 | quota-exceeded | Monthly submission quota used up. |
403 | form-not-live | Form is paused or draft. |
403 | invalid-key | Bearer key is invalid or revoked. |
403 | turnstile-failed | Turnstile is enabled and the token didn’t verify. |
404 | form-not-found | No form with that slug. |
413 | file-too-large | An attachment exceeds your plan’s per-file limit. |
429 | rate-limited | Per-IP rate limit hit (ingest), or key minting cap. |
Idempotency
POST /v1/f/:slug accepts an Idempotency-Key header. Replaying the same key for
the same form within 24 hours returns the original submission id instead of
creating a duplicate — see Submission ingest
for the exact semantics.
Conventions
- Timestamps are ISO-8601 UTC strings:
2026-06-10T10:23:00.000Z. - IDs are prefixed strings:
frm_…(forms),sub_…(submissions),wh_…(webhooks),whd_…(webhook deliveries). - Responses are
application/json; list endpoints wrap rows in{ "data": [...] }.