Every AI coding agent, plugin, and status bar eventually wants to answer one small question for its user: how much have I used, and when do I get cut off?
You'd expect a one-liner for that. GET /v1/usage, send the same API key you use for inference, get a number back. We went looking for the standard way to do this — across OpenAI, Anthropic, Codex, Claude Code, OpenCode, and every major AI gateway — because we wanted GoModel to follow it.
Here's the punchline: there is no standard. There isn't even a common convention. What we found instead was a landscape of admin-only reporting APIs, private endpoints reserved for first-party tools, and community plugins literally scraping HTML dashboards with browser cookies.
What the big providers give you: nothing (on purpose)
OpenAI has a real Usage API - GET /v1/organization/usage/completions, plus a Costs API. It's well designed: paginated time buckets, group-by model or API key, cached-token breakdowns. But it requires a separate Admin API key that only an org owner can mint, and that admin key can't do inference. The key your app actually holds can't ask about itself at all. The old self-service endpoints people remember (GET /v1/usage?date=..., /v1/dashboard/billing/usage) were never official and have been locked to browser sessions since 2023.
Anthropic made the same call. Usage and cost reports live under /v1/organizations/usage_report/* and need an sk-ant-admin... key. They even ship a read-only Rate Limits API that names "sync your gateway with your org limits" as the use case — and gated that behind the admin key too.
With a regular key, both vendors give you exactly one thing: rate-limit response headers (x-ratelimit-remaining-tokens, anthropic-ratelimit-requests-remaining, ...) on each inference call. Useful, but reactive — you learn your headroom as a side effect of spending it, and you learn nothing about budgets or historical usage.
Meanwhile, their own tools poll private endpoints
The funny part: both companies clearly know consumers need this, because their own products have it — privately.
-
Codex polls
chatgpt.com/backend-api/wham/usage(OAuth-only, undocumented) about once a minute to render its usage display. The response is percent-of-window:{"primary_window": {"used_percent": 12, "reset_at": ...}}. -
Claude Code's
/usagecommand callsapi.anthropic.com/api/oauth/usage— also undocumented, also OAuth-only, and it even rate-limits you into oblivion if you don't send the rightUser-Agent. Community usage monitors reverse-engineered it anyway.
So the pattern at the top of the market is: self-service usage exists, but only for our own clients, on internal endpoints you're not supposed to use.
And the tools downstream are hurting
The demand side is not subtle. The example that motivated this work: OpenCode — the open-source coding agent — has a gateway (Zen) with real usage windows and balances, and no API for any of it. The result:
- A community plugin (
opencode-quota) scrapes the billing dashboard's HTML using browser cookie auth to display quota percentages. - There are open issues asking for
GET /zen/v1/balanceand usage-window endpoints, and an open proposal forGET /zen/go/v1/usagereturning{"windows": [{"name": "5 hour", "usagePercent": 42, "resetInSec": 4711}]}. - Ollama Cloud users are filing the same issues (
/api/me,/api/usage) for the same reason.
When your users are scraping your dashboard with cookies, they have told you what API they want.
The closest thing to a convention
Two gateways actually shipped self-service introspection, and one got close:
-
OpenRouter:
GET /api/v1/keywith your normal inference key returnsusage,usage_daily/weekly/monthly,limit,limit_remaining,limit_reset. This is the de-facto reference design. -
Requesty:
GET /v1/manage/apikey/selfreturnsmonthly_spendandmonthly_limitwith the same bearer key. -
LiteLLM:
GET /key/infowith no parameter defaults to the calling key and returnsspend,max_budget,budget_reset_at,tpm_limit,rpm_limit.
Distill those and you get the only "standard" that exists — a design pattern, not a schema: GET, same base URL, same bearer key, self-referential, no parameters required. Plus rate-limit headers on inference responses as the complementary channel.
How GoModel does it
GoModel now answers the whole question in one call. GET /v1/usage, authenticated with the same key you use for chat completions:
curl http://localhost:8080/v1/usage \
-H "Authorization: Bearer sk_gom_..."
{
"user_path": "/team/alpha",
"server_time": "2026-07-07T09:30:00Z",
"usage": {
"start_date": "2026-06-08",
"end_date": "2026-07-07",
"total_requests": 128,
"total_tokens": 91234,
"cached_input_tokens": 12790,
"total_cost": 1.73
},
"budgets": [
{
"user_path": "/team/alpha",
"period_label": "monthly",
"amount": 100,
"spent": 41.2,
"remaining": 58.8,
"usage_ratio": 0.412,
"period_end": "2026-08-01T00:00:00Z",
"resets_in_seconds": 2125800,
"exceeded": false
}
],
"rate_limits": [
{
"user_path": "/team/alpha",
"period_label": "minute",
"max_requests": 60,
"requests_used": 3,
"requests_remaining": 57,
"requests_usage_ratio": 0.05,
"window_end": "2026-07-07T09:31:00Z",
"resets_in_seconds": 60,
"exhausted": false
}
]
}
Three blocks, three questions answered:
-
usage— requests, tokens (with cache breakdowns), and estimated cost over a date window you control (days, orstart_date/end_date, up to 365 days; last 30 by default). -
budgets— every spend limit that gates you, including ones inherited from parent paths, withspent,remaining, the period bounds, and anexceededflag that mirrors what enforcement will actually do. -
rate_limits— live counters for every rate-limit rule covering you: used, remaining, and when the window resets.
A few design decisions worth explaining:
It's scoped by user path, not by key. In GoModel, budgets, rate limits, and usage accounting all hang off a hierarchical user_path (like /team/alpha/service), and API keys bind to a path. Several keys can share one path, and when we add a full users system, a user will simply be a path. Every self-service precedent out there is key-centric; ours is identity-centric — but from the caller's perspective it behaves identically: send your key, get your own status, and you can't read outside your subtree.
Polling is free. The endpoint doesn't consume rate-limit quota, so a status widget checking every few seconds never eats into the budget it's displaying. (Codex polls its internal endpoint every 60 seconds for the same reason.)
Absolute numbers and the percentages. The consumer-facing idiom at OpenAI and Anthropic is percent-of-window (used_percent, utilization) — deliberately opaque about actual quotas. We return real counts and real money with real window boundaries, and then precompute the display idiom anyway. Every budget carries a usage_ratio, a resets_in_seconds countdown, and an exceeded flag. Every rate limit carries an exhausted flag, a usage ratio per limited dimension (requests_usage_ratio, tokens_usage_ratio), and — for windowed rules — the same countdown; a concurrency cap has no window, so it reports in-flight slots instead. Countdowns are relative to the server_time in the same payload, so client clock skew can't break them, and both flags mirror what enforcement will actually do. A status bar renders this with zero math; a plugin that wants the raw numbers has them too.
Headers still work. GoModel keeps emitting OpenAI-style x-ratelimit-* headers on inference responses, so the endpoint and the in-band signals tell one consistent story.
The endpoint is in GoModel today, and it exists because someone asked for exactly the OpenCode-plugin use case described above. Full request and response reference — every field, the date window, the error cases — is in the GoModel Usage API docs.
The takeaway
Usage visibility has quietly become admin-only across the LLM ecosystem — not because it's hard, but because nobody made it part of the API contract. The result is a cottage industry of OAuth token borrowing, dashboard scraping, and GitHub issues asking for the same three fields: used, limit, resets at.
If you run a gateway, your callers shouldn't need admin access to know when they're about to hit a wall. Give them one GET.
Top comments (0)