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.

CapabilityService keyPerson tierOwner's person keyMember's person keyWhat holding it allows
decision:resolvenoyesyesyesAnswer open product decisions and questions a paused coding agent is waiting on, which can move a theme into active development.
data:readyesyesyesnoRead the org's signals, themes, hypotheses, decisions and documents through the API.
signal:ingestyesyesyesnoSend signals into Belora through the API, which start the same clustering pipeline as any other source.
exploration:writeyesyesyesnoAttach 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:createnoyesyesnoPropose a structural change to the org's canon, a judgment a human or the reviewer then decides.
job:claimnoyesyesnoClaim a build job and commit to doing it, which starts the lease a coding agent works under.
webhook:manageyesyesyesnoRegister, 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.
TierRequests per minuteBurst
service600600
person120120

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.

OperationRouteCapability
decisions.verdictPOST /v1/decisions/{id}/verdictdecision:resolve
conformance.decisionPOST /v1/conformance/decisiondecision:resolve
sessions.confirmPOST /v1/sessions/{id}/commit/confirmdecision:resolve
sessions.dismissPOST /v1/sessions/{id}/commit/dismissdecision:resolve
jobs.claimPOST /v1/jobs/{id}/claimjob:claim
jobs.recapPOST /v1/jobs/{id}/recapjob:claim
jobs.releasePOST /v1/jobs/{id}/releasejob:claim
jobs.pausePOST /v1/jobs/{id}/pausejob:claim
jobs.claimLinkedPOST /v1/decisions/{id}/jobs/claimjob: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

orgobject
org.idstring
org.slugstring
keyobject
key.idstring
key.tier"service" | "person"
key.labelstring
key.purpose"production" | "conformance"conformance marks a key whose every write is fenced from production memory and swept after a day
personobject | 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.