Usage API
A read-only, cached JSON API that reports current usage across every account in your pool, Claude and Codex, plus your combined estimated AI spend.
If you pool multiple accounts, Hep.gg can expose their live usage as a single read-only JSON endpoint. Both pools are included: your Claude accounts and your Codex accounts. It's meant to be the source of truth for your own dashboards: it polls Anthropic on a safe schedule, caches the result, and serves from that cache, so your tools can poll it as often as they like without ever hitting Anthropic directly (and without burning your per-account rate limits).
Get a usage token
ccas usage-token # show (creates one if you don't have it)
ccas usage-token new # rotate (invalidates the old one)You can also reveal/rotate it from the AI → Accounts page in the dashboard. Give consumers the token as a bearer header.
Endpoint
https://ai.hep.gg/usageAuth requiredhttps://ai.hep.gg/usage/{id}Auth requiredAuthenticate with Authorization: Bearer <usage-token>.
{
"version": 1,
"fetched_at": "2026-05-31T18:00:00Z",
"accounts": [
{
"id": "stable-unique-id",
"label": "Work Max",
"plan": { "rate_limit_tier": "default_claude_max_5x", "label": "Max 5x" },
"status": "ok",
"error": null,
"fetched_at": "2026-05-31T17:59:40Z",
"windows": {
"five_hour": { "utilization": 35.2, "resets_at": "2026-05-31T21:30:00Z", "expected": 28.0, "pace_delta": 7.2, "pace": "high" },
"seven_day": { "utilization": 5.0, "resets_at": "2026-06-04T09:00:00Z", "expected": 8.0, "pace_delta": -3.0, "pace": "under" },
"weekly_scoped": { "utilization": 0.0, "resets_at": "2026-06-04T09:00:00Z", "model": "Fable", "pace": "none" }
},
"raw_usage": { }
}
],
"codex_accounts": [
{
"id": "codex:0f2b...",
"provider": "codex",
"label": "Personal Plus",
"plan": { "rate_limit_tier": "plus", "label": "plus" },
"active": true,
"status": "ok",
"error": null,
"fetched_at": "2026-05-31T17:59:55Z",
"window_list": [
{ "key": "five_hour", "label": "5-hour", "utilization": 41.0, "resetsAt": "2026-05-31T21:30:00Z", "windowMinutes": 300, "expected": 33.0, "paceDelta": 8.0, "pace": "high" },
{ "key": "seven_day", "label": "Weekly", "utilization": 31.0, "resetsAt": "2026-06-04T09:00:00Z", "windowMinutes": 10080, "pace": "under" }
],
"raw_usage": { }
}
],
"all_accounts": [ ],
"spend": { "total_usd": 1234.56, "claude_usd": 1100.00, "codex_usd": 134.56 }
}Both pools
accountsis your Claude pool, unchanged.codex_accountsis your Codex pool.all_accountsis the same rows as one flat list, each tagged withprovider(claudeorcodex). Read this one if you want to render everything with a single code path.
Every account also carries a window_list: the same usage windows as a plain
array, in display order, each with a key, a human label, its length in
windowMinutes, and the on-pace fields below. Claude accounts keep their keyed
windows object as well, so nothing that already reads it needs to change.
Prefer window_list for new code. Codex plans do not all report the same
windows (most report a 5-hour and a weekly one, some report a single longer
window instead), so reading the list and its labels is the only way to render
whatever a plan actually has.
Codex ids are prefixed codex:. That is deliberate, it means
POST /usage/accounts/{id}/activate works for either pool from the id alone.
Spend
spend reports your estimated AI spend in USD: total_usd plus the
claude_usd and codex_usd split. It is an estimate priced at each provider's
public API list prices, not a bill. Subscription traffic has no per-request
invoice to reconcile against, so treat it as a comparable measure of what your
usage would have cost on the API.
Field contract
id- stable per account across restarts; key your display/visibility off it. Codex ids are prefixedcodex:.provider-claudeorcodex.label- human name (your slot label or the account email); optional.plan.rate_limit_tier- the raw tier (e.g.default_claude_max_20x,default_claude_max_5x,default_claude_ai).plan.labelis a friendly form.status- one of:ok- last fetch succeeded.rate_limited- upstream returned 429/5xx/timeout; last good data is kept.auth_error- the account needs re-auth. Re-add it withccas account login(a dedicated authorization that won't drop again), orccas account addto re-snapshot.error- anything else.
error- short string whenstatus != ok, elsenull.fetched_at(per account) - UTC of the last successful fetch for that account. It keeps returning the last goodwindowswhilestatusis degraded, so an "updated N ago" stays accurate.windows.*- keys:five_hour,seven_day(weekly, all models), andweekly_scoped(the per-model weekly cap, for example Fable). Each is{ "utilization": 0..100, "resets_at": <ISO8601 UTC>, … }, ornullif that window doesn't apply.utilizationis a percentage.weekly_scopedalso carries amodellabel; Anthropic rotates which model the weekly cap targets, so read the label rather than hardcoding it. Any new windows Anthropic adds appear underraw_usagewithout an API change.- On-pace fields (per window, added server-side): tell you whether you're
burning faster or slower than a steady rate that would hit 100% exactly at reset.
expected- theutilizationyou'd be at right now if perfectly on pace (elapsed fraction of the window x 100).pace_delta-utilization - expected. Positive = ahead of pace (burning fast).pace- a bucket for easy coloring:none(0% used or no reset time -> render neutral/white),under(behind pace -> green),over(0-5% ahead -> yellow),high(5%+ ahead -> red).expected/pace_deltaare omitted whenpaceisnone.
window_list- every window as an array (see Both pools above). Same numbers aswindows, in a provider-neutral shape, withresetsAt,windowMinutes,expected,paceDeltaandpace.raw_usage- the verbatim upstream usage object, so new windows appear without an API change.
Guarantees
- All timestamps are ISO 8601 UTC.
- Served from cache, a consumer request never triggers an upstream call.
- Returns
200with per-accountstatus; one failing account doesn't fail the whole response. ETagis supported for conditional GETs, and covers both pools.POST /usage/accounts/{id}/activateswitches the active account in whichever pool the id belongs to.- Tokens are never exposed in any response.