Lanes Forms secures a submission endpoint on two independent axes: where the browser request came from (origin), and whether a server request carries a key (submission mode). Understanding the split is the key to choosing the right setup.
The two axes
allowed_originsgoverns browser submissions. A request that carries anOriginheader must match the allowlist.submission_authgoverns non-browser submissions (a server,curl, or an agent, none of which send anOrigin). It is eitheropen(the default) orapi_key, and is changed with aPATCHon a claimed form.
The important subtlety: allowed_origins applies in both modes. Switching to api_key mode does not loosen or tighten browser rules; it only decides what happens to requests with no Origin.
The full matrix
| Caller | open (default) | api_key |
|---|---|---|
Browser, Origin in allowlist | Allowed, no credential | Allowed, no credential |
Browser, Origin not in allowlist | 403 origin_not_allowed | 403 origin_not_allowed |
Server / curl / agent (no Origin) | Allowed, no credential | Requires lfk_ key, else 401 api_key_required |
| localhost origin (dev) | Allowed | Requires lfk_ key |
An empty allowed_origins rejects every browser origin, which is exactly what you want for a form that only your backend submits to.
Client (browser) interaction
For a public form on a website, the browser is the client and it cannot keep a secret. So the browser is authorized by its origin, which the browser sets and page scripts cannot forge.
- Keep the form in
openmode. - List the sites that may submit in
allowed_origins, for example["https://example.com"]. - Do not put any key in the page.
A key in the browser is a leaked key
Anything shipped to the browser is readable by anyone. That is why browser forms rely on origin allowlisting instead of secrets. Reserve api_key mode for server-to-server use.
Server-to-server interaction
When only your backend submits, there is no browser and no Origin header. Lock the form down:
- Set
submission_authtoapi_key. - Send the workspace key on every submit:
Authorization: Bearer lfk_.... - Optionally leave
allowed_originsempty so no browser can submit at all.
See API keys for creating and storing the key.
CORS
CORS is handled per form, not by a global wildcard:
OPTIONS /v1/f/{form_id}preflight echoes the exact requestingOrigin(never*), withAccess-Control-Allow-Methods: POST, OPTIONSandAllow-Headers: Content-Type, Authorization. A preflight with noOriginreturns400 preflight_origin_required; a disallowed one returns403 origin_not_allowed.- Every submission response for an allowed origin echoes
Access-Control-Allow-Originso page scripts can read success and error bodies. A disallowed origin is never echoed.
Built-in defenses
Beyond origins and keys, every form gets these for free:
- Honeypot: the
_gotchafield silently absorbs bots. See Submitting. - Rate limiting: 10 submissions per minute per IP.
- Size cap: 64 KB per request.
- Disposable-email rejection at provisioning time.
- Unclaimed cap: 25 submissions before a claim is required.
IP addresses are stored only as a salted hash, never in the raw.
Next: Errors for the full error vocabulary.