OpenUsage exposes a read-only HTTP API on the loopback interface so other local apps can consume the same usage data shown in the menu bar.
Base URL: http://127.0.0.1:6736
The server starts automatically with the app. If the port is already in use, the feature is silently disabled for that session.
Returns a machine-facing envelope for all enabled providers. Providers and resources are keyed by
stable IDs; values are raw scalars with explicit units. This is the preferred route for new integrations
and the exact format printed by the openusage CLI.
Returns the same envelope containing every provider the ID names. It works for disabled providers too.
Matching is plain string comparison: an exact provider ID names that provider, and a family ID
(claude, codex) names every account card of that family — with one account that's exactly the one
card. There is no aliasing or "pick the right account" logic; the same request always names the same
providers.
- 200 OK — limits envelope with every matched provider that has data (an
errorsentry appears when a refresh failed; a matched provider with no data yet simply has no entry). - 404 Not Found — the ID names no known provider and no family.
Returns the legacy UI-oriented snapshots for all enabled providers, in your dashboard order. Existing
consumers remain supported while this route is deprecated; new consumers should use /v1/limits.
Both routes read the same rendered provider snapshots. When iCloud Sync is on, that means they both see
the same iCloud-combined usage as the dashboard; /v1/usage returns the old UI-oriented shape, while
/v1/limits projects the data into stable resource IDs and raw scalar values.
- 200 OK — JSON array (may be empty
[]if nothing has been fetched yet).
Returns the latest snapshots for every provider the ID names (same matching as /v1/limits/:id).
Works for disabled providers too.
- 200 OK — JSON array, one snapshot per matched provider that has one (
[]when none do yet). - 404 Not Found — the ID names no known provider and no family.
Breaking change: this route previously returned a single JSON object and
204when the provider had no snapshot. It now always returns an array, so the shape stays identical whether an ID names one provider or a whole account family.
Methods other than GET/OPTIONS return 405; unknown routes return 404. When the server is already handling its maximum of 16 concurrent connections, requests get 503 — back off and retry.
kind is consumption (used) or balance (available). Bounded consumption also carries limit,
remaining, and a 0–1 utilization. Reset, window, expiry-list, and estimated fields appear only when
the provider supplies that meaning. A provider or resource with no current value is omitted rather than
invented as zero. expiresAt is always fetchedAt plus the same five-minute freshness interval used by
the app and CLI; stale says whether that instant has passed. Refresh failures appear in errors as
{"providerId":"…","message":"…"} while a last-good provider snapshot remains available.
For bounded progress resources, unit follows the provider's live metric format. For example, Cursor
totalUsage is percent on percentage-based plans, requests on request-based Enterprise plans, and
usd when Cursor reports a dollar pool.
| Provider | Resource keys |
|---|---|
| Claude | session, weekly, sonnet, fable, extraUsage |
| Codex | session, weekly, spark, sparkWeekly, credits, creditValue, rateLimitResets |
| Cursor | totalUsage, autoUsage, apiUsage, onDemand, requests, credits |
| Antigravity | geminiSession, geminiWeekly, nonGeminiSession, nonGeminiWeekly |
| Copilot | premiumCredits, extraUsage, orgCredits, orgSpend, chat, completions |
| Devin | daily, weekly, extraUsageBalance |
| Grok | weekly |
| OpenCode | session, weekly, monthly |
| OpenRouter | credits, balance, keyLimit |
| Z.ai | session, weekly, webSearches |
Charts, colors, subtitles, formatted badges, layout state, and historical spend periods stay out of this
contract. Codex's combined Credits UI row becomes two scalar resources: credits and creditValue.
{
"providerId": "claude",
"displayName": "Claude",
"plan": "Team 5x",
"lines": [
{
"type": "progress",
"label": "Session",
"used": 42.0,
"limit": 100.0,
"format": { "kind": "percent" }, // or "dollars", or "count" (+ "suffix")
"resetsAt": "2026-03-26T13:00:00.161Z", // optional
"periodDurationMs": 18000000, // optional
"color": null
},
{
"type": "text",
"label": "Today",
"value": "$5.17 · 9.2M tokens",
"color": null,
"subtitle": null
},
{
"type": "badge",
"label": "Pay as you go",
"text": "2500 cap",
"color": "#22c55e",
"subtitle": null
},
{
"type": "barChart",
"label": "Usage Trend",
"points": [
{ "label": "Mar 25", "value": 1200000.0, "valueLabel": "1.2M tokens" },
{ "label": "Mar 26", "value": 2400000.0, "valueLabel": "2.4M tokens" }
],
"note": "Estimated from local Claude logs at API rates.",
"color": null
}
],
"fetchedAt": "2026-03-26T11:16:29.000Z"
}Line types are progress, text, badge, and barChart. A barChart line carries a points array — one { label, value, valueLabel? } per day, oldest first — plus an optional note; value is the day's token count, valueLabel its pre-formatted readout, and label a localized month/day (e.g. "Mar 25"). fetchedAt is when the snapshot was last fetched successfully (ISO 8601).
The in-app model breakdown shown when hovering spend rows is not included in this API yet. Spend rows continue to serialize as the same text lines so existing local integrations keep their current shape.
In both response shapes, displayName is the card's current name — if you renamed a card in the app, the rename shows here too. Match on providerId (or the envelope key), never on the name.
{ "error": "provider_not_found" }Codes: provider_not_found, not_found, method_not_allowed, server_busy.
All responses include permissive CORS headers (Access-Control-Allow-Origin: *, methods GET, OPTIONS). OPTIONS requests return 204 for preflight.
The server only listens on the loopback interface (127.0.0.1), so it is not reachable from other machines on your network. Because the CORS header is permissive, though, a web page open in your browser can read your usage snapshots from this API while the app is running. The data exposed is the same usage numbers shown in the menu bar — no credentials or tokens are ever served. This matches the original app's behavior so existing integrations keep working.
The API serves whatever the app is showing: only successful fetches replace data, so a failed refresh never blanks the API — you keep getting the last good snapshot. See Refreshing & caching.
{ "schema": "openusage.limits.v1", "generatedAt": "2026-07-13T01:40:00.000Z", "providers": { "codex": { "displayName": "Codex", "plan": "Pro 20x", "fetchedAt": "2026-07-13T01:39:30.000Z", "expiresAt": "2026-07-13T01:44:30.000Z", "stale": false, "resources": { "session": { "kind": "consumption", "unit": "percent", "used": 42, "limit": 100, "remaining": 58, "utilization": 0.42, "resetsAt": "2026-07-13T06:00:00.000Z", "windowSeconds": 18000 }, "credits": { "kind": "balance", "unit": "credits", "available": 821 } } } }, "errors": [] }