Administration and operations¶
The /v1/admin/* family is how the console changes this deployment: the route
registry, provider credentials, configuration adoption and the two deliberate actions
against a named subscription. Alongside them sit the three paths an operator watches:
/health, /readyz and /stats.
Who the admin family answers¶
Every /v1/admin/* path answers only a brama-desktop identity
carrying no model allowlist; every other caller receives 403 forbidden
(captured). That identity comes from the boot table alone: the launcher reads the
Skarbiec item brama-desktop-model-router, field token — the same
item Brama Desktop acquires through its workload key — and admits its value as
brama-desktop with no agent and no allowlist. A bearer the authority resolves
instead always carries the routes it was issued, so it can never reach this family. When
the launcher cannot read that item it says optional client brama-desktop skipped
on its stderr and starts anyway; the admin family then answers 401 to the
desktop and to the console's own admin calls alike. Responses carry identifiers, usage and status only: a
subscription credential is write-only and is never part of an answer, on any path,
including an error path.
GET /v1/admin/snapshot¶
Routes and providers as this process currently holds them. Captured keys:
schemaVersion, whose value is the number one; automaticRollback,
which is true; boundaries
({"credentials":"skarbiec","releases":"stado","routing":"brama"});
providers[] in the same shape /stats reports; and
routes, the registry document as read from the configured file. When that file
will not read, the answer is 503 read route registry <path>: <cause>, retaining the actual filesystem, parser or validation refusal.
The registry document under routes carries exactly three known top-level
fields — the schema version, deployments and routes — and
routes maps each alias to its single destination:
{"deployments":[],
"routes":{"weles":"best","ox-alpha":"openrouter/stealth/ox-alpha"}}
The document also carries schema_version, whose value is the number one. Any
top-level field beyond those three is refused by the deserializer and the whole document is
rejected, so a file written for an earlier shape is refused rather than read in part. That
refusal is what puts every alias into routes_file_invalid.
brama routes migrate reads the configured
document, keeps the known fields, validates the result and commits it with the same atomic
owner-only replacement the writes below use; it is idempotent and prints the document it
committed.
The route registry¶
PUT /v1/admin/routes takes exactly
{"alias", "primary"}: the alias, and the one route it resolves to. An unknown
field is refused with the deserializer's own sentence (captured), so a body carrying
anything else — including a list of further routes — does not write. Refusals:
409 runtime route registry is not configured(captured) when no registry file is in effect, because there is nothing to write into;400 unknown route aliasfor a name this gateway does not accept as an alias;- a
400route-validation refusal when the stated route is unsupported, duplicates the destination the alias already holds, or is unavailable on this deployment; 503with the credential authority's operation and cause when route inspection fails, before any registry mutation;409 update alias `<alias>` in route registry <path>: <cause>for a registry read, validation or persistence failure.
DELETE /v1/admin/routes takes exactly
{"alias"} and removes that alias from the document. It answers
400 invalid route alias for a malformed name,
409 required route aliases cannot be deleted for an alias in the compiled-in
contract, 404 route alias not found for one the document does not carry, and
409 delete alias `<alias>` from route registry <path>: <cause> for a registry read, validation or persistence failure.
Brama Desktop receives these same messages through its existing failed-mutation and snapshot diagnostics.
Both answer {"ok": true, "routes": <document>}, the whole registry
document as committed, so the caller reads the new state rather than assuming it. The write
is validated first and then committed by atomic owner-only replacement: either the file is
the new document or it is the old one. Nothing partial is left on disk, and a rejected write
leaves the file exactly as it was.
What the resulting states look like from the client side is
GET /v1/aliases.
Registry-refusal qualification: cargo test --test credential-administration -- --ignored --exact route_refusal::invalid_registry_preserves_read_and_mutation_causes. Use an isolated, quiescent real gateway whose invalid registry is readable by the qualification process. Supply TEST_SOURCE_REVISION, GATEWAY_SOURCE_REVISION, BRAMA_ADMIN_URL, BRAMA_ADMIN_BEARER, EXPECTED_REGISTRY_PATH and the independently observed EXPECTED_REGISTRY_CAUSE. The test verifies build identity and the snapshot refusal before attempting writes; it uses only a fresh alias owned by that run, checks both mutation causes and byte-for-byte unchanged registry contents, and retains HTTP evidence under target/real-tests/registry-refusal. It does not create the invalid fixture, change credentials or contact a model provider.
Model categories¶
PUT /v1/admin/categories takes exactly
{"category", "providers", "routes", "terms"} — the name and the whole
membership rule — and replaces that category in the same document the routes live
in. The three lists are optional and each defaults to empty, but a category
declaring none of them is refused: a rule that matches nothing means nothing. Other
refusals name the member that was wrong —
400 model category 'Uncensored' must be lowercase letters, digits and
hyphens,
400 model category 'x' names 'not-a-route', which is not a provider/model
route — and 409 runtime route registry is not configured when no
registry file is in effect.
DELETE /v1/admin/categories takes exactly
{"category"} and answers 404 no model category 'x' is
declared for a name nobody declared, so the caller is never told a document
changed when it did not.
Both answer {"ok": true, "routes": <document>} and take the same
staged, validated, owner-only replacement the route writes take. What the declarations
then look like from the client side is
GET /v1/categories,
and what they do to the catalogue is category.
Configuration adoption¶
Two paths take a whole configuration document and merge the aliases an operator picks out of it. Both work on the registry shape above, so an alias adopted from a source document arrives with one destination.
POST /v1/admin/configuration-adoption/preview takes exactly
{"document":"<schema-1 JSON>","source_name":"<display name>","agent_id":"wisent-app"},
where agent_id is wisent-app when the request omits it. It
validates the complete source and the current destination, reads the secret-free configured
acquisition and subscription identities, and returns providers,
subscriptions, subscription_discovery, candidates and
unreferenced_deployments. It performs no write, no credential redemption, no
subscription activation and no inference.
POST /v1/admin/configuration-adoption/apply takes exactly
{"document":"<schema-1 JSON>","source_name":"<display name>","agent_id":"wisent-app","selected_aliases":["<alias>"],"replace_alias_conflicts":false}.
It repeats the preview validation, rejects a duplicate or unknown selection, and atomically
merges only the accepted selections into the configured registry file. The answer contains
the resulting secret-free routes, per-alias items, and exact
imported, unchanged, conflicting and
rejected counts. An existing alias keeps its route unless the request
explicitly asked for replacement, and a deployment conflict is never replaced.
Both adoption paths answer 409 runtime route registry is not configured when
no live registry file exists, 400 <reason> for an invalid document,
identity, route, selection or destination state, and 403 forbidden for any
caller other than Brama Desktop. Every error path leaves the registry file unchanged and
returns no credential contents.
Provider credentials¶
/v1/admin/credentials administers the standalone provider credential store
this host keeps, the one a deployment without Skarbiec redeems from.
- GET answers
{"providers": ["<provider>", ...]}, the providers a credential is stored for. Names only: no credential value is returned. - PUT takes
{"provider", "credential"}and answers{"ok": true, "provider": "<provider>"}. A provider the build does not support is400 provider must name a supported remote API provider, and a credential the store refuses is a400carrying the store's own sentence. - DELETE takes
{"provider"}and answers{"ok": true, "provider": "<provider>"}. A provider with nothing stored is404 provider credential not found.
Read and delete failures retain the failed operation and the credential store's actual cause with 409. A disabled store reports standalone credential store is not enabled; a poisoned credential lock is not misreported as a disabled store. The write retains its store refusal with 400. No refusal returns a credential value.
Qualify the refusal surface with cargo test --test credential-administration -- --ignored --nocapture, supplying BRAMA_ADMIN_URL, BRAMA_ADMIN_BEARER, TEST_SOURCE_REVISION, GATEWAY_SOURCE_REVISION and the independently known EXPECTED_STORE_REFUSAL. Use an isolated real gateway whose standalone store is unavailable. The journey verifies the serving revision and exact read refusal before attempting deletion of a fresh random provider identifier, then requires the same refusal state on a second read. It retains actual HTTP results and source identity; it installs no credential, induces no process failure and does not qualify a poisoned lock unless that is the real fixture's observed condition.
Administered subscriptions¶
Four paths act on a subscription the console administers. Each one happens because the console asked for it; no timer reaches any of them.
- POST
/v1/admin/subscriptions/:agent_id/:subscription_id/probeis the one path in the product that deliberately spends plan quota: one minimal completion against one named subscription, recorded as aprobewith sourcecompletion. It is refused with409when the subscription is inside a recorded block, and404 subscription not foundwhen it is otherwise absent (captured). Success is{"ok": true, "probe": {...}, "subscription": {...}}. - POST
/v1/admin/subscription-sign-in/:agent_id/:subscription_idsigns in one subscription owned by the administered agent. It requires a nonemptyreasonand acceptslogin_item. - POST
/v1/admin/subscription-pool/sign-insigns in the selected pooled account. The body states the method inby, every time:"weles"or"hand"; a body without it is refused, nothing is assumed. Both methods require a nonemptyreasonandsubscription_id."by": "weles"acceptslogin_itemand signs in through Weles. Both Weles sign-in paths verify current ownership, refuse a retired or mismapped account, derive the provider from the selected subscription, and return the nativesigned_inorfailedverdict with its actual detail."by": "hand"begins a manual Claude sign-in without Weles: it verifies the account is active and not retired, draws the PKCE verifier and state, keeps them in the gateway, and answers{"sign_in_id", "provider", "subscription_id", "url"}. Theurlis the provider's authorize page asomp auth-broker login anthropicopens it; the operator opens it in their own browser. Refusals:400for a blank reason or id, or alogin_item(login_item <id> names a Weles sign-in row, and by hand signs in through the operator's own browser; drop it or sign in by weles),404 subscription not found,409for a retired account or a provider without a manual flow (a manual sign-in is defined for claude-code; `codex` is not it). An open sign-in stays open until it is completed or a new one is begun for the same account, which replaces it; nothing expires on a clock.
- POST
/v1/admin/subscription-pool/sign-in/:sign_in_idcompletes it with{"code"}: thecode#statethe page showed or the whole redirect URL. The gateway checks the state against the one it drew, exchanges the code at the provider's token endpoint, stores the grant as the subscription's credential and proves it with one minimal completion, answering the samesigned_inorfailedverdict as the CLI's login --by hand. The sign-in is spent by the first paste, whatever it comes to:404 no manual sign-in with that id is openafterwards,409with the exact sentence for a paste that is empty, unreadable or carries another sign-in's state. - POST
/v1/admin/subscription-pool/disowngives one member back:{"subscription_id", "reason"?}. The gateway retires the member, records the reason in its credential ledger, removes the stored credential and stops routing to it; any machine that signed that account in keeps its own session. This isbrama subscription disown. Answers{"ok", "subscription_id", "provider", "detail"}. Refusals:400 subscription_id is required,404 the pool holds no member with that id,403for any caller but the console. There is noPOST /v1/admin/subscription-pool/grant: an endpoint that takes a grant a coding harness on the console's machine already holds hands over a session, because a provider issues one OAuth pair per sign-in and revokes it when a second holder refreshes, so each adopted grant signed that machine out. A gateway signs itself in instead. - POST
/v1/admin/subscription-pool/reinstatetakes one member back:{"subscription_id", "reason"?}. A retirement used to be permanent, so a member this pool gave back could not be put back from anywhere; reinstating returns it to the rotation, and it still holds no grant of this gateway’s own, which its recorded state then says (needs_reauthorization). This isbrama subscription reinstate. Answers{"ok", "subscription_id", "provider", "detail"}. Refusals:400 subscription_id is required,404for a member this pool does not hold,409for one that is not retired,403for any caller but the console. - POST
/v1/admin/subscription-pool/forgetdrops one id only the usage ledger remembers:{"subscription_id", "reason"}. The usage ledger keeps every id that ever ran here; one the vault no longer returns, never retired, is no pool member, counted by nothing, and the pool report answers it apart underremembered({id, provider});disownrefuses it. Forgetting journals the reason as aforgottenrecord, drops any overlay entry and local credential under the id and removes the ledger row; nothing is read from or written to the vault, and discovery may enrol the account again under its own identity. This isbrama subscription forget. Answers{"ok", "subscription_id", "provider", "detail"}. Refusals:400for an empty id or a missing reason (reason is required: say why this member is forgotten),404when neither the vault nor the usage history knows the id,409when the vault lists the member (the answer namesdisown) or a live session still leases it,503when the vault, the lease register or the journal cannot be read,403for any caller but the console. - POST
/v1/admin/subscription-pool/refreshtakes{"provider", "reason"}and runs the same audited refresh asbrama subscription refresh, answering the verdict with status 200 (captured:{"attempted":0,"detail":"no usable `codex` subscription is in this deployment's pool, ...","provider":"codex","result":"failed"}). The verdict is the answer; only the CLI turns afailedverdict into a non-zero exit code. - POST
/v1/admin/subscription-pool/attributetakes{"provider"}and records which account each member of that provider belongs to in the vault this gateway serves from, which is the vault Weles resolves a sign-in from. This isbrama subscription attribute --gateway. Answers{"provider", "recorded": [{"member", "account"}], "unattributed": [{"member", "reason"}]}with status 200 even when members are left unattributed. Refusals:400for an empty provider or one this pool holds no member of,403for any caller but the console.
Reading the pool, rather than acting on it, is discovery and subscriptions.
GET /health¶
Liveness only, no request fields, no bearer. Captured:
{"build":{"built_at":"...","platform":"...","product":"brama",
"source_revision":"...","version":"0.2.38"},
"dependencies":"not_probed","status":"ok"}
It never redeems a credential, lists subscriptions or contacts a provider: it answers
ok from a gateway whose every redemption is being refused
(architecture). Treat it as evidence the process is
running, and nothing else.
GET /readyz¶
The latest completed credential and routing check. A background sweep redeems configured provider capabilities and active subscription credentials and checks model discovery; this request returns its completed report without performing those operations again. Credential redemption waits outside the HTTP workers, so slow Skarbiec responses do not prevent the gateway answering this route.
{"build":{...},"denied":[],"operator_action_required":false,
"providers":[{"credential":true,"provider":"openai"}],
"ready":true,
"reason":"every configured provider credential was obtained, every active subscription redeemed, and every vault account carries the agent tag that makes it routable",
"routing":[],"subscriptions":[],"unredeemable":[],"unroutable":[],
"unroutable_accounts":[]}
The status is 503 with ready: false until a check has
completed or when no configured route can obtain a credential. A gateway with
working routes and failed accounts reports 200 and degraded: true.
Routing results appear under routing. Rows in subscriptions[] carry per-subscription
redeemable and the refusal in the words of whatever refused it, and never a
secret.
Every answer also carries maintenance: the last maintenance pass this
process ran, as {"last_pass_unix": <seconds>, "last_pass_ok": <bool>}.
The serving process runs its own passes, with no schedule to declare: one when it starts,
then one at the next moment its usage ledger states something becomes due (a grant's stated
expiry, a plan window's stated reset, a rate block's stated end), and one right after a
request is refused subscription_reauthorization_required; each renews due grants
and signs members in through Weles by itself. brama
maintain asks for one more. Before the first pass ends it reads
{"last_pass_unix": null, "detail": "the serving process's first maintenance pass has
not finished since it started: it renews due grants and signs members in through Weles by
itself, and its result appears here when it ends"}.
operator_action_required is true only for what that maintenance cannot
repair: a direct-provider credential the vault does not hold, a vault account this process
was not started with or cannot route, or a member listed in
automatic_sign_in.blocked for something other than a dependency outage. A
member blocked because Weles did not answer (weles_unreachable, including a
resolver route that answered a server error with no JSON) or because a provider is rate
limiting is listed there and retried by the next pass without asking anyone; an
unredeemable subscription alone asks nothing of anyone.
Every answer carries model, read from the usage ledger at the moment
/readyz is asked: {"state": "available", "reason": null} while some
signing agent's pool holds a member that can be served (live, or expired or unchecked and so
renewed when a request redeems it); {"state": "none", "reason": "..."} while every
member needs a new sign-in, with each member's id, provider, state, the refusal in its way and
its repair (a burnt grant: the Weles sign-in of that member, which the maintenance pass orders);
{"state": "unknown", "reason": "..."} with the read that failed; and
{"state": "local"} on a gateway serving its own provider credentials. With
none or unknown the answer is 503, ready: false,
and reason carries that sentence after any cause the report was already unready for,
joined by | . A routed model request to a pool every member of which needs a new
sign-in is refused the same way, before any credential read, snapshot or provider call:
503 subscription_reauthorization_required naming every member, in
milliseconds rather than after each member fails in turn. Clients read that refusal or the
503 readiness answer and send nothing further, naming Brama's reason.
A credential call that stands unanswered overrides the completed report. Each call to
the credential source — a Skarbiec introspection over HTTP, and every vault child the gateway
shells (list subscriptions, inspect credential account,
read credential account, route resolve, a credential write, the
account-discovery registration and routing-mark restoration, …) — is recorded while it
stands. A call is measured by the refresh interval this host's Stado resolver declares, or,
where no resolver declares one (a standalone or Desktop gateway Stado does not place), by
BRAMA_CREDENTIAL_CALL_STANDING_SECONDS; every answer says which in
standing_measure: {"seconds", "declared_by"}, or
{"unbounded": "neither this host's resolver nor BRAMA_CREDENTIAL_CALL_STANDING_SECONDS
declares how long a credential call may stand, so a call that never answers is listed in
standing_calls and never ended"}, or {"unreadable": "<cause>"}. A
reading happens when /readyz is asked, and the gateway takes one itself whenever a
call begins or ends and at the moment the oldest call reaches the measure, whether or not any
client is asking. When a reading finds one that has stood that long or longer, the
answer is 503, ready: false, and reason and
credential_source carry the credential source has not answered: <N>
call(s) have stood <N>s (<who declared it>) or longer, the oldest to vault child
`read credential account` (pid <N>) for <N>s; Brama serves no chat until it
answers. The same reading ends every such call: the vault child is
ended, the gateway's log says vault child abandoned by the readiness reading
(and, for a reading the gateway took itself, standing credential calls refused by the
gateway's own reading: <sentence>), and the request that waited on it is refused
with its own call's sentence — the credential source has not answered <operation>
to <target> (pid <N>) for <N>s, past the <N>s <who declared it>
declares; Brama serves no chat until it answers — instead of standing until the
client's transport gives up with an empty
reply, also when nobody watches /readyz. Every answer lists each call standing
now in standing_calls, the longest-standing first, as
{"target", "operation", "pid", "since", "refused", "elapsed_seconds"}:
operation is the vault operation (read credential account, …) or
Skarbiec token introspection, pid the vault child's process
(null for an HTTP call), since when it began in RFC 3339,
refused whether a reading has already ended it while its process is reaped, and
elapsed_seconds how long it had stood when the answer was read; the list is
empty while nothing stands. Qualification against a vault that
never answers: cargo test --test readiness-vault-standing -- --ignored, with the
isolated gateway started with SKARBIEC_BIN naming that test's executable and
BRAMA_TEST_VAULT_STANDS set.
Every answer carries digest, the SHA-256 of its status and body without the
elapsed_seconds of its standing calls, so it changes when calls begin, end or are
refused, not every second one stands.
GET /readyz?after=<digest> answers once the readiness answer differs from
the one that digest names: at once when it already does, otherwise when a sweep or a
maintenance pass publishes, a credential call begins or ends, or the oldest standing call
reaches the declared measure — the moment the gateway's own reading above happens.
A client that watches Brama while it waits on a request asks with the digest of the
answer it last read, so it makes one request per change instead of one per answer.
Qualification: cargo test --test readiness-standing -- --ignored, which asks
with each answer's digest and refuses two readings carrying one.
GET /stats¶
Bearer-protected process telemetry, with no dependency probing. Captured keys:
build, configuredDirectProviders, dependencyPolicy
({"capabilityBroker":"final-use","catalog":"lazy","subscriptions":"lazy"}),
models[]
with per-model count, latencyMs, lastLatencyMs,
tps and lastTps (a rate with no time to be measured over is
null, never zero: latencyMs for a model with no dispatch counted,
tps while no latency was measured, lastTps when the most recent
dispatch was answered in no measurable time), perfModels, and
providers[] with per-descriptor id, displayName,
wireProtocol and configured. It also carries the cumulative
total_requests, total_failures,
total_provider_attempts, total_input_tokens,
total_output_tokens and uptimeSeconds, and
standingCalls[], the same records as standing_calls in
/readyz, so a vault child that never answers is named with its operation,
process and age by the gateway's diagnostics as well.
configured reports the declared capability or field-scoped route,
not successful authentication. Neither /stats nor the admin snapshot
issues credentials; use /readyz for observed redemption failures and
a real completion to verify the full inference path. An unreadable declared grant-route file or failed provider inventory returns 503 with its actual operation, resource and cause rather than a successful empty provider list.
Read-only qualification: cargo test --test credential-administration -- --ignored --exact inventory_refusal::provider_inventory_failure_is_not_reported_as_an_empty_pool. Supply TEST_SOURCE_REVISION, GATEWAY_SOURCE_REVISION, BRAMA_ADMIN_URL, BRAMA_ADMIN_BEARER, EXPECTED_PROVIDER_INVENTORY_OPERATION, EXPECTED_PROVIDER_INVENTORY_RESOURCE and EXPECTED_PROVIDER_INVENTORY_CAUSE for an isolated real failing inventory. The test checks actual snapshot, stats and model-catalogue refusals and retains HTTP evidence under target/real-tests/provider-inventory; it changes no registry, credential or provider state. Pair it with the readiness journey to verify independently working subscriptions remain serviceable while inventory diagnostics are visible.
Because an alias resolves to one route, total_provider_attempts counts one
attempt per request that reached a provider, plus any credential rotation the gateway made
before the first byte. Process telemetry is not durable billing evidence: it resets with the
process.