The Customer API uses API keys as bearer credentials. Mint keys in
the dashboard at /api-keys.
Token format
fs_<env>_<32 characters, A-Z and 0-9>
fs_live_…keys are for production traffic.fs_test_…keys are for non-destructive integration work. The split is cosmetic in v1 — both keys hit the same database. Use the prefix to keep test scripts from accidentally calling a live key.
The full token is shown once at creation. Store it in your secret manager. If you lose it, mint a new key and revoke the old one.
Sending the token
Authorization: Bearer fs_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Every authenticated endpoint requires this header. Missing or malformed
tokens return 401 with a WWW-Authenticate: Bearer header.
Scopes
Each key carries a list of <resource>:<action> scopes. The action is
read, write, or *. The wildcard *:* is allowed but should be
reserved for tightly-controlled internal integrations.
Common scope shapes:
| Goal | Scopes |
|---|---|
| Read-only reporting | forms:read, records:read, organizations:read |
| Submit records | records:write, forms:read |
| Configure webhooks | webhooks:read, webhooks:write |
| Manage members | users:read, users:write |
| Manage forms | forms:read, forms:write |
| Report automation | reports:read, reports:write |
| Full instance admin | *:* — reserve it for instance keys |
A request with a valid key but the wrong scope returns 403 forbidden
with the required scope echoed in the error message.
Key types
- Organization-scoped key (
organization_idset) — only addresses paths under/organizations/{org_id}/…where{org_id}matches the key's binding. Attempting to address a different org returns403. - Instance-scoped key (
organization_idis NULL) — can address any org, plus the instance-level surfaces (/users,/instance,POST /organizations,DELETE /organizations/{id}). Only instance owners can mint these.
Lifecycle
- The dashboard offers no edit operation for a key. To change the name, scopes, or rate limit, mint a new key and revoke the old one.
- Revocation is permanent. Revoked keys remain visible in the
dashboard for audit; subsequent requests return
401. - Optional
expires_atmakes a key auto-expire. After that timestamp, the key returns401even if it isn't explicitly revoked.
Rotating a key
- Mint a new key with the same scopes.
- Update the integration to use the new key.
- Verify the new key is in use (look at the audit log or watch the
old key's
last_used_atstop changing). - Revoke the old key.
Security notes
- We store the SHA-256 of the plaintext, never the plaintext itself.
- The plaintext is shown exactly once at creation; the dashboard then shows only the prefix.
- The
Authorizationheader is the only bearer mechanism in v1. Query-string tokens are not supported. - Constant-time hash comparison is used in the auth middleware.