Search

↑↓ to move↵ to openesc to closetop 6 documentation results

Book a demoTry TokenTriage

In development · Explore the product preview. Join the launch list or book a walkthrough.

Browse docs

The management API

Drive governance over HTTP with the endpoints grouped by job, the role each one needs, and the behaviours no schema records.

ReferenceLast updated

On this page

You want to mint a key from a provisioning script, raise one customer’s cap from your own admin tool, or graph how close each team is to its monthly dollars. All of that is HTTP on the admin plane, under one path prefix, behind one credential.

This page teaches the shape, not the fields. The rendered API reference already carries every parameter, schema and status code, so what follows is how the operations group, what role each group needs, and the handful of behaviours a caller cannot read off a schema. If limits, tenancy nodes and virtual keys are new, start at governance.

Where the API lives, and how you authenticate

The governance API rides the dashboard, and the dashboard is off unless you ask for it, so start with the port, and with whether anything is answering on it.

Everything below is served on the admin listener, server.admin, default 127.0.0.1:9090, alongside the dashboard UI, and every path in this section is relative to /tt/api/v1/governance. The data plane, server.listen, default 127.0.0.1:8787, carries none of it.

Both surfaces are gated by dashboard.enabled, which is false in a config file until you set it. The zero-config paths turn it on for you: tokentriage demo, and the --anthropic and --simple/--complex quickstarts of tokentriage run unless you pass --no-dashboard. dashboard.auth.mode then decides the credential. Its default is off, which is valid only on a loopback admin bind and treats every caller as a local admin. Set it to token and a bearer is required:

$ tokentriage token new --role admin --label docs-capture
token id: 39d61563c5ab79d8  role: admin
This token is shown ONCE and cannot be recovered:
ttk_tQIzM0aMxJtSY9gcFn-T3k5Ne3Br…

That capture is from a source build against a throwaway state directory, with the secret’s tail trimmed. Send it as Authorization: Bearer ttk_…. mode: users swaps bearers for argon2id logins and session cookies, and under it an Authorization header is not read at all. Scoped tta_ management tokens are the other mode: token credential, and they are the only one a per-operation scope is checked against. Who holds what is who can administer governance.

One status code is worth knowing before your first call. Where governance is not armed, most reads answer 200 with an empty view and the writes answer 404 feature_absent with an enable_hint naming the config change. That 404 means this deployment has no governance plane, not that your object is missing.

Two reads answer it as well, because neither has an empty view to give: GET /keys/{kid} and POST /preview. The two exports answer it on a different condition, since they read the decision ledger rather than the governance plane, and a ledger that has not been written yet is a 404 too.

The endpoints, by job

Twenty-five operations, on twenty-three paths, counted from the shipped spec. Paths below drop the shared /tt/api/v1/governance prefix. The role column is the route floor every credential clears first. A scope printed after the role is checked against a tta_ management token only; a session and a ttk_ bearer take their grant from their role and face no scope check.

Keys and identity. The ttv_ lifecycle, minus the grace window.

Operation What it does Role
GET /keys key metadata for a subtree; no field can hold a secret, and last_used has no writer, so it is always absent viewer
POST /keys mint a key; the secret appears once, in this response admin · keys:mint
GET /keys/{kid} one key by id; an out-of-subtree kid gets the same 404 as an unknown one viewer
POST /keys/{kid}/revoke switch a key off; the row survives as state: revoked operator
POST /keys/{kid}/rotate mint a successor and record the lineage admin · keys:mint

Tenancy nodes. The org/team/user tree, as flat parent-pointed rows.

Operation What it does Role
GET /nodes the runtime-plane nodes, each with its parent, kind and label viewer
POST /nodes create a runtime node of kind org, team or user; the tenancy tree picks it up at the next reload operator
DELETE /nodes/{id} delete a node; its overrides cascade away with it operator

Limits and overrides. What is armed, and what one node may have instead.

Operation What it does Role
GET /limits the armed definitions, plus every persisted overrides row viewer
POST /limits/overrides set one node’s amount for one limit operator
DELETE /limits/overrides/{limit_id}/{node} drop that node’s override operator

Status and health. Where a limit stands, and whether enforcement is intact.

Operation What it does Role
GET /status per node and per limit: burn, fraction, forecast, overrun bound viewer
GET /health shared-state reachability, breaker state, suspensions, convergence lag viewer
GET /health/fleet the same picture per replica, on the federated coverage envelope viewer
GET /reconcile ledger-recomputed spend drift, as an alert with no adopt button; no rollup can be imported here, so each scope reports its whole burn viewer

Refusals and exports. The audit trail, live and in bulk.

Operation What it does Role
GET /refusals the curated feed, newest first, cursor- or offset-paged inside a subtree viewer
GET /exports/refusals the same feed uncapped, JSON or CSV via format=, provenance-stamped viewer
GET /exports/usage periodized per-node spend and usage rollup, JSON or CSV viewer

Preview and shadow. Two reads that show effect without enforcement.

Operation What it does Role
POST /preview dry-run a proposed limit set over ledger history and live counters viewer
GET /shadow would-refuse counts per mode: shadow limit, with rows observed viewer

Delegated self-serve. A team admin acting inside one subtree.

Operation What it does Role
GET /selfserve the caller’s own subtree: nodes, status, keys, refusals viewer + subtree binding
POST /selfserve/keys mint a key whose owner node is inside that subtree viewer + subtree binding · keys:mint
POST /selfserve/overrides raise a node’s cap under the template’s declared ceiling viewer + subtree binding

