Skip to main content
Version: 3.5.0-rc.2

Tenant Metrics & Usage Tracking

Overview​

The platform records the tokens every model call consumes, and the portal reads the records back as usage views. The portal shows token usage per tenant, model, and API key. Per-user breakdowns are available through the API. The same records feed the API key usage export.

Key Concepts​

What Gets Tracked​

The platform automatically tracks the following metrics:

  • Prompt Tokens (input): Tokens sent in requests
  • Completion Tokens (output): Tokens returned in responses
  • Embedding Tokens: Tokens used for embedding operations
  • Rerank Tokens: Tokens used for reranking operations
  • Total Tokens: Every metered token: prompt, completion, embedding and rerank tokens added together.
  • Audio Duration: Length of transcribed audio, in minutes, as the model's backend reports it from the decoded audio. It is not a token count, so it has its own chart and never joins a token total or a ranking.

Transcriptions count as prompt and completion tokens, like chat, when the model reports them; a plain-text response_format returns no usage at all, and those requests stay out of token totals. A successful transcription also records audio duration, kept out of every token total. The in-repo Whisper engine reports it for every format it serves; other backends record it when their reply includes a duration. Streamed replies record no audio duration.

How It Works​

Services log token usage to ClickHouse after each model call. The portal reads the records back over two services, each gated on the audience it serves:

  • cmind.metering.v1.PlatformUsageService: a platform administrator, across every tenant, optionally narrowed to some of them.
  • cmind.metering.v1.TenantUsageService: a tenant administrator, inside their own tenant. The tenant comes from the credential, never from the request.

The platform views also read the service-mesh access logs, for the request volume and latency of every HTTP call the platform served, including calls that never reached a model.

Using the Usage Views​

Tenant Usage​

Tenant administrators reach their own usage through Usage in the tenant sidebar. The link is shown to tenant administrators only.

The page holds the time-range controls, the token usage explorer, the Requests card, and the export of token usage per API key.

Platform Usage​

Platform administrators reach usage in Platform → Usage, which holds two tabs: Usage, for the token usage explorer and the request counts, and Resources, for the cluster's live compute resources. The open tab is part of the page address (?tab=), so a view is shareable.

The Usage Explorer​

The explorer draws token usage over time as stacked bars or one line per group. Window totals shows Total, Input, Output, Embedding, and Rerank for every group in the selected period. Chart and table controls are independent.

Compare selects Models or Tenants for the platform chart. Use the picker beside it to search for and select up to 20 groups. Its label is Chart models or Chart tenants, based on what it selects. The chart starts with the 20 largest groups in the window, or all groups if there are fewer. Clear a selection to make room for another. Clearing every selection leaves the chart empty. The legend lists the groups drawn and their totals. Groups that stay selected keep their colour when selection or ranking changes. Chart switches between stacked bars and lines. If names cannot be loaded, the page uses IDs and offers Retry group names.

Group by belongs to the breakdown table. It offers Models, Tenants, Tenants, then models, or Models, then tenants on the platform view. The Filter models and Filter tenants controls apply before grouping and stay available with every grouping. All models and All tenants stay first in their lists, including during search. Select either option to remove that filter; Clear filters restores the complete table. Table filters have no 20-group limit. They do not change the chart, window totals, or CSV export.

The table displays 25 parent groups per page. Pair views start with the five largest children under each parent. Select Load 5 more to show the next children. Paging or loading more children pauses Live at the current window. Select Now to resume live updates. Parent totals include every matching child, including those not yet shown. Column headings sort parents across all pages. Children stay ordered by total tokens, largest first. Use the API or CSV export for complete data and further analysis.

If the chart cannot be loaded, select Retry chart. If a breakdown page cannot be loaded, select Retry table. A failed page change keeps the rows and page number from the last successful request. If more children cannot be loaded, the table keeps its current rows and shows Could not load more children. Try again. Select Load 5 more to retry.

The ordered table grouping (?groupBy=tenant,model, for example), chart dimension (?chartBy=), chart type (?chart=), chart selections (?tenants=, ?models=, ?apiKeys=), and table filters (?tableModels=, ?tableTenants=, ?tableApiKeys=) are part of the page address, so a view is shareable. The time range belongs to the page; every card follows the same exact bounds. Zooming preserves both sets of controls. Chart selection does not change the CSV export.

A tenant administrator sees the same explorer on their own Usage page. Compare offers Models and API keys, with a Chart models or Chart API keys picker beside it. The table's Group by offers Models, API keys, API keys, then models, and Models, then API keys, with independent Filter models and Filter API keys controls. All API keys removes the key filter. Only usage inside the selected tenant is shown. API keys use their display names when available, or their IDs otherwise. No API key contains usage from requests made without an API key and can also be selected in the table filter.

