Skip to content

Instantly share code, notes, and snippets.

@nickfarrow
Created May 15, 2026 05:56
Show Gist options
  • Select an option

  • Save nickfarrow/111ff798a119d2796ec9ddd5a83b2b78 to your computer and use it in GitHub Desktop.

Select an option

Save nickfarrow/111ff798a119d2796ec9ddd5a83b2b78 to your computer and use it in GitHub Desktop.
fetch claude usage in json, claude code reverse engineered by claude code
#!/usr/bin/env python3
"""Fetch Claude Code subscription usage across windows and models.
Hits the same `/api/oauth/usage` endpoint that the in-app `/usage` command
uses, authenticated with the OAuth token stored by `claude auth login`.
Library use:
from claude_usage import get_usage
data = get_usage() # dict — raw API response
"""
from __future__ import annotations
import json
import sys
import urllib.error
import urllib.request
from pathlib import Path
CREDS_PATH = Path.home() / ".claude" / ".credentials.json"
USAGE_URL = "https://api.anthropic.com/api/oauth/usage"
class UsageError(RuntimeError):
"""Raised when usage cannot be retrieved."""
def _load_token(creds_path: Path = CREDS_PATH) -> str:
try:
creds = json.loads(creds_path.read_text())
except FileNotFoundError as e:
raise UsageError(f"{creds_path} not found — run `claude auth login` first") from e
token = (creds.get("claudeAiOauth") or {}).get("accessToken")
if not token:
raise UsageError("no accessToken in credentials")
return token
def get_usage(token: str | None = None, timeout: float = 15.0) -> dict:
"""Return the parsed usage JSON from the Claude OAuth API.
Pass an explicit `token` to skip reading the credentials file.
"""
if token is None:
token = _load_token()
req = urllib.request.Request(
USAGE_URL,
headers={
"Authorization": f"Bearer {token}",
"anthropic-beta": "oauth-2025-04-20",
"Accept": "application/json",
},
)
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
return json.load(resp)
except urllib.error.HTTPError as e:
body = e.read().decode("utf-8", errors="replace")
raise UsageError(f"HTTP {e.code} from {USAGE_URL}: {body}") from e
except urllib.error.URLError as e:
raise UsageError(f"could not reach {USAGE_URL}: {e.reason}") from e
def main() -> int:
try:
data = get_usage()
except UsageError as e:
print(f"error: {e}", file=sys.stderr)
return 1
json.dump(data, sys.stdout, indent=2)
sys.stdout.write("\n")
return 0
if __name__ == "__main__":
sys.exit(main())
@nickfarrow
Copy link
Copy Markdown
Author

Example output:

{
  "five_hour": {
    "utilization": 5.0,
    "resets_at": "2026-05-15T09:50:00.769147+00:00"
  },
  "seven_day": {
    "utilization": 2.0,
    "resets_at": "2026-05-21T00:00:00.769165+00:00"
  },
  "seven_day_oauth_apps": null,
  "seven_day_opus": null,
  "seven_day_sonnet": {
    "utilization": 0.0,
    "resets_at": null
  },
  "seven_day_cowork": null,
  "seven_day_omelette": {
    "utilization": 0.0,
    "resets_at": null
  },
  "tangelo": null,
  "iguana_necktie": null,
  "omelette_promotional": null,
  "extra_usage": {
    "is_enabled": false,
    "monthly_limit": null,
    "used_credits": null,
    "utilization": null,
    "currency": null,
    "disabled_reason": null
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment