Keys and identity
Every call carries a bearer key. The key decides who Belora believes is calling and what that caller may do.
Format
A key is the token blr_<orgSlug>_<keyId>.<secret>. The secret is 43 URL-safe characters and is shown once, at creation. Belora keeps a hash of it and the prefix through the dot, which is all the keys list shows. A missing, malformed, unknown, revoked or expired key is the same 401 unauthorized.
Proved by keys.format auth.whoami
Tiers, and the role behind a person key
A service key belongs to the org. It reads, relays what people said, adds evidence to hypotheses and manages webhooks. A person key is bound to one verified person and acts as that person. It holds the intersection of the person tier's capabilities and what its person's role in the org grants, worked out on every call. A member's person key therefore holds less than an owner's, and GET /v1/whoami lists what the key holds right now. An org can override what a role grants; the role columns show the defaults.
| Capability | Service key | Person tier | Owner's person key | Member's person key | What holding it allows |
|---|
| decision:resolve | no | yes | yes | yes | Answer open product decisions and questions a paused coding agent is waiting on, which can move a theme into active development. |
| data:read | yes | yes | yes | no | Read the org's signals, themes, hypotheses, decisions and documents through the API. |
| signal:ingest | yes | yes | yes | no | Send signals into Belora through the API, which start the same clustering pipeline as any other source. |
| exploration:write | yes | yes | yes | no | Attach findings, with a falsifier, to the hypotheses an exploration is gathering evidence for, and take part in a session: open one, speak as its agent, record context, run its tools, stage a commit. |
| proposal:create | no | yes | yes | no | Propose a structural change to the org's canon, a judgment a human or the reviewer then decides. |
| job:claim | no | yes | yes | no | Claim a build job and commit to doing it, which starts the lease a coding agent works under. |
| webhook:manage | yes | yes | yes | no | Register, list, disable, enable and replay the webhook endpoints that receive the org's signed events. Revoking the key that registered an endpoint disables it until a live key enables it again. |
| Tier | Requests per minute | Burst |
|---|
| service | 600 | 600 |
| person | 120 | 120 |
A person key stops resolving, as 401 unauthorized, when its person goes inactive, leaves the org, or has no verified Belora login link.
Stated, not run by the suite: keys.tiers
Why an unbound key never commits judgment
A verdict, a proposal and a job claim each commit the org to something, so each is attributed to a verified person who holds the capability for it. A service key has no person, so none of the judgment capabilities is in its set: calling any person-tier operation answers 403 key_lacks_capability before the handler runs and nothing is written but the key's last-used time. key_cannot_commit_judgment (403) is a second guard behind it.
| Operation | Route | Capability |
|---|
| decisions.verdict | POST /v1/decisions/{id}/verdict | decision:resolve |
| conformance.decision | POST /v1/conformance/decision | decision:resolve |
| sessions.confirm | POST /v1/sessions/{id}/commit/confirm | decision:resolve |
| sessions.dismiss | POST /v1/sessions/{id}/commit/dismiss | decision:resolve |
| jobs.claim | POST /v1/jobs/{id}/claim | job:claim |
| jobs.recap | POST /v1/jobs/{id}/recap | job:claim |
| jobs.release | POST /v1/jobs/{id}/release | job:claim |
| jobs.pause | POST /v1/jobs/{id}/pause | job:claim |
| jobs.claimLinked | POST /v1/decisions/{id}/jobs/claim | job:claim |
A person key is minted in the console by the person it is bound to. Minting for anyone else is refused as person_mismatch, and for a person with no verified link as person_not_verified; these are console refusals, not /v1 responses.
Stated, not run by the suite: keys.judgment
Lifecycle
A key may carry an expiresAt. There is no rotate operation: create the new key, switch your clients, then revoke the old one. Revoking is immediate and disables the webhook endpoints the key created.
GET/v1/whoami
Describe the calling key, its org and its person
whoamiservice keydata:readsuccess 200mcp whoami
Response
| org | object | |
| org.id | string | |
| org.slug | string | |
| key | object | |
| key.id | string | |
| key.tier | "service" | "person" | |
| key.label | string | |
| key.purpose | "production" | "conformance" | conformance marks a key whose every write is fenced from production memory and swept after a day |
| person | object | null | |
| capabilities | "decision:resolve" | "document:approve" | "connector:attach" | "connector:detach" | "runner:manage" | "member:manage" | "key:manage" | "billing:view" | "billing:manage" | "data:read" | "signal:ingest" | "exploration:write" | "proposal:create" | "job:claim" | "webhook:manage"[] | |
Errors: 401 unauthorized429 rate_limited
Proved by auth.whoami webhooks.key_lapsed
Proved by
Every claim on this page is one of these checks; those marked stated are documented behaviour the suite cannot drive. Run them against your org:
npx @belora/conformance --key $BELORA_CONFORMANCE_KEY --api $API --mcp https://mcp.belora.dev/mcp
- auth.whoami A key identifies itself
GET /v1/whoami with a bearer key answers its org, tier and capabilities; a missing or wrong key answers 401 unauthorized in the error envelope.
- keys.format A key token has the published shape
A minted token matches blr_<orgSlug>_<keyId>.<secret> with a secret of the published length, and the list shows only the prefix through the dot.
- keys.tiers Each tier holds exactly its capabilitiesstated, not run by the suite
GET /v1/whoami lists for a service key exactly the capabilities the capability table gives the service tier, and for a person key the person tier's capabilities narrowed to those its person's role also grants.
- keys.judgment A service key never commits judgmentstated, not run by the suite
A service key calling decisions.verdict, jobs.claim or any person-tier operation answers 403 key_lacks_capability before the handler runs, and nothing is written but the key's last-used time.
- webhooks.key_lapsed An endpoint stops with its keystated, not run by the suite
Revoking the creating key disables its endpoints with disabledReason key_revoked; a key that expires or loses webhook:manage disables them with key_lapsed at the next event; a live key brings them back with POST /v1/webhooks/{id}/enable.