The breakdown lists groups with usage in the selected period that match the table filters.

Time Range Selection​

Preset ranges: Last hour, last 2 hours, last 6 hours, last 24 hours, last 7 days, Month to date and One month. A view opens on Last 2 hours. From and To stay visible for every preset.

Month to date runs from the first day of the current month to now, in your local timezone. One month covers the calendar month selected in Month. A completed month stays fixed, with Live off. The current month ends at now while Live is on. The Month list offers the current month and the eleven months before it. To view an older month, set From and To yourself.

Custom range: Edit From or To to switch to a custom window. Setting To stops Live at that instant. Now resumes live tracking; from a completed month, it moves to the current month. Pausing Live keeps the end time currently shown.

Zoom: Drag across the token graph to select a smaller time window. This pauses Live and updates every card. Reset zoom restores the window from before the first drag. The date fields provide the same time selection without dragging. Changing a time control clears the saved zoom window.

Live: On by default for a window that ends at now. Figures refresh every 30 seconds at the fastest, or once per bucket for wider buckets, up to 5 minutes. The breakdown keeps its sort order and keyboard focus during refresh.

Bucket width: Derived from the span, finer for a short window and coarser for a long one. The server chooses the width it actually returns and may coarsen the one it was asked for. Calendar months use sub-day buckets, so a UTC bucket is not presented as a complete local day. Bucket alignment does not extend the requested totals window.

Per-Tenant Usage (Portal)​

Platform administrators can see how many tokens each tenant used: open Platform → Usage and set Group by to Tenants. The table under the bars has one row per tenant:

ColumnDescription
TenantTenant name, or the tenant ID when the name cannot be found
TotalInput, Output, Embedding and Rerank added together
InputTokens sent in requests
OutputTokens returned in responses
EmbeddingTokens used for embedding
RerankTokens used for reranking

Tenants are listed with the highest total first, and clicking a column heading sorts by that column. Use Compare → Tenants, then Chart tenants above the chart to choose the tenants to draw. Tenants with no usage in the period are not listed.

Token counts are shortened, for example 1.2K for 1,200 and 3.4M for 3,400,000.

The table uses the same period as the rest of the page. When you change the period, or when Live is on, the table updates with it.

Understanding the Charts​

X-axis: Labels follow the viewer's locale, like the other dates in the portal

  • Interval of one day: The weekday and the date, without the year
  • Window that covers more than one day: The weekday and the date at the first bucket and at the first bucket of each new date, even when these do not fall at midnight. Other ticks show the time. The chart shades alternate days and draws a line at each day change, with no line at the start of the window
  • Window that stays in one day: The time

Hover label: Follows the viewer's locale. It carries the weekday and the date as well as the time when the period covers more than one day.

Y-axis: Auto-formatted token counts (K = thousands, M = millions)

Series: The explorer draws the selected groups in stable colours, with their totals in the legend. Window totals always include all groups. On an individual API key's usage tab, the lines instead show token types; select a type beside the chart to hide or show it.

Exporting API Key Usage​

Tenant administrators can download token usage per API key as a CSV file.

Portal​

  1. Open Usage in the tenant sidebar. The link is shown to tenant administrators only.
  2. Select a Time range preset, or set the period with From and To.
  3. Click Export API-key CSV.

The file covers the selected period, which is also included in its name, for example api-key-usage-2026-08-01T000000Z-to-2026-08-06T000000Z.csv. Keys without usage in that period are omitted. See API Key Usage Export for the columns, row structure, ordering, and attribution rules.


API Reference​

API Key Usage Export​

POST /cmind.metering.v1.TenantUsageService/ExportTokenUsageByApiKey

Download one CSV file with token usage for every attributed API key in a tenant. The caller must be a tenant administrator for that tenant.

Request Fields:

  • window (required): The time range, with a start and an end. end must be after start.

The time range includes start and excludes end.

The request does not name a tenant. The tenant comes from the credential you call with: an API key belongs to one tenant, and a user token must send the tenant ID in the X-Cm-Tenant-Id header. A call that names no tenant fails with permission_denied.

Response Fields:

  • filename: Suggested filename, such as api-key-usage-2026-08-01T000000Z-to-2026-08-06T000000Z.csv
  • csv: CSV file contents as bytes

CSV Columns:

ColumnDescription
period_start_utcStart of the requested range, in UTC
period_end_utcEnd of the requested range, in UTC
api_key_idStable API key ID, to distinguish keys with the same name
modelInternal model resource name stored with the usage
input_tokensTokens sent in requests
output_tokensTokens returned in responses
embedding_tokensTokens used for embedding
rerank_tokensTokens used for reranking
audio_duration_msLength of audio submitted for transcription, in milliseconds
api_key_nameCurrent display name, as shown in the portal
api_key_prefixCurrent key prefix, as shown in the portal; never the full secret
model_display_nameCurrent display name; empty when unset
model_public_idPublic ID of the underlying model, used to invoke it directly

The file has one row for every pair of API key and model. There is no summary row for a key and no total column. To get a key's token total, add its rows and its four token columns together in a spreadsheet or in your own code. audio_duration_ms is not a token count, so leave it out of that sum.

Rows are ordered by API key ID, and by model within each key. Revoked keys with usage are included. Key names and prefixes describe the key at export time, not at the time of each request. Model metadata also reflects the current model resource. The model fields are separate: an unset display name stays empty. Model display names and public IDs are empty when the caller cannot see the model in the selected tenant, the resource no longer exists, or a deployment and an external model have the same resource name. The stored resource ID and usage stay in the export. Usage through an alias is attributed to its underlying model. If a key is absent from the caller's key lists, its name and prefix are empty, but its ID and usage stay in the export.

The file uses UTF-8 with a byte-order mark and CRLF line endings so Excel can open names with non-ASCII characters. This changes the file's leading bytes: programmatic readers must use a BOM-aware decoder, such as Python's encoding="utf-8-sig", or remove the leading U+FEFF before parsing the CSV. Otherwise the first header can be read as \ufeffperiod_start_utc instead of period_start_utc.

Text that starts with a spreadsheet formula marker has a leading apostrophe. Token counts and audio duration remain exact decimal integers.

The export is not paginated and has no row limit. It includes every API key with attributed usage in the requested range. If no key has usage, the file contains only the header row.

Only usage that the platform can attribute to an API key is included. Requests made without an API key are omitted, so the CSV totals can be lower than the tenant totals shown in the usage views.

The request fails with invalid_argument if window is missing, if its start or end is missing, or if end is not after start.

The export reads current API key and model information before it creates a file with usage rows. If a lookup fails, the request returns an error, such as unavailable, and no CSV file. For unavailable, try the export again later.

Per-Tenant Token Usage​

POST /cmind.metering.v1.PlatformUsageService/ListPlatformTokenUsage

Get token totals for one time range, broken down by tenant, model, user, or tenant/model pairs. The caller must be a platform administrator and must call without selecting a tenant. A request that names a tenant fails with permission_denied.

Request Fields:

  • window (required): The time range, with a start and an end. end must be after start.
  • groupByDimensions (required): An ordered list of one or two dimensions. Use ["TOKEN_USAGE_GROUP_BY_NONE"] for one total. A single dimension can be "TOKEN_USAGE_GROUP_BY_TENANT", "TOKEN_USAGE_GROUP_BY_MODEL", or "TOKEN_USAGE_GROUP_BY_USER". Only tenant and model can be paired: ["TOKEN_USAGE_GROUP_BY_TENANT", "TOKEN_USAGE_GROUP_BY_MODEL"] returns tenant/model pairs; reverse the list for model/tenant pairs. Dimensions must be unique. NONE must appear alone.
  • tenantIds (optional): Tenant IDs to include. Leave it empty to include every tenant. This filter is independent of the grouping dimensions.
  • page (optional): Cursor and page size. Paging always applies; the default is 25 parent groups, maximum 100.
  • orderBy (optional): Parent sort field and direction. See Cursor paging.
  • modelIds (optional): Models to include. Leave it empty to include every model.
  • userIds (optional): Users to include. Leave it empty to include every user.

The time range includes start and excludes end. The result is not split into time buckets. Each row gets one set of totals for the whole range.

Response Fields:

  • window: The time range the totals cover.
  • page: Parent page cursors and continuation flags.
  • parentGroups: For two dimensions, parents on this page with complete totals and a childPage cursor.
  • groups: The single-dimension page, or up to five children per parent for two dimensions, with these fields:
FieldDescription
groupKeysOne key per requested dimension, in the same order. A key is empty when usage has no attribution on that dimension. The list is empty for an ungrouped total.
tokens.inputTokensTokens sent in requests
tokens.outputTokensTokens returned in responses
tokens.embeddingTokensTokens used for embedding
tokens.rerankTokensTokens used for reranking
tokens.audioDurationMsLength of audio submitted for transcription, in milliseconds. It is not a token count.

By default, parents are ordered by their four token counts added together, highest first. Audio duration is not part of that sum and does not change the order. Tenants with no usage in the range are not returned. The response contains tenant IDs, not tenant names.

Grouping by API key is not available here, because an API key belongs to one tenant. Use ListTokenUsage for per-key detail inside a tenant.

The request fails with invalid_argument if window is missing, if its start or end is missing, if end is not after start, if groupByDimensions is empty, if a grouping is unsupported or has duplicate dimensions, or if a value in tenantIds or userIds is not a valid ID.

GetPlatformTokenUsageSeries and GetTokenUsageSeries take one scalar groupBy, including TOKEN_USAGE_GROUP_BY_NONE. Their responses include maxSeries: the server's maximum number of groups in a comparison chart. The portal uses it to limit token-chart selections. It is also returned for NONE, so a picker can read the limit with the window totals. Request-count charts use ungrouped queries and have no group picker. The series limit does not cap breakdown filters or exports.

A grouped token series request returns at most maxSeries groups. If more groups have usage in the range, only the largest by token total are returned. The other groups are omitted, so a group absent from a chart can still have usage. Page through ListPlatformTokenUsage or ListTokenUsage to read every group in a breakdown.

Cursor Paging for Token Breakdowns​

Both token breakdown endpoints always use cursor paging, even when page is omitted. Set page.pageSize to the number of first-dimension groups to return: the default is 25 and the maximum is 100. To continue, send the response's page.endCursor as page.cursor while page.hasNextPage is true. Keep the window, filters, and order fixed. Late usage reports can change totals between requests; paging does not provide a snapshot.

The response's page contains startCursor, endCursor, hasNextPage, and hasPreviousPage. The cursors identify the first and last groups on the page. To read the previous page while hasPreviousPage is true, send the current response's page.startCursor as page.cursor with page.direction: "PAGE_DIRECTION_BACKWARD". Forward paging is the default. Groups always return in the requested sort order, whichever direction you read in.

A cursor belongs to the sort order it was issued for. Using it with a different orderBy sort order returns invalid_argument. Start a fresh traversal when you change the sort order.

With two dimensions, parentGroups contains the selected parents and their complete totals. groups contains up to five children per parent. Each parent's childPage shows whether more children exist. To continue one parent, filter by its key, group by the second dimension alone, and send its childPage.endCursor as page.cursor with page.pageSize: 5 and orderBy: "total_tokens desc".

orderBy accepts total_tokens, input_tokens, output_tokens, embedding_tokens, or rerank_tokens, followed by asc or desc. The default is total_tokens desc. Group keys break ties in ascending order. Children always start in descending total-token order.

The former limit field is removed. Follow parent and child cursors to read the full breakdown. CSV exports remain complete and do not use these page limits. A database timeout returns deadline_exceeded; exceeding the query memory limit returns resource_exhausted. Use a narrower window or more filters before retrying those queries.

Token Usage Breakdown for One Tenant​

POST /cmind.metering.v1.TenantUsageService/ListTokenUsage

Get token totals for one time range inside one tenant. The caller must be an administrator of that tenant.

The request has no tenant field. For a user token, send the tenant ID in the X-Cm-Tenant-Id header. The service reads the tenant scope from the caller's credential. A request without a tenant fails with permission_denied.

Request Fields:

  • window (required): The time range, with a start and an end. end must be after start.
  • groupByDimensions (required): An ordered list of one or two dimensions. Use ["TOKEN_USAGE_GROUP_BY_NONE"] for one total. A single dimension can be "TOKEN_USAGE_GROUP_BY_MODEL", "TOKEN_USAGE_GROUP_BY_USER", or "TOKEN_USAGE_GROUP_BY_API_KEY". Only API key and model can be paired: ["TOKEN_USAGE_GROUP_BY_API_KEY", "TOKEN_USAGE_GROUP_BY_MODEL"] returns API key/model pairs; reverse the list for model/API key pairs. Dimensions must be unique. NONE must appear alone.
  • page (optional): Cursor and page size. Paging always applies; the default is 25 parent groups, maximum 100.
  • orderBy (optional): Parent sort field and direction. See Cursor paging.
  • modelIds (optional): Models to include. Leave it empty to include every model.
  • userIds (optional): Users to include. Leave it empty to include every user.
  • apiKeyIds (optional): API keys to include. Leave it empty to include every key.

"TOKEN_USAGE_GROUP_BY_TENANT" is not accepted here, because the result already covers one tenant. Use ListPlatformTokenUsage to compare tenants.

Response Fields:

  • window: The time range the totals cover. It includes start and excludes end.
  • page and parentGroups: The same parent pagination and complete totals as the platform endpoint.
  • groups: The single-dimension page, or up to five children per parent, with the same groupKeys and tokens fields as Per-Tenant Token Usage. Each row covers the whole range. By default, rows are ordered by their four token counts added together, highest first. Audio duration is not part of that sum.

The request fails with invalid_argument if window is missing, if its start or end is missing, if end is not after start, if groupByDimensions is empty, if a grouping is unsupported or has duplicate dimensions, or if a value in userIds is not a valid ID.