BBramaDocs

Brama HTTP API reference

Discovery and subscriptions

Four paths answer what a caller may ask for and what it owns: the model catalog, the alias listing, the subscription pool and plan usage. A fifth signs one subscription in for the authenticated account. All of them require a bearer (transport and identity).

GET /v1/models

Public catalog metadata combined with what the caller can execute: account discovery marks the models executable by that account's stored keys, and signed agent discovery includes the shared subscription pool, which serves every caller. Captured shape:

{"data":[{"id":"<provider>/<model>","object":"model","owned_by":"<provider>"}, ...]}

A declared alias the caller may name but that cannot serve is listed with available: false and an unavailable_reason; it is not omitted from the answer. The reason is a fact about this gateway's configuration, so it is shown to every caller. See an alias that cannot serve says so.

Each model also carries what it produces and how its weights were published: kind (text, image, video or audio), output_modalities, open_weights (null where the catalogue said nothing) and the categories the operator declared. The listing narrows on all four — ?kind=, ?weights=open|closed, ?category=, ?provider= — and any other query member is refused by name: `limit` is not a model filter; narrow by kind, weights, category or provider. An unreadable value is refused in the same way, so a filter never silently widens to the whole catalogue.

GET /v1/categories

Every model category the operator declared, the rule each one carries, and how many catalogue models it currently holds. The count is the check on the declaration: a rule that matches nothing reads 0 rather than looking like a working facet. Captured shape:

{"object":"list",
 "categories":[
  {"category":"uncensored","providers":["abliteration-ai"],"routes":[],
   "terms":["uncensored","abliterated"],"models":24}],
 "registry":"~/.config/brama/inference-routes.json",
 "degraded":false}

A deployment whose operator declared nothing answers an empty list, and degraded is true when the public catalogue could not be read, which is when the counts are unknown rather than zero.

GET /v1/aliases

Every alias this gateway declares — the compiled-in contract plus every operator route — with the state the gateway would put it in right now and the one route it resolves to. Captured shape:

{"object":"list",
 "unserviceable":1,
 "aliases":[
  {"alias":"weles","state":"serving","route":"best",
   "reason":null,"required":true,"authorized":true},
  {"alias":"ox-alpha","state":"capability_absent",
   "route":"openrouter/stealth/ox-alpha",
   "reason":"alias `ox-alpha` routes to openrouter/stealth/ox-alpha but this gateway holds no provider credential for openrouter; issue the provider capability on this host or point the alias at a route it can reach, such as `best`",
   "required":false,"authorized":true}]}

