Authentication & scopes
#Two ways to authenticate
Every /v1/* route accepts one of two credentials:
| Credential | Header | Who uses it | Resolves to |
|---|---|---|---|
| API key | Authorization: Bearer afb_live_... |
Servers, scripts, the SDK | { workspaceId, keyId, scopes, actor: "api_key" } |
| Session cookie | Cookie: afb_session=... |
The console web app | { userId, workspaceId, role, actor: "session" } |
Routes under /v1/console/* are session-only (they exist for the console's own UI); every other route accepts either. A session request resolves its workspace from the X-Workspace-Id header, or your first workspace if that header is absent.
#Scopes
An API key is issued with one or more scopes, checked per route. There is no partial-scope wildcard — request exactly the scopes an integration needs:
| Scope | Grants |
|---|---|
runs:read |
List/get runs, steps, output, artifacts, live events |
runs:write |
Create runs; submit input/approve/review/cancel |
agents:read |
List/get agents and their versions; list presets and models |
agents:write |
Create agents and agent versions; manage evals |
files:write |
Upload files |
feedback:write |
Submit run feedback |
webhooks:manage |
Manage webhook subscriptions and save-output connections |
usage:read |
Read usage totals and balance |
keys:manage |
Create/list/revoke API keys |
A request with a valid key but a missing scope gets 403 { "error": { "code": "forbidden", ... } }, not 401 — the key itself is valid, it just isn't allowed to do that particular thing. A session request is instead checked against the caller's workspace role (owner/admin/member) for the closest matching action; a role that can't perform it also gets 403.
#Creating and revoking keys
# Create
curl -X POST "https://api.agentflowbind.com/v1/api-keys" \
-H "Authorization: Bearer afb_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "name": "Nightly reconciliation job", "scopes": ["runs:read", "runs:write", "files:write"] }'
# List (plaintext key is never returned again after creation)
curl "https://api.agentflowbind.com/v1/api-keys" -H "Authorization: Bearer afb_live_xxxxxxxxxxxx"
# Revoke
curl -X DELETE "https://api.agentflowbind.com/v1/api-keys/8f1e2b0a-...-c9d3" \
-H "Authorization: Bearer afb_live_xxxxxxxxxxxx"DELETE returns 204 No Content; revoking an already-revoked or unknown key id returns 404.
#Rate limits
Every key-authenticated request is rate-limited per minute, tracked per key. The limit itself comes from the calling workspace's plan (entitlements.apiRateLimitPerMin) — indicative Phase 0 values:
| Plan | Requests/min |
|---|---|
| Free trial | 30 |
| Starter | 60 |
| Business | 300 |
| Scale | 1,200 |
A request over the limit gets:
HTTP/1.1 429 Too Many Requests
Retry-After: 37
{ "error": { "code": "rate_limited", "message": "Rate limit exceeded" } }Retry-After is in seconds — back off at least that long before retrying. See Errors & rate limits for the full error-code reference, including the separate, lower rate limits on /v1/console/auth/sign-in and /sign-up.
#Unverified accounts
A session-authenticated (console) user whose email isn't verified yet can still use runs:write for a limited number of runs (AFB_UNVERIFIED_RUN_LIMIT, default 3) before POST /v1/runs starts returning:
{ "error": { "code": "auth.email_verification_required", "message": "Verify your email to create more runs (the unverified limit of 3 has been reached)" } }at 403. This limit only applies to session callers — an API key request is never subject to it.