Usage and costs
Verified with Cortex on October 2, 2026
Cortex measures what every request consumes: input and output tokens, and a cost estimate. Usage rows carry identifiers and counters only, never conversation content. Everything is in the console, under "FinOps". The cost is Cortex's estimate, not the provider's invoice.
The page has four tabs: "Usage", "Budgets", "Virtual keys" and "Pricing". The "Administrator" role sees all four. The "Auditor" role sees "FinOps" in the menu, but only the "Usage" tab.
Usage dashboard
Choose the "Period" ("Last 24 hours" by default, "Last 7 days", "Last 30 days" or "Custom" with "From" and "To"), the "Destination" and "Group by", then click "Apply". Nothing changes before that click: before the first one, the figures cover all recorded usage, even though the list reads "Last 24 hours".
"Group by" offers "Overall" (the default), "User", "Role", "Destination", "Virtual key" and "Day". There is no grouping by department. A user who holds several roles is counted under each, and days follow UTC time.
Four cards summarize the period: "Requests", "Estimated cost", "Served from cache" and "Input tokens". The table details each group, with "Output tokens", "Cost" and "Unpriced". The cost is shown per currency: Cortex never adds two currencies together. "Unpriced" counts the requests whose model had no price at the time of the call.
"Export CSV" and "Export JSON" download the usage rows one by one, with the applied period and destination.
Model pricing
- A local destination is valued at the single rate
FINOPS_LOCAL_COST_PER_MTOK, in Canadian dollars (CAD) per million tokens, input and output together. Without this variable, the local cost is 0 CAD. It is set in the.envfile at the repository root, then recreate the gateway container (docker compose up -d gateway). - An external (API) destination is valued from the pricing catalog, by provider and model. A model missing from the catalog has no cost and counts under "Unpriced".
The "Pricing" tab shows this catalog, in prices per million tokens. It is shared by every tenant of the gateway. "imported" rows come from the pricing sync; an "admin" price, set by hand, is never overwritten by a sync.
- Click "Add a price", or "Reprice" on a model's row.
- Fill in the "Input cost per million tokens", the "Output cost per million tokens" and the "Currency" (three uppercase letters). To add a price, also enter the "Provider" and the "Model"; repricing keeps them.
- Click "Save".
Prices are corrected forward: the current row is closed and a new row takes effect. Nothing is deleted. Turn on the "Show history" switch, then click "Apply" to see the closed rows.
Budgets
A budget is a spending limit, managed in the "Budgets" tab.
- Click "New budget".
- Choose the "Scope": "Tenant" (the whole deployment), "Role", "User" or "Virtual key", then the "Target" (except for "Tenant").
- Choose the "Period": "Daily", "Weekly" (from Monday), "Monthly" or "Custom" ("Start" and "End"). Periods follow UTC time.
- Enter the "Limit amount" and the "Currency" (CAD by default).
- Set the "Alert threshold (%)" (80 by default), then click "Create".
"Edit" opens the budget window; the "Active" switch in it suspends the budget without deleting it. Click "Save" to apply. "Delete" asks for confirmation and cannot be undone. These changes are not recorded in the audit journal.
A budget counts only the spending in its own currency: a CAD budget ignores a USD spend. A role budget counts the spending of every account holding that role, including through their virtual keys, but it is not checked on a request made with a virtual key (user, key and tenant budgets are).
Cortex checks budgets before sending a request. When the estimated spending of the period reaches the limit, the request is refused with HTTP status 402 (budget_exceeded) and nothing is sent to the provider. The employee reads "This request was declined because a spending limit has been reached." The refusal is recorded in the audit journal as "Request blocked (budget)". The request that crosses the alert threshold is recorded as "Budget alert"; Cortex sends no email.
Virtual keys
A virtual key is for an application that calls the gateway without the interface: it is sent in the Authorization: Bearer header of the OpenAI-compatible API (/v1/…, for example /v1/chat/completions) and is refused elsewhere. It belongs to a user, its requests are counted under that user's name, and it reaches only its allowed destinations.
- In the "Virtual keys" tab, click "Issue a key".
- Fill in the "Label" and the "Owner", then tick at least one destination under "Allowed destinations".
- Optional: "Requests per minute", "Tokens per minute" and "Expires at".
- Click "Issue". The "Key issued" window shows the key, which starts with
cor_vk_. - Click "Copy", store the key in the application's secret store, then click "Done".
On an "Active" key, "Rotate" generates a new secret, shown once, and the old one stops working at once. "Revoke" disables the key at once and cannot be undone; an expired key is refused in the same way. Issuing, rotating and revoking are recorded in the audit journal.
Limits are measured over a sliding 60-second window. When one is reached, the request receives status 429 (quota_exceeded), recorded as "Request blocked (quota)". For a key with no limit of its own, FINOPS_DEFAULT_RPM_LIMIT and FINOPS_DEFAULT_TPM_LIMIT set the defaults; without them, there is no limit. The count is kept per gateway process. See Configuration.