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 behind the dashboard

Every screen is a client of one HTTP API on the admin plane. What it covers, how it is metered, and where its reference lives.

ExplanationLast updated

On this page

The dashboard has no private channel to the engine. Every figure on every screen came from an HTTP call you can make yourself, against /tt/api/v1 on the admin listener, documented by an OpenAPI specification the binary serves.

That matters for two reasons. Anything the interface can do, a script can do. And when a surface shows you something surprising, you can ask the same question directly and compare.

The reference

The full specification is rendered at the API reference: every operation, its parameters, its response schema, its errors, with request samples. It is generated from the specification the binary serves rather than written alongside it.

Your own instance serves the same document, which is the copy to trust if the two ever differ:

$ curl -s http://127.0.0.1:9090/tt/api/v1/openapi.yaml

That document and the browsable reference at /tt/api/docs both sit at the viewer floor by default. Setting dashboard.docs_public: true moves them to the public floor for teams that want an open reference; the default is false.

What it covers

Roughly the shape of the surface index. Reads over the ledger (/query, /rows, /facets, /requests/{id}), and over pricing, providers, cache, governance, quality and evidence. Writes for configuration, ladder mode, saved views, schedules, calibration activation, reconciliation imports and pricing overrides. Administration for users, tokens and share tokens.

Start from /capabilities, which needs no credential and answers what this instance can do: its engine mode, its pricing snapshot, which features are composed, the whole filter-field vocabulary, and the API’s own conventions.

$ curl -s http://127.0.0.1:9090/tt/api/v1/capabilities
{"api_version":2,"engine":{"version":"dev","mode":"ledger","pricing_snapshot":"litellm@2026-07-14#179affb"},"features":{"index":true,"stream":true,"control_plane":true,"replay":false,"budgets":{"defined":0,"enforce_granted":false},"alerts":false,"anomaly":true,...

Conventions worth knowing before you write a client

The capability document publishes these too, with the reasoning, so a client can read them rather than hard-coding them.

Two rate budgets, per principal. Reads and writes are metered separately, so a write storm cannot starve the reads you need during an incident. The defaults are 600 reads and 120 writes per minute, set by dashboard.api_limits. Classification follows what an operation does rather than its HTTP method: several heavy reads travel by POST because they carry a filter body, and they draw the read budget. The response header says which budget it charged.

A fixed window admits about twice its budget across a boundary. That is inherent to the algorithm rather than a defect, and it is the bound to pace against.

Cursors are opaque. Echo next_cursor back; do not construct one. On the finite feeds its absence is the end of the feed. The event log is the exception and says so on its own operation: it has no end, so its cursor is present on every response and means “resume from here whenever you next poll”.

A limit above the maximum is refused rather than clamped, with the cap named, because a silently clamped page would misreport what the server heard. One operation clamps instead and discloses the clamp on the response.

Every response carries a request id, and the same value appears in any error body and on the server’s own log line for that request.

Idempotency, events and webhooks

Idempotency is available on the operations that advertise it, through an Idempotency-Key header. Three properties to design around: a replay returns the stored first response verbatim including a stored error, the store is per-process so a retry landing on another node re-executes, and the store is bounded so an eviction before the retention window also causes a re-execution. Treat the window as a maximum rather than a guarantee.

The event log at /events is a bounded ring of successful mutations — configuration applied, mode set, keys minted or revoked, budget thresholds crossed, calibrations activated. It is derived from the audit trail, which records every attempt including the rejected ones, so the trail is the underived answer and polling the log is always sufficient.

Webhooks deliver those same payloads with a standard signature scheme. Subscriptions are configuration rather than an API resource, they flow the same validate-diff-apply loop, and adding one requires a restart. Delivery is at least once: deduplicate on the message id, and enforce the timestamp tolerance or a captured delivery replays forever.

Authentication and scope

The API uses the same credentials and the same roles as the interface. A session cookie works if you are already signed in; a bearer token is what a script should use, and a session-authenticated write additionally needs the CSRF header that a token principal does not.

A token can carry an explicit scope list narrower than its role, which is how a Grafana panel or a CI job gets exactly the access it needs. Roles and permissions has the vocabulary and the privileged set.

Errors are RFC 9457 problem documents with a stable type URL, and the instance serves its own catalogue of them at /tt/api/v1/problems. A 403 caused by a missing scope names that scope in a response header as well as in the body.

What it is not

It is not the data plane. Nothing here proxies an LLM request. Your traffic goes to server.listen and is documented by connect your app.

It is not a metrics endpoint. /metrics on the same listener serves Prometheus, and that belongs to Observability.

It does not change under you silently. The capability document carries an API version and a management-API version, and a deprecated operation carries a Deprecation header. A Sunset header appears only once a removal date has been chosen, so its absence means not yet scheduled rather than an invented date.