Monitoring OpenRouter Spend and Usage in Real Time
You cannot control agent cost you do not measure. OpenRouter exposes inline usage on every completion, key balance endpoints, generation lookup, and (with a management key) analytics queries.
Summary
Log usage from each chat response into your metrics backend, poll GET /api/v1/key for remaining budget, and use Activity UI or the Analytics API for model/key rollups when investigating spikes.
Recipe
- Emit structured logs on every OpenRouter call:
generation_id,model,provider(if present), tokens,usage.cost, route tier, agent name, tenant id. - Treat inline
usageas the real-time source of truth (always included; legacyusage.includeflags are deprecated/no-ops). - On streaming, read usage from the final chunk.
- Poll
GET /api/v1/keyevery N minutes (or before batches) forlimit_remaining,usage_daily, and friends. - For spike analysis, create a management key and query Analytics (
/api/v1/analytics/metathen/api/v1/analytics/query) or use the Activity dashboard. - Optionally fetch
/api/v1/generation?id=...(path verify at build) for historical cost audit by generation id. - Alert on: daily spend velocity, 402 rate, cost per successful task, and sudden model-mix shifts toward frontier slugs.
Working Example
import os
import time
import requests
from openai import OpenAI
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"],
)
def chat_and_meter(messages, model="openai/gpt-4o-mini", **provider):
t0 = time.perf_counter()
resp = client.chat.completions.create(
model=model,
messages=messages,
extra_body={"provider": provider} if provider else None,
)
latency_ms = (time.perf_counter() - t0) * 1000
usage = resp.usage
# OpenAI SDK may expose usage as object; normalize
cost = getattr(usage, "cost", None)
if cost is None and isinstance(usage, dict):
cost = usage.get("cost")
record = {
"id": resp.id,
"model": resp.model,
"latency_ms": latency_ms,
"prompt_tokens": getattr(usage, "prompt_tokens", None),
"completion_tokens": getattr(usage, "completion_tokens", None),
"cost": cost,
}
print("meter", record) # replace with metrics.export
return resp.choices[0].message.content, record
chat_and_meter([{"role": "user", "content": "hello"}], sort="price")
# Key-level remaining budget
key = requests.get(
"https://openrouter.ai/api/v1/key",
headers={"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}"},
)
key.raise_for_status()
print("key_budget", key.json()["data"])Analytics rollup (management key; API is beta - verify schema at build):
import os
import requests
from datetime import datetime, timedelta, timezone
mgmt = os.environ["OPENROUTER_MANAGEMENT_KEY"]
headers = {
"Authorization": f"Bearer {mgmt}",
"Content-Type": "application/json",
}
meta = requests.get("https://openrouter.ai/api/v1/analytics/meta", headers=headers)
meta.raise_for_status() # discover metrics/dimensions; do not hardcode forever
end = datetime.now(timezone.utc)
start = end - timedelta(days=7)
query = {
"metrics": ["total_usage", "request_count", "tokens_total", "cache_hit_rate"],
"dimensions": ["model"],
"order_by": {"field": "total_usage", "direction": "desc"},
"time_range": {
"start": start.strftime("%Y-%m-%dT%H:%M:%SZ"),
"end": end.strftime("%Y-%m-%dT%H:%M:%SZ"),
},
"limit": 20,
}
resp = requests.post(
"https://openrouter.ai/api/v1/analytics/query",
headers=headers,
json=query,
)
resp.raise_for_status()
print(resp.json())Deep Dive
Three horizons of observability
| Horizon | Source | Use |
|---|---|---|
| Per request (seconds) | Response usage | Live dashboards, unit economics |
| Per key (minutes) | GET /api/v1/key | Cap enforcement, burn alerts |
| Per org (hours/days) | Activity UI / Analytics API | Model mix, tenant keys, regressions |
Cost fields
Inline usage commonly includes:
prompt_tokens/completion_tokens/total_tokenscost- charged amountcost_details.upstream_inference_cost- upstream charge (especially meaningful for BYOK contexts; generation lookup notes differ - verify)- Cache and reasoning token details when applicable
For streaming, ignore intermediate chunks without usage; meter the final usage payload.
Credits endpoint
OpenRouter also exposes credit balance APIs for account-level remaining credits (see credits API reference at build). Use that for org dashboards; use /key for the specific runtime credential.
Generation id follow-up
Store resp.id from completions. Later, generation metadata endpoints let you re-read cost for disputes and audits without trusting only local logs.
Agent-specific metrics
Track more than tokens:
- Cost per successful task (not per call)
- Calls per task (loop amplification)
- Fallback rate (provider or model changes)
- 402 / 429 counts
- Share of spend on hard-tier models
A stable token rate with rising calls/task means the agent, not the price sheet, regressed.
Gotchas
- Analytics need a management key. Inference keys get 403 on admin analytics.
- Truncated analytics pages. Check
metadata.truncatedbefore summing. - Dimension limits. Analytics may allow at most two dimensions per query (beta behavior - verify).
- SDK shape drift.
usage.costmay needmodel_extraor raw JSON depending on SDK version. - Clock skew on daily usage. OpenRouter daily windows are UTC.
- Logging prompts by default is a privacy incident. Meter tokens/cost/ids; sample raw prompts only in secure stores with policy approval.
- Real-time is not infinite cardinality. Bound labels (agent, tier, model) to avoid metrics explosions.
Alternatives
| Stack | Pros | Cons |
|---|---|---|
| Inline usage + your APM | True real-time | You build rollups |
| OpenRouter Activity UI | Fast human debug | Not automated |
| Analytics API | Org-scale queries | Management key, beta churn |
| External LLM observability (Langfuse, etc.) | Traces + cost | Extra vendor |
| Provider consoles only | Native discounts view | No multi-model unity |
FAQs
Is usage always present without extra flags?
Yes under current OpenRouter usage-accounting docs. Old usage: {include: true} parameters are deprecated and unnecessary.
How do I monitor streaming agents?
Accumulate content from deltas; read usage from the final SSE event or SDK final chunk.
Can I alert before the key hard-fails?
Yes. Poll limit_remaining and alert at 20% / 5% thresholds so humans act before 402s.
What is a good burn-rate alert?
Compare today's usage_daily to the 7-day median by key. Alert on 2-3× spikes plus absolute dollar ceilings.
Does Activity include provider breakdown?
The Activity UI supports filtering by model, provider, and API key. Use it when a single slug's cost jumps after a routing change.
How do I attribute cost to end users?
Send stable user/app metadata if you use OpenRouter user-tracking features, and always log your own tenant_id beside usage.cost.
Will fallbacks confuse my model dashboards?
If model fallbacks fire, resp.model may differ from the requested primary. Group spend by served model, and tag requested model separately.
Where should I start if I only have an hour?
Log usage.cost + model per call, poll /api/v1/key for remaining, and open the Activity page for last 24h by key.
Related
Related: Setting Spend Limits and Usage Caps per API Key
Related: Cost-Based Routing: Sending Cheap Tasks to Cheap Models
Related: Comparing OpenRouter's Per-Model Pricing Against Direct Provider Rates
Stack versions: Pins from the category manifest (verify at build): OpenRouter (~315+ models, July 2026 pricing/fees); LangGraph 1.0+; CrewAI 1.14+; Microsoft Agent Framework 1.0; Vercel AI SDK 6; Pydantic AI (latest); LlamaIndex (latest); OpenAI Agents SDK (latest + MCP); MCP (Linux Foundation governance); A2A (HTTP+SSE+JSON-RPC 2.0); Solana
@solana/web3.js+@solana/spl-token.