Two credentials, one account

Authentication

A session cookie from the email code flow drives the console and key management. A per-machine key drives agents and hosts. Both resolve to the same account. A key may carry a capability allow-list; a session always holds every capability.

Session cookie

POST /v1/auth/request-otp with your email, then POST /v1/auth/verify-otp with the six digit code. The response sets afa_jwt: HttpOnly, SameSite=Strict, sent only over HTTPS, valid for eight hours. Five wrong codes lock the attempt; a code expires after ten minutes. POST /v1/auth/signout clears it.

Check: GET /v1/me returns 401 missing_token without the cookie and your account with it.

API key

Created in the console or with POST /v1/me/api-keys under the session cookie. The token reads afa-beta-<26 characters>-<4-character check>, is returned once, and only its SHA-256 is stored. The response also carries key_prefix and format, so a client can recognise and validate one without matching on prose. It expires 30 days after issue; POST /v1/me/api-keys/{key_id}/rotate issues a fresh token and revokes the old one. Send it as X-API-Key. Events written under a key carry that key's machine id.

curl -sS "https://api.afaprotocol.com/v1/me" -H "X-API-Key: afa-beta-EXAMPLE-e4qs"

Check: the same call with a revoked key returns 401 invalid_or_expired_api_key.

Bearer form

The MCP endpoint accepts the same key as Authorization: Bearer afa-beta-... in addition to X-API-Key. The REST routes read X-API-Key.

Check: call GET /v1/me with each header form. A 401 names the form this deployment does not read.

Capability scopes

A key created with scopes can call only the operations that map to a listed capability. Anywhere else it is refused with 403 and the body {"error": "scope_missing", "required": "<capability>"}. A path nobody has classified refuses a limited key as well, rather than admitting it by omission. Each operation in the reference names the capability it needs.

CapabilityWhat it covers
events:writeAppend events, singly or in a batch, and write settlement blocks.
events:readRead, search and stream events; graphs, machines, blocks and exports.
grantsSub-agent grants: create, list, status, use, revoke.
delegationOrdered delegation tokens and machine-to-machine grants.
dedupDedup checks, membership proofs, cache lookup and counters.
verifyExported record verification, the block chain verifier and the remote tools.
notificationsThe dispatch log, preferences, test sends and the alert feed.
webhooksEndpoint registration.
usageUsage counts and exports.
keysAPI keys and machine signing identities.
governancePolicy test, chain heal and checkpoint advice.
anchorsAnchor receipts and the anchor run.
surfacesThe surfaces listing.
healthReserved; the health probe itself needs no key.
orgsThe team model, when a build serves it.
adminReserved for a route this build does not serve.

Check: create a key with scopes: ["events:read"] and call POST /v1/events: 403 with required events:write. The same call with an unlimited key succeeds.

Precedence and limits

RuleWhat it means for you
Key header wins over cookieA request carrying both is treated as the key's machine.
Keys cannot mint keysCreate, rotate and revoke need the cookie; a key gets 403.
Ten active keys per accountThe eleventh create returns 409 max_keys_reached.
Scopes are optionalNo scopes means every capability, which every key issued before scopes existed has.
Rate ceilings100 events per minute per key on batch sync; 10,000 events per account per day.