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-desktopidentity 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_idnames 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 is400 agent_id is derived from the proven identity and must not be sent; omitting it as the deployment is400 agent_id names the agent whose pool is written and is required for a deployment-scoped write; a malformed one is400 invalid agent id.bankstores a credential on the coordinate the routes table already names for that subscription. With nosubscription_idthe 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 foundfor 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.retirerequiressubscription_id(400 subscription_id is required) and answers404 subscription not foundfor 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": {": 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
limitswith each window'sused_fraction, its reset and observation instants, andusage_source: "provider". Window percentages come only from valid provider metrics. - A report the provider refused keeps the last good reading, marks it
stale, setsusage_check.ok: false, and puts the refusal inerrorsin 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.