The console¶
For administrators
A map of the console at /console: which screen answers which question, and which page explains it.
The console is the React admin app the gateway serves at /console. Everyone
who signs in sees two personal screens; administrators also get an Admin
button beside their name, which opens a landing page with a card per screen.
Each card says what the screen decides, not what it is called.
For everyone¶
| Screen | What it shows |
|---|---|
| Overview | where you stand this period, your quotas and the API keys you spend with (create and revoke them here; a person without the API-keys capability is told so instead of being offered Create) |
| Your usage | what you spent, and what the figures rest on. Administrators have it too: their own spend is not administration |
Money is shown to three decimal places and never rounds a real amount down to
zero (it reads < €0.001). Administrators can switch to full precision with
the Exact figures toggle.
Administration¶
| I want to… | Screen | Read |
|---|---|---|
| see what the deployment spent, by group, person, model, provider or day, and chart it over time | Usage | Accounting and quotas |
| list every request, one line each and never its content, filter it and export it | Requests | The request log |
| set a ceiling — the group's total or each member — see what it has consumed, check a counter against the ledger (Quota health, with Reconcile) or read a rule's reset history (resetting itself is done under Quota usage) | Quotas | Quotas, Group rules: the total, or each member, Quota health and reconcile |
| see each user's quotas: which apply, how much of each they have used, the limit and when it resets, and reset any quota on the record, with or without a reason: one user's count of a per-user quota, a user's own quota, or a shared total for everyone it counts | Quota usage | Quotas |
| decide what is stripped from prompts before a provider sees them, and for whom | Redaction | Redaction |
| add a provider, its credentials and what it reports | Providers | Providers and plugins |
| change what models are on offer, who may use them and what they cost, and name a model as your clients should show it | Models | Three cost figures, A model's name and its display name |
| choose the search backends, set what a search costs per request (0 for billed out of band), and see how many searches each group spent | Web search | Surfaces, Web search: a price per request |
| offer a model under a name of your own, and change what that name serves without any client changing | Routing | Model aliases |
| add, disable, reset or merge a person | Users | Bundled accounts, Identity |
| see at a glance who may use only the chat | Users (the Chat only badge) | Chat-only people |
| decide who belongs together, what a group may use, and who holds coding agents or API keys | Groups (the two Capabilities toggles) | Groups, Chat-only people |
| map or dismiss the group names your identity provider reports | Groups, Groups from your identity provider | Mapping the provider's groups |
| configure mail, read the identity provider, decide who may become a user, set what new groups start with, or turn a capability on or off for every group | Settings | Identity |
| see what happened to a person's account, and who did it | Users → Edit → Activity | The audit trail |
| see what administrators changed in models, prices, aliases, grants, providers and quota rules, and who did it | Audit log | The administrator audit log |
Two things on the Settings screen are worth knowing before you look for
them. The identity provider is read-only: it is set in .env and rewritten
at every start (One provider, set in the environment),
so the console shows it and offers User sync… only for an external provider
(User sync). What the console does edit
is the provisioning policy: who may come to exist, which claim names their
groups, what an IdP group means here, and what new groups start with
(Chat-only people).
The Models screen's Discover button reads a provider's catalogue and offers its models for import. A model the provider lists without a price is listed too, marked No price yet: tick it and a price field opens on the row — per million tokens, per page for an OCR model, or per minute of audio for a transcription model — and typing one there is what makes the row importable; the import records it in the price history as a price entered by hand. A chat row also takes optional cache read and cache write rates; left empty, cached tokens are billed at the input rate. If the provider (or the community file) turns out to publish a price after all, theirs is the one that is kept and the result says your figure was not used. See Importing models from a provider's catalogue.
A model's Features (on the model page, and when you add one by hand) are
what it can do. Images in tool results is the gateway's own: it lets the
chat show the model page images that come back from a tool, for example the
slides of a document, as users of a vision model expect. It is available only
when the model accepts images (Accepts: image), and it starts ticked for
every model that does: for models that already existed when it arrived, for
models imported from a provider's catalogue, and for a model you add by hand
once you tick image input. Turn it off for a model that ignores or mishandles
images placed after a tool result. The chat reads the setting from
supported_features on GET /v1/models, and the change is recorded in the
audit log. Removing it from a model stays
removed: the catalogue is read only when a model is created.
Reasons are optional. Every form that offers one (resetting a quota, merging users, a redaction rule, the sign-in policy) labels the field Reason (optional); a reason you give is kept in the audit log.
A non-administrator who follows a bookmarked admin link is told they are not one, rather than shown a blank page; the API refuses them regardless.
The administrator audit log¶
The Audit log screen (GET /api/admin/audit, administrators only) lists what
administrators changed in configuration, newest first: when, who (their email at
the time), the action, the target, and an expandable before and after for the
fields that changed. It is separate from the identity audit
trail, which records who exists and who may sign
in.
Recorded actions: model.create|update|delete (including models imported from a
catalogue), price.create, alias.create|update|delete,
grant.group.add|remove and grant.user.add|remove (only when a grant really
changed, so a repeated request records nothing), provider.create|update|delete
and limit.create|update|delete (quota rules). Each event is written in the
same transaction as the change, so a change that fails leaves no event.
Credentials are never recorded. A provider's API key shows only as
api_key: set, rotated or cleared; its extra headers show as header names,
with extra_headers_values: rotated when only a value changed; any credential
embedded in a base URL is stripped. Aliases still also write to the identity
events, as before.
Filters: from and to (a day, inclusive), administrator (their id, or part of
their email), action prefix (grant. lists every grant change) and target type.
Rows are kept indefinitely: there is no retention setting or purge.
Translations¶
The console's words live in one catalogue, apps/console/messages/en.json:
every button, heading, notice and placeholder on every screen is read from
there, and none is written in the screen's own file. English is the only
language that ships; the machinery exists so that adding one is a file, not a
change to twenty screens.
Adding a language is three steps:
- Copy
messages/en.jsontomessages/<locale>.json(say,messages/de.json) and translate the values. The keys and the file's shape stay exactly as they are; a{placeholder}or a{#tag}…{/tag}inside a value is syntax, not text, and must survive the translation where it is. - Add the locale to
localesinapps/console/project.inlang/settings.json. - Run
pnpm --filter @llmp/console i18n:check. It fails on a key the code uses that the new file is missing, and on a message whose placeholders do not match the English one, before anything ships.
The language a reader gets is picked in order: an explicit choice stored in the
browser (localStorage["console.locale"]), then the browser's own preferred
language if the catalogue has it, then English. Setting the key by hand — say,
localStorage.setItem("console.locale", "de") in the console's devtools — is
how you try a translation that has no menu yet.
Finding what is not yet translated is what en-XA is for. It is a
pseudo-locale generated from the English catalogue
(pnpm --filter @llmp/console i18n:pseudo): every message comes back accented
and wrapped in brackets, with the placeholders untouched. Set
localStorage["console.locale"] to en-XA, reload, and walk the screens: a
string still reading as plain English is one the catalogue does not carry yet.
It is a developer tool — no reader can arrive at it by accident, and no menu
offers it.
pnpm --filter @llmp/console i18n:check is the guard, in CI with the console's
own checks: it fails on a key used in code that en.json does not have, on
en-XA.json being out of date with en.json, and on a screen that has been
converted carrying English text in its JSX again.