CLAUDE LABJP
VERSION — v2.1.227 landed on August 10 with no new features, just fixes around plan detection and CI behaviorAUTO — Two days remain until August 14, when auto mode becomes the default in Claude Code for Pro, Max, and TeamCI — Bash commands no longer fail across the board under claude-code-action with allowed_non_write_users on GitHub-hosted runnersBILLING — Sessions started with an expired token could misread your plan and nudge Max users toward usage credits; that is now fixedSUNSET — The legacy Workbench and the experimental prompt tool APIs retire on August 17, five days outPRICE — Sonnet 5 promo pricing at $2/$10 per Mtok runs through August 31, moving to $3/$15 on September 1VERSION — v2.1.227 landed on August 10 with no new features, just fixes around plan detection and CI behaviorAUTO — Two days remain until August 14, when auto mode becomes the default in Claude Code for Pro, Max, and TeamCI — Bash commands no longer fail across the board under claude-code-action with allowed_non_write_users on GitHub-hosted runnersBILLING — Sessions started with an expired token could misread your plan and nudge Max users toward usage credits; that is now fixedSUNSET — The legacy Workbench and the experimental prompt tool APIs retire on August 17, five days outPRICE — Sonnet 5 promo pricing at $2/$10 per Mtok runs through August 31, moving to $3/$15 on September 1
Articles/Claude Code
Claude Code/2026-06-26Intermediate

We Collected Plenty of Claude Code Usage Data — Then Budgeting Still Didn't Work. Field Notes on Cost Attribution and Idle Seats

The Claude Code Analytics API gives you data, but data alone doesn't run a budget. Here are field notes on turning raw usage logs into cost attribution, idle-seat detection, and alerts that don't cry wolf — with working code and the thresholds we actually use.

claude-code129analytics3cost2monitoring9teambudget2

Premium Article

The data was there, but the meeting couldn't use it

When we rolled out the Claude Code Analytics API, the first thing I built was a dashboard of daily token consumption and cost. The numbers came out cleanly. But every monthly budget meeting stalled on the same question: "This figure — which work did it pay for?"

You can see per-seat consumption. The trouble is that seats and work aren't one-to-one. One person spans several repositories, spends one week firefighting an incident and the next writing a feature in a sprint. Per-seat numbers answer "who used it"; what budgeting actually wants to know is "what it was used on." Running several apps and blogs in parallel as an indie developer myself, I'd already learned that cost has to be tied to work, not to people, before it becomes something you can act on.

This article walks through the three stages it took to turn the API's raw data into something usable in a budget meeting — attributing cost to workstreams, detecting idle seats, and building alerts that don't go stale — with the code we actually run. The focus isn't setup; it's the part where you get stuck after the data is already flowing.

First, pin down the granularity of the raw data

The Analytics API returns metrics per day. The first job is to understand exactly what granularity comes back. Get vague here and every downstream cost attribution drifts.

import os
import httpx
 
BASE = "https://api.anthropic.com/v1/organizations/usage_report/claude_code"
 
def fetch_daily(starting_at: str, ending_at: str) -> list[dict]:
    """Fetch daily Claude Code usage records.
    Note: one record = one day x one user x one model."""
    headers = {
        "x-api-key": os.environ["ANTHROPIC_ADMIN_API_KEY"],
        "anthropic-version": "2023-06-01",
    }
    records, page = [], None
    with httpx.Client(timeout=30) as client:
        while True:
            params = {"starting_at": starting_at, "ending_at": ending_at, "limit": 1000}
            if page:
                params["page"] = page
            r = client.get(BASE, headers=headers, params=params)
            r.raise_for_status()
            body = r.json()
            records.extend(body["data"])
            page = body.get("next_page")
            if not page:
                break
    return records

Two things matter here. First, each record is a "day x user x model" tuple. If the same person uses both Sonnet and Opus on the same day, their single day splits into two records. Aggregate over the wrong key and a mixed-model seat double-counts or quietly drops cost.

Second, always page all the way through. A larger limit won't return everything; you have to loop until next_page is null. On a large team near month-end, skipping this silently loses a few days of data. I missed this early on and once argued a budget off the first-of-month numbers alone — and was wrong.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
An aggregation that attributes cost to workstreams instead of seats, and why that framing changes the conversation
Idle-seat logic that surfaces the seats you pay for but nobody actually uses, every week
A three-part alert threshold (week-over-week, moving average, absolute floor) that stays meaningful instead of going stale
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Claude Code2026-04-29
Observability for Claude Code with OpenTelemetry — A Production-Grade Tracing Guide for Agentic Workflows
Trace Claude Code agent runs end to end with OpenTelemetry. Hook integration, per-tool spans, MCP propagation, cost attribution, and sampling patterns that survive thousands of runs per day.
Claude Code2026-07-17
The Morning My Table Ended in "… 2,847 more rows" — Separating Render Caps from Token Cost in Tool Output
Claude Code 2.1.209 caps markdown tables at 200 rows plus a remainder count. Only the rendering is capped — the model still receives every row. Here is how to measure the gap and redesign tool output around aggregates.
Claude Code2026-07-15
Answering auto mode's confirmation prompts in headless runs — a deny-by-default permission-prompt-tool
auto mode's confirmation step is a friend when you're at the keyboard, but in an unattended midnight run it becomes the reason a job sits waiting until morning. Here is how I catch those prompts with permission-prompt-tool, decide deny-by-default, and log every ruling — with working code.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →