openrouter-analytics-schema
About openrouter-analytics-schema
openrouter-analytics-schema is a skill for discovering the structure of OpenRouter analytics data, intended for users constructing analytics queries. It uses live metadata to describe available metrics, dimensions, filter operators, and time granularities.
Key features
- 01Retrieves live metric and dimension definitions
- 02Lists filter operators and time granularities
- 03Explains data label resolution rules
- 04Documents time-range limits for metrics and dimensions
Use cases
- 01Check currently available analytics fields
- 02Choose data breakdowns by model or provider
- 03Verify query time-range and granularity limits
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ openrouterteam/ skills --skill openrouter-analytics-schemaAfter installing, type/openrouter-analytics-schemain Claude Code to confirm it loaded.
Bundled filesREADME.md
Details
From the project READMEOpenRouter Analytics Schema Discovery
Discover what analytics data is available for querying. The meta endpoint returns live, always-current definitions of metrics, dimensions, filter operators, and granularities.
Prerequisites
- An OpenRouter management key. Management keys are separate from regular API keys — get one at https://openrouter.ai/settings/management-keys
- Pass it via
--api-key <key>or set theOPENROUTER_API_KEYenvironment variable
Discovery Endpoint
GET https://openrouter.ai/api/v1/analytics/meta
Authorization: Bearer sk-or-v1-...
Or via the openrouter-analytics skill scripts:
cd <openrouter-analytics-skill-path>/scripts && npx tsx discover-schema.ts
Response Shape
{
"data": {
"metrics": [
{ "name": "request_count", "display_label": "Request Count", "is_rate": false, "display_format": "number" }
],
"dimensions": [
{ "name": "model", "display_label": "Model" }
],
"operators": [
{ "name": "eq", "value_type": "scalar" }
],
"granularities": [
{ "name": "day", "display_label": "Day" }
]
}
}
Understanding Metrics
Each metric has:
| Field | Meaning |
|---|---|
name | Identifier to use in query requests |
display_label | Human-readable label |
is_rate | Whether this is a ratio/rate (averaged, not summed) |
display_format | How the value should be formatted: number, currency, percent, latency, or throughput |
Time Range Limits
Most volume and cost metrics support time ranges up to 367 days with day or coarser granularity. Latency/throughput metrics, blended_cost_per_million_tokens, and generations-only dimensions (provider, origin, country, data_region, skin, finish_reason, external_user, context_length_bucket, generation_id, session_id, streamed) are limited to 31-day time ranges, as is hour granularity. Possible-cache metrics (possible_cached_tokens, possible_cache_hit_rate, cache_capture_rate) always read an hourly rollup, so they are capped at 31 days too and coarser granularity does not lift that cap.
Exceeding a cap returns 400 with the applicable maximum in the message, not a truncated result. If a query times out instead, try narrowing the time range or removing latency/throughput metrics and per-generation dimensions.
Metric Categories
Volume metrics (how much):
request_count— number of API requests (up to 367 days). Excludes server-tool rows (web search / file-parsing charges recorded alongside a request), so it counts model requests, not billed line itemstokens_total,tokens_prompt,tokens_completion— token counts (up to 367 days)reasoning_tokens— tokens used for extended thinking (up to 367 days)cached_tokens— tokens served from cache (up to 367 days)possible_cached_tokens— prompt tokens that were eligible for caching, whether or not they were cached (31-day limit, hourly rollup only)byok_request_count— number of BYOK requests (up to 367 days). Server-tool rows are excluded when the query reads raw generations (any latency/throughput metric or generations-only dimension in the same request) but still counted when served from the rollups, so the two paths can differ for BYOK traffic that uses server toolsguardrail_invoked_count— count of requests that triggered guardrails (31-day limit)response_cached_count— count of responses served from cache (31-day limit)
Cost metrics (how much money):
total_usage— total cost in USD, including BYOK inference cost (up to 367 days). Computed assum(usage) + sum(byok_usage_inference)so it reflects true spend for both credits and BYOK users.byok_usage— BYOK (bring your own key) inference cost in USD (up to 367 days)credits_usage— all charges billed to OpenRouter credits in USD, including BYOK platform fees (up to 367 days)openrouter_usage— non-BYOK inference spend in USD; excludes requests made with user-provided keys (31-day limit)byok_fees— BYOK platform fees in USD; the platform fee portion ofcredits_usagecharged on BYOK requests (31-day limit).credits_usageincludes both non-BYOK inference charges and these BYOK platform fees.usage_upstream— provider-side (upstream) cost in USD (up to 367 days)usage_cache— cache cost component in USD (up to 367 days)usage_data— data logging cost adjustment in USD; typically negative when a data logging discount applies (up to 367 days)usage_web— web search cost in USD (up to 367 days)usage_upstream_web— provider-side web search cost in USD (up to 367 days)usage_file— file processing cost in USD (31-day limit)usage_upstream_file— provider-side file processing cost in USD (31-day limit)usage_web_fetch— web fetch cost in USD (31-day limit)usage_upstream_web_fetch— provider-side web fetch cost in USD (31-day limit)blended_cost_per_million_tokens— average cost per 1M tokens (prompt + completion), including BYOK inference cost (31-day limit; always reads raw generations). Server-tool spend is excluded from the numerator. A rate metric:nullfor buckets with no tokens, so it cannot be summed across rows.
Performance metrics (how fast; all 31-day limit, milliseconds unless noted). Each family has avg_, p50_, p90_, p95_, and p99_ variants:
*_latency— provider-side time to first token (full response time for non-streamed requests)*_router_latency— time spent in OpenRouter routing before the provider request*_total_time_to_first_token—latency + router_latency, computed over streamed requests only*_generation_time— provider-side generation time*_inter_token_latency— average time between consecutive completion tokens,(generation_time - latency) / (native_tokens_completion - 1), computed over streamed requests with more than one completion token only*_throughput— completion tokens per second, derived fromnative_tokens_completion / generation_time;nullfor requests with no completion tokens
All performance metrics exclude server-tool rows (web search / file-parsing charges recorded alongside a request).
Efficiency metrics (how well):
cache_hit_rate— ratio of cached tokens to prompt tokens (0–1)possible_cache_hit_rate— ratio of cache-eligible prompt tokens to prompt tokens (0–1; 31-day limit, hourly rollup only)cache_capture_rate— ratio of cache-eligible prompt tokens that were actually served from cache (0–1 by construction; 31-day limit, hourly rollup only)guardrail_invoked_rate— ratio of requests that triggered guardrailsresponse_cached_rate— ratio of responses served from cache
Understanding Dimensions
Each dimension has:
| Field | Meaning |
|---|---|
name | Identifier to use in query requests |
display_label | Human-readable label |
Dimensions are what you break down by — "show me spend by model" means dimensions: ["model"].
You can combine up to 2 dimensions in a single query (e.g., ["model", "provider"]).
Label Resolution
Some dimensions have their raw IDs automatically resolved to human-readable labels in query results. Data rows contain the resolved display names directly:
| Dimension | Resolved to |
|---|---|
api_key_id | Key name/label |
app | App title or origin URL |
user | User name or email address |
workspace | Workspace name (deleted workspaces are still named) |
Sentinel buckets get named labels rather than raw values:
api_key_id-1→Chatroom(traffic with no API key, e.g. chatroom requests)app-1→Unknown(rows with no app attribution)workspacemax-UUID sentinel →Unattributed(no workspace could be resolved when the row was written)
All other dimensions (e.g., model, provider, country) are returned as-is without resolution.
Rows with an empty
uservalue represent traffic not attributed to a specific org member (e.g., API keys created at the org level).Rows written before workspace resolution existed carry an all-zero workspace UUID. Grouping folds them into the account's default workspace, and a filter on the default workspace UUID matches them too, so one logical workspace stays one group.
Dimension Categories
Available with all time ranges:
model— the OpenRouter model ID (permaslug)variant— model variant (e.g., standard, extended)api_key_id— which API key made the requestuser— the creator user ID (for org-level queries)workspace— workspace IDapp— application ID
Limited to 31-day time ranges:
generation_id— unique ID for each generation (use to drill down to individual requests, then inspect via theopenrouter-generationsskill)provider— upstream provider nameorigin— request origin/sourcecountry— request countrydata_region— data region the request was served from; empty values report asglobalskin— API skin (which API shape the request used, derived from the request path)finish_reason— why the generation ended (stop, length, etc.)external_user— custom user ID passed by the callercontext_length_bucket— bucketed context length (1K, 10K, 100K, etc.)session_id— session the request belonged to; sessionless requests group as the literalnonestreamed— whether the request was streamed; values are the stringstrueandfalse
Classifier Dimensions
Beyond the standard dimensions above, you can group by classifier dimensions — dynamic labels produced by a user-created classifier (e.g., topic, sentiment, category).
- Use the
classifier_dimensionsrequest field to group by classifier-produced values - At most 2 classifier dimension names per query; naming two cross-groups results by both
- Use the
classifier_filtersrequest field to filter on classifier values - The classifier must belong to your account (validated server-side)
- Classifier dimensions/filters limit the query time range to 31 days
- See the
openrouter-analytics-queryskill for the full request shape
Mapping Questions to Classifier Queries
| Question pattern | Request fields | Notes |
|---|---|---|
| "Spend by topic" | classifier_dimensions: { classifier_id, dimension_names: ["topic"] } + metrics: ["total_usage"] | Single dimension → column aliased to topic |
| "Only billing-related requests" | classifier_filters: { classifier_id, filters: [{ field: "category", operator: "eq", value: "billing" }] } | Filters support eq, neq, in, not_in only |
| "Sentiment breakdown including unclassified" | classifier_dimensions: { classifier_id, dimension_names: ["sentiment"], include_nulls: true } | Includes rows without classification |
Understanding Operators
Filter operators for the filters array in query requests:
| Operator | Value Type | Meaning |
|---|---|---|
eq | scalar | Equals |
neq | scalar | Not equals |
gt | scalar | Greater than |
gte | scalar | Greater than or equal |
lt | scalar | Less than |
lte | scalar | Less than or equal |
in | array | In list |
not_in | array | Not in list |
in and not_in accept an optional include_unset: true to also match rows where the dimension has no value. Only dimensions with an unset bucket support it (api_key_id, app, user); anything else is rejected with 400.
Understanding Granularities
Time bucketing for time-series queries:
| Granularity | Use when |
|---|---|
minute | Last few hours, real-time monitoring |
hour | Last 1–3 days |
day | Last week to 3 months |
week | Last 3–12 months |
month | Year-scale trends |
When no granularity is set, the query returns aggregate totals without time bucketing.
Mapping Questions to Queries
Use this guide to translate natural-language questions into the right metric/dimension/filter combination:
| Question pattern | Metrics | Dimensions | Notes |
|---|---|---|---|
| "How much did I spend?" | total_usage | — | Add granularity for trends |
| "Which models cost the most?" | total_usage | model | Order by total_usage desc |
| "How many requests?" | request_count | — | Add model or api_key_id for breakdown |
| "How many tokens?" | tokens_total | — | Use tokens_prompt / tokens_completion for split |
| "Which provider is fastest?" | avg_latency, p90_latency | provider | 31-day limit |
| "What's my cache hit rate?" | cache_hit_rate | model | Rate metric — shows per-model caching |
| "Which API key uses the most?" | request_count, total_usage | api_key_id | — |
| "Usage over time" | request_count or total_usage | — | Set granularity: "day" |
| "Latency trends" | p90_latency | — | Set granularity: "hour", 31d limit |
| "How smooth is streaming?" | p50_inter_token_latency, p95_inter_token_latency | model or provider | 31-day limit. Streamed requests with more than one completion token only |
| "Usage by country" | request_count | country | 31-day limit |
| "How can I save money?" | total_usage, cache_hit_rate, tokens_total | model | See cost optimization in openrouter-analytics skill |
| "Show me individual requests" | total_usage, tokens_total | generation_id | 31-day limit. Use returned IDs with openrouter-generations skill for full metadata and content |
| "How much BYOK spend?" | byok_usage | model | Up to 367 days |
| "BYOK vs credits split?" | byok_usage, credits_usage | — | Both up to 367 days |
| "BYOK platform fees?" | byok_fees | model | 31-day limit |
| "Non-BYOK inference spend?" | openrouter_usage | model | 31-day limit |
| "How many guardrail triggers?" | guardrail_invoked_count, guardrail_invoked_rate | model | 31-day limit |
| "How many cached responses?" | response_cached_count, response_cached_rate | model | 31-day limit |
| "Where does my spend go?" | usage_upstream, usage_cache, usage_data | — | Full cost breakdown (up to 367 days) |
| "Web search costs?" | usage_web, usage_upstream_web | model | Up to 367 days |
| "File processing costs?" | usage_file, usage_upstream_file | model | 31-day limit |
| "Web fetch costs?" | usage_web_fetch, usage_upstream_web_fetch | model | 31-day limit |
Filter Value Reference
Several dimensions are label-resolved in query results — the response shows human-readable names, but filters must use the underlying ID. Here's where to find each:
| Dimension | Filter value | Where to find it |
|---|---|---|
api_key_id | Numeric ID or 64-char SHA-256 hash | Numeric ID: generation metadata (api_key_id field). Hash: GET /api/v1/keys (key_hash field). Hashes are auto-resolved server-side. If a hash can't be resolved, a sentinel value returns zero rows (no error). |
user | Clerk user ID (e.g. user_abc123) | User settings or org member list — not the display name/email shown in results. |
workspace | Workspace UUID. Filtering on the default workspace UUID also matches legacy rows written before workspace resolution existed. | Workspace settings page or GET /api/v1/workspaces — not the workspace name shown in results. |
app | Numeric app ID | Generation metadata (app_id field) or app settings — not the app title shown in results. |
model | Permaslug (e.g. openai/gpt-4o) | Model page URL or GET /api/v1/models — not the display name. |
Other dimensions (provider, origin, country, finish_reason, external_user, etc.) are not enriched — filter values match what's returned in results.
Constraints
- Maximum 2 dimensions per query
- Maximum 20 filters per query
- Maximum 2 classifier dimension names per query
- Maximum 10 classifier filters per query
- Maximum 10,000 rows returned per query (default 1,000)
group_limit(1–10,000): controls max rows per dimension combination. Auto-computed on time-series queries with dimensions to guarantee full time-window coverage. Set explicitly to cap per-group rows (e.g., top N per model per day).- Most volume/cost metrics: up to 367 days with daily granularity
- Latency/throughput metrics,
blended_cost_per_million_tokens, and per-generation dimensions: up to 31 days - Classifier dimensions/filters: always limited to 31 days
- Minute granularity: only available when the time window is ≤ 3 hours
hourgranularity reads the minute materialized view: 31-day maximum- Possible-cache metrics (
possible_cached_tokens,possible_cache_hit_rate,cache_capture_rate): 31-day maximum,houror coarser granularity, and onlymodel/providerdimensions and filters — any other dimension, filter, classifier, orminutegranularity returns 400 - Rate-limited to 64 requests per minute