Tenant tokens. A read credential you can hand to the tenant.

Operation What it does Role
POST /tenant-tokens mint a tts_ token frozen to one subtree, shown once admin · keys:mint
GET /tenant/{token} read that one subtree, and nothing above or beside it none, the token is the credential

What a schema cannot tell you

Five behaviours decide whether your integration works, and not one of them is a field or a status code you can read off a schema. They are the reason this page exists.

An override is durable now and enforced later. Writing one persists and audits it immediately, and changes nothing about the limits being applied. Every success comes back with pending_reload: true, and GET /limits keeps the two apart: definitions is what is armed, overrides is what is waiting for the next governance reload. The response says so in its own note:

definitions are the ARMED limits enforced now; overrides are persisted runtime writes that take effect on the NEXT governance reload (compile-bound). No override is reported as live-enforced here.

So after you raise org_monthly_budget from $1,000 to $2,000, definitions keeps reporting amount.usd: 1000 until governance re-arms. That is the enforced number, not a stale read.

No governance route triggers that re-arm. It happens on a restart, or on a config reload that changed governance.limits or governance.tenants, which over HTTP means POST /tt/api/v1/config/apply. A reload touching neither block leaves your override pending. POST /nodes sits on the same clock: the row lands in governance.db at once and in the tenancy tree only at the re-arm. Minting a key is the one runtime write that takes hold immediately.

Two ways the override route itself bites. An unknown limit_id is accepted here with a 200 and pending_reload: true and is then dropped at the reload, while the delegated twin answers 400 for the same body. And a type that cannot carry a per-node amount (rpm, tpm, concurrency) is turned away 422 unsupported_override_class. Check the id against definitions before you trust the 200. Which types take an override, and why, is the six limit types.

Rotate has no grace window on this API. POST /keys/{kid}/rotate always rotates with grace 0. The successor is minted and the predecessor goes dark in the same transaction, so a caller still holding the old secret starts getting 401s straight away. A window is a command-line concern: tokentriage keys rotate <kid> --grace 24h is the only way to get one, and it is what you want when in-flight callers matter. The mechanism is on virtual keys.

The self-serve role floor is a pre-filter, not the gate. All three /selfserve routes sit at a viewer floor, so the role column on its own reads as “a viewer can mint a virtual key”. It cannot. Each route also carries x-delegation: subtree, and the decision is made in the handler: the caller has to hold an admin-authored role binding whose root contains the requested node, and an unbound viewer clears the floor and then gets a 403 with the attempt audited under its own username. No route here writes that binding, and no CLI verb does either; it is a line an admin hand-edits into role-bindings.json under dashboard.state_dir.

Two more things the matrix cannot show you. Bindings resolve from a username, so they work only in dashboard.auth.mode: users, which is also the mode that reads no bearer at all: the keys:mint scope printed on the mint row cannot be what turns a caller away. And the subtree confines the three /selfserve routes rather than the account, so the same person still reads every tenant on the routes above. Read delegated self-serve before you grant anyone the role.

GET /nodes enumerates the runtime plane only. The handler lists the nodes table in governance.db, whose only writers are POST /nodes and the demo seeder. Tenants declared under governance.tenants in your config compile straight into the tenancy tree without ever landing in that table, so a config-only deployment gets {"nodes": []} back while resolution walks its tree perfectly well. On a sandbox whose governance.tenants declared five nodes, that empty list is what came back. Both planes merge into one tree when governance compiles, and this listing shows one of them. What the tree does at request time is the tenancy hierarchy.

posture collapses three configurations into one word. The field takes alert_only, enforcing, fail_closed or suspended, and alert_only covers mode: shadow, enforce: soft and mode: off alike, so a limit watching your traffic looks the same as one switched off. GET /shadow picks out one of the three, because only mode: shadow writes the would_refuse marker it counts; a limit at enforce: soft and one at mode: off stay indistinguishable on both surfaces. What each mode does is how a limit counts.

Why /tt/v1/me is not part of this API

“What am I allowed to do, and how much is left?” is a tenant’s question, not an operator’s, and it is answered on the other port. GET /tt/v1/me sits on the data-plane listener (server.listen, default 127.0.0.1:8787), takes a ttv_ virtual key rather than a dashboard token, and returns the caller’s own node, chain, scopes and live counters. Nothing about anyone else.

The split is about which credential you are willing to hand out. A dashboard token is an operator credential, and at the viewer floor it reads every tenant’s keys, statuses and spend unless you narrow it with a tts_ tenant token. A ttv_ key is already in that tenant’s hands, and the route it unlocks takes no parameter that could name another node, so it has no way to widen. An unrecognised or absent credential gets a bare 401:

$ curl -i http://127.0.0.1:8790/tt/v1/me
HTTP/1.1 401 Unauthorized
Content-Length: 0

Port 8790 is that sandbox’s data-plane listener; yours is whatever server.listen says. The empty body is the point: the response cannot confirm or deny that any particular key exists. The full 200 shape, with the resolution chain and per-limit remaining, is on virtual keys.

What this page does not cover

Field-level detail (every parameter, schema, status code and problem type) is the rendered API reference, where these operations appear on their own and inside the Management API document under the governance tag. Command-line equivalents, including the rotation flag this API does not expose, are in the CLI reference. What a governance 429 or 403 carries on the wire is the refusal wire reference. Health, suspensions, exports and reconcile as a routine are operating governance.