A row is exactly {"alias", "state", "route", "reason", "required", "authorized", "last_answer", "concurrency"}. concurrency is how many calls at once this gateway has seen the alias carry since it started, or null before its first request: {"carried", "refused_beside", "in_flight"}. A call counts in flight from routing until its provider answers (a buffered answer, or a stream's commit). carried is the most calls in flight when one was answered, lowered by a capacity refusal (provider_rate_limited or subscription_unavailable) to the calls still in flight beside it, and absent until the alias has answered once; refused_beside is that count at the newest capacity refusal, absent when there was none; in_flight is the calls unanswered now. A consumer that fans calls out to one alias reads its allowance here instead of being given a worker count. The listing's top-level measured_concurrency carries the same record for every name the gateway has been asked for, keyed by that name, including selectors such as best that are not declared aliases. last_answer is the newest answer the alias got from what it routes to, or null before its first request: {"answered", "refusal", "message", "at"}, where refusal is the contract code (provider_quota_exhausted, …) and message the provider's sentence when it did not answer, and at is when that answer was first seen. A declared, credentialed route whose provider refuses (an account without credits) has answered false and counts in unserviceable. route is the single destination the registry maps the alias to, so a row states one route and never a list. state is one of serving, no_route, capability_absent or routes_file_invalid; reason is null for a serving alias and otherwise the sentence a refusal would carry; required says the alias is part of the compiled-in contract; authorized says the presenting bearer may name it. A completion naming an alias whose state is not serving is refused with 503 and code alias_unserviceable, carrying that same sentence as its message and the row's state and route in details.

The same listing on the terminal is brama aliases, which prints alias, state and route. brama routes migrate is what moves an existing registry document to the shape this listing reads.

The subscription pool

One read and one write on /v1/subscription-pool, for three audiences. Which subscriptions exist and who owns them was previously asked eleven ways — an agent-signed lifecycle, an account lifecycle, an administered lifecycle, a console pool listing and a CLI leaf — and each answered the same question from the same vault declaration. This capability answers it once and narrows the answer by the identity the caller proved (entitlement), which is where ownership already lives.

The scope is read off the proof, in this order:

  • a caller presenting the HMAC trio is that agent, and is held to the signature even when its bearer would also pass for something wider;
  • a verified Wisent user session is that account, whose pool is keyed on the session's own user id and never on a caller-supplied identifier;
  • a brama-desktop identity with no model allowlist is this deployment: every account it holds, whichever agent owns it, plus the accounts only the usage ledger still remembers.

Anything else is 403 forbidden; a request with no bearer at all is 401 unauthorized. A model-scoped bearer reaches this path and plan usage; together with /v1/models those are the only paths outside inference it may reach at all.

Reading the pool

GET answers one document for every audience: {"ok", "observed_at_ms", "scope", "errors", "accounts", "subscriptions"}. accounts counts provider accounts rather than members, because the two are different numbers and reading one as the other is how a deployment holding five accounts reads as fifteen: it carries total, per_provider — one list of account coordinates per provider — plus members_without_account, the members whose own record states no account, and inactive_members, the retired or disabled members, in the pool but no account of its count, and malformed_members, rows the pool holds without the id or provider every member carries, shown as written; ids the vault no longer lists and only the usage ledger remembers are no members at all and stand in the document's own remembered list. Two members naming one account are one account. An account is read from the member itself — the brama:account: tag on its vault item, or that item's account_ref — and from nothing else. A member's id, its label and the login row it signs in through are names, not accounts: one Google login can back both a Claude Code and a Codex subscription of one person, so counting by login reports two accounts as one. A member that records no account is listed here rather than counted, and brama subscription attribute <provider> records it from that member's own grant.

accounts.capacity states what each of those accounts has left, one object per account the rows attribute, read across every member that carries it: provider, account, members, usable_members (nothing in the ledger stands against them), refused_members (member, refusal in the ledger's words), blocked_until_ms (the latest instant a standing block on any member lifts, absent when none stands), live_sessions (the leases all members carry, null when the lease register could not be read), windows, tightest_remaining_fraction and available. Each window is the newest reading of one plan window (limit_id, for Claude five_hour and seven_day) any member of the account holds, named by the member it was read off: used_fraction is the provider's own statement, remaining_fraction is the whole limit less it and is null for a window that is not current (its resets_at_ms has passed and nothing has been read since), and read_at_ms is when Brama read it. available is whether a session put on the account now would be served: a usable member exists and no current window is at its limit. Two members of one account draw on one quota, which is why the reading is per account; Brama computes no number the provider did not state, so an account with no reading carries an empty windows and a null tightest_remaining_fraction rather than a free plan. The acquisition and a session-placing policy (Oko's model policy) read this object, so what they decide on is what brama subscription list and Brama Desktop show.

scope is deployment or the agent id the answer was narrowed to, so a caller reads the narrowing instead of inferring it from the row count. Every row carries the same fields whoever asked: id, provider, status, state (live | expired | burnt | unknown), expires_at, last_redeem_error, limits, measured, block, observed_at_ms, probe, usage_check, credential (state: active | needs_reauthorization | disabled, with cause and instants), usage_source and stale. No error names an account outside the caller's narrowing. Field semantics are in concepts/subscription.

Writing the pool

POST takes {"action", "agent_id"?, "provider"?, "label"?, "api_key"?, "login_item"?, "subscription_id"?}, and an unknown field is refused with the deserializer's own sentence. action is bank or retire; anything else is 400 action must be "bank" or "retire".

  • agent_id names the owner and may be sent only by the deployment scope, which is the one caller whose proof is not itself an agent. Sending it as an agent or an account holder is 400 agent_id is derived from the proven identity and must not be sent; omitting it as the deployment is 400 agent_id names the agent whose pool is written and is required for a deployment-scoped write; a malformed one is 400 invalid agent id.
  • bank stores a credential on the coordinate the routes table already names for that subscription. With no subscription_id the deterministic primary slot is written; an explicit id selects another existing subscription owned by that agent, and the primary slot is overwritten only when the request names it. Refusals: 404 subscription not found for an unknown or unowned selection, 409 selected subscription belongs to a different provider, 400 provider must name a supported remote API or subscription provider, 400 api_key must not be empty, 400 login_item must contain 1..160 ASCII letters, digits, hyphens, underscores or dots, a document that does not reduce to a bearer (400), a mapping conflict (409) and a failed vault write (500). Success is {"subscription": {"id", "provider", "agent_id", "status": "active", "label", "login_item"}}; the credential value is never returned.
  • retire requires subscription_id (400 subscription_id is required) and answers 404 subscription not found for a target the proven owner does not own, including one the deployment holds for somebody else. Success is {"ok": true}. Retirement is a journal record that outranks whatever the last refresh concluded, never a vault deletion.

Leases: which session runs on which subscription

/v1/subscription-pool/leases is the gateway's register of live sessions per subscription, read and written by the same proof as the pool. A lease is one session's claim on one member, taken by the program that starts the session before its harness starts and released when the terminal ends; the operator's limit of sessions one subscription carries is the number the provider's declaration in src/subscription_dispatch/acquire/providers.json names in src/subscription_dispatch/acquire/numeric-provenance.json (sessions_per_subscription_max for claude-code), with his words.

POST takes {"provider", "session_id", "holder"} and answers {"lease": {"id", "provider", "subscription_id", "account", "session_id", "holder", "taken_at_ms"}, "live_on_subscription", "limit", "new"}: the usable member (active, not retired, nothing standing against it in the ledger) carrying the fewest live leases, below the limit; among equals, the one whose plan is least used. A session already holding a live lease on the provider gets it back with new: false. Refusals: 400 invalid_request (a field missing or empty, or an unknown field, in the deserializer's words); 409 pool_full when every usable member carries the limit — details.refusal.members lists each member with its live count, and details.acquisition carries the verdict of the acquisition the gateway started for that shortage (result acquired, refused with its code, or failed); 503 no_usable_member with each member and what the ledger holds against it; 503 limit_unstated; 503 dependency_unavailable with the inventory's or the register's cause.

A POST that finds every usable member full, or no usable member at all, starts an acquisition for that shortage (trigger sessions_full or no_usable_member) and answers only when it ends. When it bought an account, the lease is taken again and the answer is the lease document with the purchase verdict in acquisition. When it did not, the answer is the refusal above with the verdict in details.acquisition; a lease refused again after a purchase carries the second refusal in details.refusal and the first in details.first_refusal.

POST /v1/subscription-pool/leases/refused takes {"session_id", "provider", "retry_at", "reason"} from a holder whose harness keeps its own sign-in (OMP) and met the provider's limit on the leased member without this gateway seeing it: every member the session holds a live lease on for that provider is blocked in the usage ledger until retry_at (RFC 3339, the provider's own instant) with reason, as a full rate window, and those leases are released, so the session's next POST lands on another usable member of the same provider, or its shortage starts an acquisition. It answers {"blocked": [lease...], "until_ms"}; a session holding no such lease is answered with blocked: [] and detail saying so. Refusals: 400 for a malformed body, an empty field, a retry_at that is not RFC 3339 or is not after now; 503 when the register could not be read or a blocked lease not released. An agent reports only the leases it took.

GET answers {"scope", "limits": {"<provider>": n}, "unstated": {"<provider>": refusal}, "leases": [lease...], "counts": {"": n}}: each declared provider's limit, or why it states none. DELETE takes {"lease_id"?, "session_id"?} and answers {"released": [lease...]} — every live lease named, none when nothing was live; a body naming neither is 400. DELETE /v1/subscription-pool/leases/:lease_id releases one lease by path. The register is BRAMA_SUBSCRIPTION_LEASES_FILE, or leases.json under BRAMA_STATE_DIR, replaced atomically; the pool read shows each member's live_sessions from it, and an unreadable register is one entry of the pool read's errors.

A lease records the agent whose proof took it (agent; absent when the deployment console took it), and the proof that reaches these routes decides which leases a caller acts on. The console sees and ends every lease. A signed agent or an account holder sees in leases only the leases it took, gets back on POST only a standing lease it took, and ends only its own: a DELETE naming a lease another identity took is 403 naming that lease, and nothing in that request is released. counts stays the pool's, as the pool read shows every caller each member's live_sessions. Leases taken before leases recorded their agent carry none, so only the console ends them.

A session that runs on a leased member names it on every model request, whatever wire it speaks, in two headers the chat, Anthropic messages and OpenAI responses routes all read: X-Brama-Subscription: <provider>:<subscription_id> and, when the member declares an account, X-Brama-Account: <address>. They are the request's billing target unless the body named its own billingTarget; a header that is not <provider>:<subscription_id> is refused 400 by name. Oko's local proxy sends them for every session it starts on a lease.

POST /v1/plan-usage

What every plan the caller proved it owns has left. One invocation for the same three audiences, replacing the four per-audience refreshes this page used to list — POST /v1/subscription-usage/:agent_id, POST /v1/account/subscription-usage, POST /v1/admin/subscription-usage/:agent_id and POST /v1/admin/subscription-pool/usage — each of which read this same ledger and these same reports. They are gone from the router (command surface).

The scope is read off the proof in exactly the order the pool reads it, through the same resolver. Anything else is 403 forbidden; a request with no bearer at all is 401 unauthorized. A model-scoped bearer reaches this path, which is what makes a signed workload able to read its own plans at all.

It is a POST because answering reads each provider's own usage report and records what it said. That spends no plan quota: no completion is sent and no sign-in is started. A read asked for here is taken now, unlike the maintenance sweep, which reads an account only when its reading is due (no reading, a reset window, no stated reset, or a failed last read); concurrent readers join one bounded read per account rather than asking a provider the same question twice in the same second, and no more than four accounts are read at a time because several accounts on one host share one source address.

The body is empty and signed empty — the answer follows from the proven identity, so a request carrying any field is 400 plan usage takes no request fields; the answer follows from the proven identity rather than answered from a signature over something the handler disregards.

Subscription usage results

The pool read and plan usage answer one document: ok, observed_at_ms, errors, scope and subscriptions, with every reading in the plan-usage answer brought current. A completed report can return HTTP 200 with ok: false: the body retains the successful rows and carries standard Wisent failure envelopes for the failures, so clients inspect ok rather than read success out of the HTTP status. An admission failure still uses the ordinary HTTP error body.

Three outcomes stay distinct per account.

  • A report the provider answered sets limits with each window's used_fraction, its reset and observation instants, and usage_source: "provider". Window percentages come only from valid provider metrics.
  • A report the provider refused keeps the last good reading, marks it stale, sets usage_check.ok: false, and puts the refusal in errors in the provider's own words with its status — an incomplete answer that names the account it is incomplete about. A missing measurement is never reported as zero usage.
  • A provider with no supported free report is recorded as exactly that, which is a third statement and not a failure.

Each row carries usage_check with the latest free report attempt's attempted_at_ms, ok, detail and source, kept separate from a completion probe. An inventory Brama could not read stops the reads and is reported instead, because a listing that failed says nothing about which accounts exist. An unreachable dependency, an invalid response or a storage failure appears in the result naming the failed operation and the actual reason.

Sign-in is asked for explicitly

POST /v1/account/subscription-sign-in/:subscription_id signs in one subscription owned by the authenticated account. Neither the pool read nor plan usage ever starts this flow; it happens because a caller asked for it on this path.

The sign-in routes require a nonempty reason and accept login_item; the pooled administrative variant additionally requires subscription_id in the body. Brama verifies current ownership, refuses a retired or mismapped account, derives the provider from the selected subscription, and invokes the existing Weles flow on the Stado-selected host. It returns the native signed_in or failed verdict with the actual detail. The two administered variants are in administration and operations.