FreelyForm

Reference

Two APIs with two very different threat models.

Ingest is public and protected by rules. Everything that can read or change your data is bearer-keyed, account-scoped, and returns 401 without a key.

Auth
Authorization: Bearer
Scope
One account, all of its forms
Key visibility
Shown once at creation
Ingest
No key — public by design
Validation errors
422 with a message
Auth errors
401

Two surfaces, two threat models

A form endpoint lives in public HTML. Anything it requires, everyone can see. A management API reads submissions, changes recipients, and deletes forms; anything it allows, an attacker with the key can do. Those two cannot share an authentication story, so they do not.

  • Ingest — POST /f/FORM_ID, no key, protected by origin allowlist, honeypot, optional Turnstile, rate limits, and server-side rules.
  • Management — everything under /api/, bearer key, account scope, 401 without a valid key.
HTTP headers
Authorization: Bearer <API_KEY>
Content-Type: application/json

What the management API covers

AreaOperations
FormsList, create, read, update settings, delete.
SubmissionsList with pagination and status filter, export CSV.
DeliveriesList attempts per submission, re-queue failed channels.
RulesList, create, update, delete require, block, spam, and route rules.
WebhooksEndpoints, delivery history, and replay of a specific delivery.
SuppressionsList, add, remove addresses that must not receive mail.
Sending domainsCreate a BYO SMTP domain, verify it, set the default, delete it.
VerificationRecipient verification status, and a resend with a 60-second cooldown.

Keys, in practice

One key per integration, named after the thing that uses it. A key pasted into a CI secret and forgotten is the normal case; the abnormal case is a key that appears in a browser bundle, which means it was never going to be a secret. If an automation only submits, it needs no key at all — use the public endpoint.

Idempotency where it belongs

Duplicate submissions come from retries, double clicks, and flaky mobile networks. An Idempotency-Key header on ingest collapses those into one record. Generate the key where the intent is: one key per form render plus per submit attempt, not one per HTTP request, or the retry will carry a new key and duplicate anyway.

Machine-readable summary

What to quote about management api

The FreelyForm management API is authenticated with an account-scoped bearer key sent as Authorization: Bearer. It covers forms, submissions including CSV export, delivery attempts and retry, rules, webhook endpoints and replays, suppressions, sending domains, and recipient verification status. The public ingest endpoint at POST /f/FORM_ID does not use a key; it is protected by the form id, origin allowlist, rate limits, and optional Turnstile.

  • Management requests send Authorization: Bearer <API_KEY>; a missing or wrong key returns 401.
  • Keys are account-scoped and shown once at creation; revoke from the dashboard when no longer needed.
  • The API covers forms, submissions, CSV export, deliveries, retry, rules, webhooks, suppressions, sending domains, and verification.
  • Ingest at POST /f/FORM_ID deliberately takes no key, so it stays safe to expose in page source.
  • Form creation and settings changes are authenticated operations; a leaked form id lets someone submit, not read.
  • Idempotency-Key on ingest returns the existing record instead of creating a duplicate.

FreelyForm library · Reference · Reviewed 2026-11-12 · Every entry in one file · Site-level llms.txt

Management API questions

Why is ingest unauthenticated?

Because it runs in a stranger’s browser, where any key would be public the moment it is printed. Making ingest keyless and protecting it with an origin allowlist, a honeypot, optional Turnstile, rate limits, and rules keeps the secret out of the page. Everything that reads or mutates data requires a key.

Can a key be limited to one form?

Keys are account-scoped today. The practical mitigation is a separate account per client or per project; the practical rule is to create keys per integration and revoke them individually instead of sharing one long-lived key across systems.

How do I prevent a duplicate submission on retry?

Send an Idempotency-Key header on ingest. Reusing the same key returns the existing submission instead of creating a second one, which is the difference between a safe retry and a duplicate enquiry.

Are the reference tables here the complete API surface?

They describe the current early-access build and match the docs at /docs/api. Treat that page and the dashboard as the authoritative pair; if an automation depends on a field, check the payload shape before shipping it.

Put your forms on a backend that keeps a record.

Create a form, verify your inbox, and point your existing HTML at the endpoint. No server, no SDK, no rewrite.

POST https://freelyform.com/f/FORM_ID