Diagnostic LLM APIs
code execution has spent its free 1,550 container hours
The analytics route attaches the customer's CSV to the request, because sometimes the question is about the numbers in it and when it is, the model should be able to run something. Sometimes is about one call in forty. The other thirty-nine attach the file, spin up a container to hold it, ask something that needs no arithmetic at all, and are billed for five minutes of a machine that was never asked to do anything.
One call with an Admin API key: GET /v1/organizations/cost_report?starting_at={month start}&limit=31&group_by[]=description&group_by[]=workspace_id. Keep the results whose cost_type is code_execution and sum amount per workspace.
Any non-zero total is the finding. There is no threshold to argue about. Each organization gets 1,550 free container hours a month, they are consumed before anything is charged, and billing at $0.05 per container-hour starts only once they are gone. A dollar on that row means the allowance is already spent.
Do not go looking for this in GET /v1/organizations/usage_report/messages. Code execution is excluded from that report entirely — no field, no grouping, no counter. It exists on the invoice and nowhere else, which is why a dashboard built on the usage report can be complete, correct and blind to it at the same time.
The problem in plain words
Two things make this expensive quietly rather than loudly. The first is that a container is billed for wall-clock time with a five-minute minimum per execution, so a request that runs one line of Python for four hundred milliseconds costs the same as one that grinds for five minutes. A route that calls the tool often and briefly is the worst possible shape, and it is also the most common one.
The second is worse, because it does not require the tool to be called at all. If files are attached to a request, they are preloaded onto a container, and container time accrues whether or not the model ever reaches for the tool. So the cost is not driven by how often the model decides to compute something. It is driven by how often your code decides to attach a file just in case, which is a decision made once, in a code path, and never revisited.
Nothing in the API is going to raise this. The cost report will show it if you ask for the right cost_type, and the usage report will not show it under any circumstances, so the natural monitoring surface for an LLM integration — tokens over time, per model, per key — has no way to represent it. The first signal most teams get is the invoice.
Why it happens
The allowance is the threshold, and the platform has already applied it. 1,550 container hours per organization per month, free, consumed first. You are not comparing a number against a budget; the platform did that before it wrote the row. Reading a non-zero amount and inferring "the allowance is gone" is exact, not heuristic.
The messages usage report cannot see this line. It carries token fields, a server_tool_use object and grouping dimensions, and none of them account for code execution. That is a structural blind spot rather than an omission you can grouping your way around, and it is the reason this note reads the cost report first and the usage report only to prove the absence.
Hours are derived, not reported. The API gives you dollars. At $0.05 per container-hour, dollars divided by the rate is billed hours, and adding the free 1,550 gives total container time for the month. The rate is a published price rather than a field, so the script keeps it as a named constant and lets you override it rather than pretending it read it.
The five-minute minimum turns hours into a ceiling on executions, not a count. Every execution bills at least five minutes, so twelve billed hours could be one long job or one hundred and forty-four short ones, and never more than that. Reporting the ceiling is honest; reporting an execution count would not be.
The charge can be removed rather than reduced. Code execution is free when bundled with a current web search or web fetch tool version (web_search_20260209, web_fetch_20260209 or later). That makes the repair a tool-version question as often as it is a usage question, which is not where anyone looks first.
The fix, as a flow
This line is not in the usage report at all: no field, no grouping, no counter. It exists as money and nowhere else. And because the free 1,550 container hours are consumed before anything is charged, there is no threshold to argue about on the fix side, only a question of how far past the allowance a workspace already is.
How to fix it
Align the window to the calendar month
The allowance resets monthly, so a rolling thirty-day window straddles two allowances and the arithmetic stops meaning anything. The script defaults starting_at to the first of the current month at midnight UTC, and takes --days only if you deliberately want a rolling read.
Group by description and workspace
GET /v1/organizations/cost_report?starting_at={month start}&limit=31&group_by[]=description&group_by[]=workspace_id. The workspace grouping is what makes the finding actionable: the org total tells you the allowance is gone, and the per-workspace split tells you whose route to go and read.
Filter on cost_type, and keep what you did not filter
Keep cost_type == "code_execution". Also keep a tally of every other cost_type the report returned, and print it. A cost type this script has never heard of is the next billable surface arriving, and a filter that silently discards everything it does not recognise is how it stays invisible for another quarter.
Prove the usage report cannot see it
Read one page of GET /v1/organizations/usage_report/messages and scan every field name in every result for anything mentioning code execution. There is nothing, which is the point: the script says so out loud rather than leaving the reader to wonder whether it simply forgot to look.
Convert to hours, print the two repairs
Dollars over $0.05 gives billed hours; add 1,550 for the month's total container time. Then print both repairs: find the routes attaching files to requests that never need the tool, and check whether pairing code execution with a current web search or web fetch tool version removes the charge outright.
How to check it worked
Detach the file from the route that does not need it, then re-read the same month. The code_execution amount should stop growing day over day; it will not fall, because you cannot un-spend an allowance.
python3 anthropic_code_execution_hours_audit.py
# allowance-spent wrkspc_01Qy $84.60 billed, which is 1692 container hour(s) on top of the free 1,550
# at the 5 minute minimum that is at most 20304 execution(s)
# repair: find the routes attaching files to requests that never call the tool
# repair: bundling code execution with web_search_20260209 or later removes the charge
# note: the messages usage report carries no code execution field at all
# 3 workspace(s) with cost, 1 finding(s)
The full code
One cost-report read and one page of the usage report, both GET, both against /v1/organizations/*, so this needs ANTHROPIC_ADMIN_KEY as an Admin key. The usage read is there only to demonstrate an absence. Six pure functions: parsing the decimal-string amount, folding cost into workspace and type, pulling out the one type that matters, converting dollars to container hours, bounding executions by the five-minute minimum, and the verdict — which has no threshold to tune, because the platform applied the only threshold there is before it wrote the row.
"""Report that Claude code execution has spent its free container hours.
Read only. GET requests and nothing else: ANTHROPIC_ADMIN_KEY must be an Admin
API key (sk-ant-admin...), which can be provisioned read-only. A workspace key
is rejected by every /v1/organizations/* path.
The finding has no threshold. Each organization gets 1,550 free container hours
a month and they are consumed before anything is billed, so a non-zero amount on
a code_execution cost row means the allowance is already gone. The messages
usage report does not carry this line under any grouping, which is why the check
lives on the cost report and reads usage only to prove the absence.
The repair is printed, never applied. Detaching a file from a request path is a
deploy, and changing a tool version changes what the model can do.
"""
import argparse
import datetime as dt
import logging
import os
import sys
import requests
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("anthropic_code_execution_hours_audit")
API = "https://api.anthropic.com/v1"
VERSION = "2023-06-01"
# Free container hours per organization per month. Consumed before anything is
# charged, which is what makes any non-zero amount a finding on its own.
FREE_CONTAINER_HOURS = 1550
# Published price per container hour, and the per-execution minimum. Both are
# prices rather than fields, so they are constants you can override rather than
# something this script pretends to have read from the API.
HOURLY_RATE = 0.05
MINIMUM_MINUTES = 5
COST_TYPE = "code_execution"
FINDINGS = ("allowance-just-crossed", "allowance-spent", "allowance-dwarfed")
def amount(row):
"""Read a cost row's amount as a float. Pure.
The cost report returns amount as a decimal STRING. Summing the raw values
concatenates them in one language and throws in the other.
"""
raw = (row or {}).get("amount")
if raw is None or raw == "":
return 0.0
try:
return float(raw)
except (TypeError, ValueError):
return 0.0
def fold(cost_buckets):
"""Sum spend into {workspace_id: {cost_type: dollars}}. Pure.
Every cost_type is kept, not just the one being looked for. A filter that
discards what it does not recognise is how the next billable surface stays
invisible for a quarter.
"""
out = {}
for bucket in cost_buckets or []:
for result in bucket.get("results") or []:
workspace = str(result.get("workspace_id") or "default workspace")
kind = str(result.get("cost_type") or "unspecified")
per_type = out.setdefault(workspace, {})
per_type[kind] = per_type.get(kind, 0.0) + amount(result)
return out
def code_execution_spend(folded, cost_type=COST_TYPE):
"""Dollars of code execution per workspace, zeros dropped. Pure."""
return {workspace: types[cost_type]
for workspace, types in (folded or {}).items()
if types.get(cost_type, 0.0) > 0}
def billed_hours(dollars, rate=HOURLY_RATE):
"""Container hours behind a dollar amount. Pure.
Rounded rather than left raw. Dollars are a decimal quantity and 0.05 has no
exact binary representation, so 84.60 / 0.05 comes out at 1691.9999999999998
and every later int() reports an hour that was never missing.
"""
if rate <= 0:
raise ValueError("rate must be positive")
return round(max(0.0, float(dollars or 0.0)) / rate, 6)
def executions_ceiling(hours, minimum_minutes=MINIMUM_MINUTES):
"""The most executions that could account for these hours. Pure.
A ceiling and never a count. Every execution bills at least the minimum, so
twelve hours is one long job or 144 short ones and nothing above that. The
API does not report an execution count, and inventing one would be worse
than saying how far the number could go.
"""
if minimum_minutes <= 0:
raise ValueError("minimum_minutes must be positive")
return int(max(0.0, float(hours or 0.0)) * 60.0 / minimum_minutes)
def usage_report_mentions_code_execution(pages):
"""Does the messages usage report carry this line anywhere? Pure.
The answer today is no, under any grouping, in any field. The check is here
so the script can state that as an observation rather than an assumption,
and so it starts reporting the field on the day one appears.
"""
for page in pages or []:
for bucket in page.get("data") or []:
for result in bucket.get("results") or []:
for name in result.keys():
if "code_execution" in str(name).lower():
return True
return False
def verdict(dollars, free_hours=FREE_CONTAINER_HOURS, rate=HOURLY_RATE,
marginal=5.0):
"""Classify one workspace's code execution spend. Pure. Returns (state, detail).
There is no threshold to tune. The platform consumed the free allowance
before it wrote the row, so zero means "inside the allowance" and anything
else means "past it". The marginal band exists only to keep the language
proportionate, not to suppress a finding.
"""
spend = float(dollars or 0.0)
if spend <= 0:
return ("within-allowance",
"no code_execution rows, so the free %d container hour(s) cover "
"this workspace, or the tool is bundled free with a current web "
"search or web fetch version" % free_hours)
hours = billed_hours(spend, rate)
shape = ("$%.2f billed, which is %d container hour(s) on top of the free %d"
% (spend, int(hours), free_hours))
if spend < marginal:
return ("allowance-just-crossed",
"%s. The allowance is gone; the overage is still small enough "
"to fix before it is not." % shape)
if hours > free_hours:
return ("allowance-dwarfed",
"%s. Billed hours now exceed the whole free allowance, so the "
"free tier has stopped being a meaningful part of this bill."
% shape)
return ("allowance-spent",
"%s. Container time is being charged on every execution from here "
"to the end of the month." % shape)
def get(session, path, params):
r = session.get(API + path, params=params, timeout=60)
if r.status_code in (401, 403):
raise SystemExit("%d from Anthropic: /v1/organizations/* needs an Admin "
"API key (sk-ant-admin...), not a workspace key"
% r.status_code)
r.raise_for_status()
return r.json()
def pages(session, path, params):
"""Walk the paginated usage or cost report."""
params = dict(params)
while True:
page = get(session, path, params)
yield page
if not page.get("has_more") or not page.get("next_page"):
return
params["page"] = page["next_page"]
def month_start():
"""First of the current month, midnight UTC.
The allowance resets monthly, so a rolling window straddles two of them and
the arithmetic stops meaning anything.
"""
now = dt.datetime.now(dt.timezone.utc)
return now.replace(day=1, hour=0, minute=0, second=0,
microsecond=0).strftime("%Y-%m-%dT%H:%M:%SZ")
def rolling_start(days):
"""Midnight UTC, days ago, for a deliberate rolling read."""
now = dt.datetime.now(dt.timezone.utc)
midnight = now.replace(hour=0, minute=0, second=0, microsecond=0)
return (midnight - dt.timedelta(days=days)).strftime("%Y-%m-%dT%H:%M:%SZ")
def main():
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--days", type=int, default=0,
help="read a rolling window instead of the calendar month")
ap.add_argument("--rate", type=float, default=HOURLY_RATE,
help="dollars per container hour (default 0.05)")
args = ap.parse_args()
admin = os.environ.get("ANTHROPIC_ADMIN_KEY")
if not admin:
log.error("set ANTHROPIC_ADMIN_KEY to an Admin API key (sk-ant-admin...); "
"a workspace key cannot read /v1/organizations/*")
return 2
s = requests.Session()
s.headers.update({"x-api-key": admin, "anthropic-version": VERSION})
start = rolling_start(args.days) if args.days else month_start()
if args.days:
log.warning("reading a rolling %d day window: the free allowance resets "
"monthly, so this may span two of them", args.days)
cost_buckets = []
for page in pages(s, "/organizations/cost_report",
{"starting_at": start, "limit": 31,
"group_by[]": ["description", "workspace_id"]}):
cost_buckets.extend(page.get("data") or [])
folded = fold(cost_buckets)
spend = code_execution_spend(folded)
bad = 0
for workspace in sorted(folded, key=lambda w: -folded[w].get(COST_TYPE, 0.0)):
state, detail = verdict(spend.get(workspace, 0.0), rate=args.rate)
line = "%-24s %-16s %s" % (state, workspace, detail)
if state not in FINDINGS:
log.info(line)
continue
bad += 1
log.warning(line)
hours = billed_hours(spend[workspace], args.rate)
log.warning(" at the %d minute minimum that is at most %d execution(s)",
MINIMUM_MINUTES, executions_ceiling(hours))
log.warning(" repair: find the routes attaching files to requests that "
"never call the tool. Attached files are preloaded onto a "
"container and bill time whether the tool runs or not.")
log.warning(" repair: bundling code execution with web_search_20260209 "
"or web_fetch_20260209 or later removes the charge entirely")
seen = sorted({kind for types in folded.values() for kind in types})
log.info("cost_type values in this window: %s", ", ".join(seen) or "none")
usage = [next(iter(pages(s, "/organizations/usage_report/messages",
{"starting_at": start, "bucket_width": "1d",
"limit": 1})), {})]
if usage_report_mentions_code_execution(usage):
log.warning("the messages usage report now carries a code execution "
"field: read it, this script predates it")
else:
log.info("note: the messages usage report carries no code execution "
"field at all, which is why this check reads the cost report")
log.info("%d workspace(s) with cost, %d finding(s)", len(folded), bad)
return 1 if bad else 0
if __name__ == "__main__":
sys.exit(main())
/**
* Report that Claude code execution has spent its free container hours.
*
* Read only. GET requests and nothing else against the Admin API, which needs
* an Admin API key (sk-ant-admin...); a workspace key is rejected by every
* /v1/organizations/* path.
*
* The finding has no threshold. 1,550 free container hours per organization per
* month are consumed before anything is billed, so a non-zero amount on a
* code_execution cost row means the allowance is already gone. The messages
* usage report does not carry this line under any grouping.
*/
const API = 'https://api.anthropic.com/v1';
const VERSION = '2023-06-01';
// Free container hours per organization per month, consumed before anything is
// charged. That is what makes any non-zero amount a finding on its own.
const FREE_CONTAINER_HOURS = 1550;
// Published price per container hour, and the per-execution minimum. Prices
// rather than fields, so they are constants rather than something this script
// pretends to have read from the API.
const HOURLY_RATE = 0.05;
const MINIMUM_MINUTES = 5;
const COST_TYPE = 'code_execution';
const FINDINGS = ['allowance-just-crossed', 'allowance-spent', 'allowance-dwarfed'];
/** Read a cost row's amount as a number. Pure. amount is a decimal STRING. */
export function amount(row) {
const raw = row?.amount;
if (raw === null || raw === undefined || raw === '') return 0;
const value = Number(raw);
return Number.isFinite(value) ? value : 0;
}
/**
* Sum spend into {workspace_id: {cost_type: dollars}}. Pure.
* Every cost_type is kept. A filter that discards what it does not recognise is
* how the next billable surface stays invisible for a quarter.
*/
export function fold(costBuckets) {
const out = {};
for (const bucket of costBuckets ?? []) {
for (const result of bucket.results ?? []) {
const workspace = String(result.workspace_id ?? 'default workspace');
const kind = String(result.cost_type ?? 'unspecified');
if (!out[workspace]) out[workspace] = {};
out[workspace][kind] = (out[workspace][kind] ?? 0) + amount(result);
}
}
return out;
}
/** Dollars of code execution per workspace, zeros dropped. Pure. */
export function codeExecutionSpend(folded, costType = COST_TYPE) {
const out = {};
for (const [workspace, types] of Object.entries(folded ?? {})) {
if ((types[costType] ?? 0) > 0) out[workspace] = types[costType];
}
return out;
}
/**
* Container hours behind a dollar amount. Pure.
*
* Rounded rather than left raw. Dollars are a decimal quantity and 0.05 has no
* exact binary representation, so 84.60 / 0.05 comes out at 1691.9999999999998
* and every later truncation reports an hour that was never missing.
*/
export function billedHours(dollars, rate = HOURLY_RATE) {
if (rate <= 0) throw new Error('rate must be positive');
return Math.round(Math.max(0, Number(dollars ?? 0)) / rate * 1e6) / 1e6;
}
/**
* The most executions that could account for these hours. Pure.
* A ceiling and never a count: every execution bills at least the minimum, and
* the API reports no execution count at all.
*/
export function executionsCeiling(hours, minimumMinutes = MINIMUM_MINUTES) {
if (minimumMinutes <= 0) throw new Error('minimumMinutes must be positive');
return Math.trunc(Math.max(0, Number(hours ?? 0)) * 60 / minimumMinutes);
}
/**
* Does the messages usage report carry this line anywhere? Pure.
* The answer today is no, under any grouping. The check exists so the script
* states that as an observation, and so it notices the day one appears.
*/
export function usageReportMentionsCodeExecution(pages) {
for (const page of pages ?? []) {
for (const bucket of page.data ?? []) {
for (const result of bucket.results ?? []) {
for (const name of Object.keys(result ?? {})) {
if (String(name).toLowerCase().includes('code_execution')) return true;
}
}
}
}
return false;
}
/**
* Classify one workspace's code execution spend. Pure. Returns [state, detail].
* No threshold to tune: the platform consumed the free allowance before it
* wrote the row, so zero is inside it and anything else is past it.
*/
export function verdict(dollars, freeHours = FREE_CONTAINER_HOURS,
rate = HOURLY_RATE, marginal = 5.0) {
const spend = Number(dollars ?? 0);
if (!(spend > 0)) {
return ['within-allowance',
`no code_execution rows, so the free ${freeHours} container hour(s) cover ` +
'this workspace, or the tool is bundled free with a current web search ' +
'or web fetch version'];
}
const hours = billedHours(spend, rate);
const shape = `$${spend.toFixed(2)} billed, which is ${Math.trunc(hours)} ` +
`container hour(s) on top of the free ${freeHours}`;
if (spend < marginal) {
return ['allowance-just-crossed',
`${shape}. The allowance is gone; the overage is still small enough to ` +
'fix before it is not.'];
}
if (hours > freeHours) {
return ['allowance-dwarfed',
`${shape}. Billed hours now exceed the whole free allowance, so the free ` +
'tier has stopped being a meaningful part of this bill.'];
}
return ['allowance-spent',
`${shape}. Container time is being charged on every execution from here to ` +
'the end of the month.'];
}
async function get(key, path, params) {
const url = new URL(API + path);
for (const [k, v] of Object.entries(params)) {
if (Array.isArray(v)) for (const item of v) url.searchParams.append(k, String(item));
else if (v !== undefined && v !== null) url.searchParams.set(k, String(v));
}
const res = await fetch(url, {
headers: { 'x-api-key': key, 'anthropic-version': VERSION },
});
if (res.status === 401 || res.status === 403) {
throw new Error(`${res.status} from Anthropic: /v1/organizations/* needs an ` +
'Admin API key (sk-ant-admin...), not a workspace key');
}
if (!res.ok) throw new Error(`${res.status} from ${path}`);
return res.json();
}
async function readPages(key, path, params) {
const out = [];
let next = { ...params };
for (;;) {
const page = await get(key, path, next);
out.push(page);
if (!page.has_more || !page.next_page) return out;
next = { ...next, page: page.next_page };
}
}
/** First of the current month, midnight UTC. The allowance resets monthly. */
function monthStart() {
const now = new Date();
return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1))
.toISOString().replace(/\.\d{3}Z$/, 'Z');
}
/** Midnight UTC, days ago, for a deliberate rolling read. */
function rollingStart(days) {
const midnight = new Date();
midnight.setUTCHours(0, 0, 0, 0);
midnight.setUTCDate(midnight.getUTCDate() - days);
return midnight.toISOString().replace(/\.\d{3}Z$/, 'Z');
}
async function main() {
const key = process.env.ANTHROPIC_ADMIN_KEY;
if (!key) {
console.error('set ANTHROPIC_ADMIN_KEY to an Admin API key (sk-ant-admin...); ' +
'a workspace key cannot read /v1/organizations/*');
process.exitCode = 2;
return;
}
const days = Number(process.env.DAYS ?? 0);
const rate = Number(process.env.RATE ?? HOURLY_RATE);
const start = days ? rollingStart(days) : monthStart();
if (days) {
console.warn(`reading a rolling ${days} day window: the free allowance ` +
'resets monthly, so this may span two of them');
}
const costBuckets = [];
for (const page of await readPages(key, '/organizations/cost_report',
{ starting_at: start, limit: 31, 'group_by[]': ['description', 'workspace_id'] })) {
costBuckets.push(...(page.data ?? []));
}
const folded = fold(costBuckets);
const spend = codeExecutionSpend(folded);
let bad = 0;
const workspaces = Object.keys(folded).sort(
(a, b) => (folded[b][COST_TYPE] ?? 0) - (folded[a][COST_TYPE] ?? 0));
for (const workspace of workspaces) {
const [state, detail] = verdict(spend[workspace] ?? 0,
FREE_CONTAINER_HOURS, rate);
const line = `${state.padEnd(24)} ${workspace.padEnd(16)} ${detail}`;
if (!FINDINGS.includes(state)) {
console.log(line);
continue;
}
bad += 1;
console.warn(line);
const hours = billedHours(spend[workspace], rate);
console.warn(` at the ${MINIMUM_MINUTES} minute minimum that is at most ` +
`${executionsCeiling(hours)} execution(s)`);
console.warn(' repair: find the routes attaching files to requests that ' +
'never call the tool. Attached files are preloaded onto a ' +
'container and bill time whether the tool runs or not.');
console.warn(' repair: bundling code execution with web_search_20260209 ' +
'or web_fetch_20260209 or later removes the charge entirely');
}
const seen = [...new Set(Object.values(folded).flatMap((t) => Object.keys(t)))].sort();
console.log(`cost_type values in this window: ${seen.join(', ') || 'none'}`);
const usage = await readPages(key, '/organizations/usage_report/messages',
{ starting_at: start, bucket_width: '1d', limit: 1 });
if (usageReportMentionsCodeExecution(usage.slice(0, 1))) {
console.warn('the messages usage report now carries a code execution field: ' +
'read it, this script predates it');
} else {
console.log('note: the messages usage report carries no code execution ' +
'field at all, which is why this check reads the cost report');
}
console.log(`${Object.keys(folded).length} workspace(s) with cost, ${bad} finding(s)`);
process.exitCode = bad ? 1 : 0;
}
// Only run when invoked directly, so importing this module from the test file
// does not fire main() and fail on the missing key.
if (import.meta.url === `file://${process.argv[1]}`) {
main().catch((err) => { console.error(err.message); process.exitCode = 2; });
}
Add a test
The first test is the note in one line: sixty cents on a code_execution row is a finding, because the free hours were spent before that cent was charged. The second is the blind spot — a realistic messages usage result, with tokens and a server_tool_use object on it, contains no mention of code execution anywhere, and the function that looks would report one the day it appeared. The rest pin the decimal-string amount, the derived hours, and the fact that the execution figure is a ceiling rather than a count.
from anthropic_code_execution_hours_audit import (amount, billed_hours,
code_execution_spend,
executions_ceiling, fold,
usage_report_mentions_code_execution,
verdict)
def cost(workspace="wrkspc_01Qy", cost_type="code_execution", value="84.60"):
"""One bucket of GET /v1/organizations/cost_report."""
return {"starting_at": "2026-08-01T00:00:00Z",
"results": [{"workspace_id": workspace, "cost_type": cost_type,
"description": "Code Execution Usage",
"amount": value, "currency": "USD"}]}
def usage_page():
"""One page of the messages usage report, as rich as it actually gets."""
return {"data": [{"starting_at": "2026-08-01T00:00:00Z", "results": [{
"uncached_input_tokens": 900000, "output_tokens": 40000,
"cache_read_input_tokens": 120000,
"cache_creation": {"ephemeral_5m_input_tokens": 30000,
"ephemeral_1h_input_tokens": 0},
"server_tool_use": {"web_search_requests": 200},
"model": "claude-sonnet-5", "api_key_id": "apikey_01Rs",
}]}], "has_more": False}
def test_any_non_zero_amount_means_the_allowance_is_already_gone():
# No threshold to argue about: the platform consumed the free 1,550 hours
# before it wrote this row, so sixty cents is a finding.
state, detail = verdict(0.60)
assert state == "allowance-just-crossed"
assert "12 container hour(s) on top of the free 1550" in detail
assert verdict(0.0)[0] == "within-allowance"
def test_the_states_scale_with_how_far_past_the_allowance_you_are():
assert verdict(40.00)[0] == "allowance-spent"
state, detail = verdict(84.60)
assert state == "allowance-dwarfed"
assert "1692 container hour(s)" in detail
def test_the_usage_report_cannot_see_this_line_at_all():
# The spine of the note. A usage result carrying tokens, cache fields and a
# server_tool_use object still has nothing about code execution on it.
assert usage_report_mentions_code_execution([usage_page()]) is False
# And the check would notice the day a field did appear.
future = {"data": [{"results": [{"code_execution_container_hours": 12}]}]}
assert usage_report_mentions_code_execution([future]) is True
def test_amount_is_a_decimal_string_and_folds_by_workspace_and_type():
assert amount({"amount": "84.60"}) == 84.60
assert amount({"amount": ""}) == 0.0
assert amount({}) == 0.0
folded = fold([cost(value="80.00"), cost(value="4.60"),
cost(cost_type="web_search", value="500.00"),
cost(workspace="wrkspc_02Zz", cost_type="tokens", value="9.00")])
assert folded["wrkspc_01Qy"]["code_execution"] == 84.60
# Every cost_type is kept, not only the one being looked for.
assert folded["wrkspc_01Qy"]["web_search"] == 500.00
assert code_execution_spend(folded) == {"wrkspc_01Qy": 84.60}
def test_hours_are_derived_from_the_published_rate():
assert billed_hours(84.60) == 1692.0
assert billed_hours(0.0) == 0.0
assert billed_hours(0.60) == 12.0
def test_the_execution_figure_is_a_ceiling_and_not_a_count():
# Twelve billed hours is one long job or 144 short ones, and never more.
assert executions_ceiling(12) == 144
assert executions_ceiling(0) == 0
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { amount, billedHours, codeExecutionSpend, executionsCeiling, fold,
usageReportMentionsCodeExecution, verdict }
from './anthropic-code-execution-hours-audit.mjs';
/** One bucket of GET /v1/organizations/cost_report. */
function cost({ workspace = 'wrkspc_01Qy', costType = 'code_execution',
value = '84.60' } = {}) {
return {
starting_at: '2026-08-01T00:00:00Z',
results: [{ workspace_id: workspace, cost_type: costType,
description: 'Code Execution Usage', amount: value, currency: 'USD' }],
};
}
/** One page of the messages usage report, as rich as it actually gets. */
function usagePage() {
return {
data: [{
starting_at: '2026-08-01T00:00:00Z',
results: [{
uncached_input_tokens: 900000, output_tokens: 40000,
cache_read_input_tokens: 120000,
cache_creation: { ephemeral_5m_input_tokens: 30000, ephemeral_1h_input_tokens: 0 },
server_tool_use: { web_search_requests: 200 },
model: 'claude-sonnet-5', api_key_id: 'apikey_01Rs',
}],
}],
has_more: false,
};
}
test('any non-zero amount means the allowance is already gone', () => {
const [state, detail] = verdict(0.60);
assert.equal(state, 'allowance-just-crossed');
assert.match(detail, /12 container hour\(s\) on top of the free 1550/);
assert.equal(verdict(0)[0], 'within-allowance');
});
test('the states scale with how far past the allowance you are', () => {
assert.equal(verdict(40.00)[0], 'allowance-spent');
const [state, detail] = verdict(84.60);
assert.equal(state, 'allowance-dwarfed');
assert.match(detail, /1692 container hour/);
});
test('the usage report cannot see this line at all', () => {
assert.equal(usageReportMentionsCodeExecution([usagePage()]), false);
const future = { data: [{ results: [{ code_execution_container_hours: 12 }] }] };
assert.equal(usageReportMentionsCodeExecution([future]), true);
});
test('amount is a decimal string and folds by workspace and type', () => {
assert.equal(amount({ amount: '84.60' }), 84.60);
assert.equal(amount({ amount: '' }), 0);
assert.equal(amount({}), 0);
const folded = fold([cost({ value: '80.00' }), cost({ value: '4.60' }),
cost({ costType: 'web_search', value: '500.00' }),
cost({ workspace: 'wrkspc_02Zz', costType: 'tokens', value: '9.00' })]);
assert.equal(folded.wrkspc_01Qy.code_execution, 84.60);
assert.equal(folded.wrkspc_01Qy.web_search, 500.00);
assert.deepEqual(codeExecutionSpend(folded), { wrkspc_01Qy: 84.60 });
});
test('hours are derived from the published rate', () => {
assert.equal(billedHours(84.60), 1692);
assert.equal(billedHours(0), 0);
assert.equal(billedHours(0.60), 12);
});
test('the execution figure is a ceiling and not a count', () => {
assert.equal(executionsCeiling(12), 144);
assert.equal(executionsCeiling(0), 0);
});
FAQ
Why is a single cent on that row a finding?
Because the free allowance is applied before billing, not after. Every organization gets 1,550 container hours a month and they are consumed first, so the platform does not write a code_execution amount until they are gone. That makes the check exact rather than heuristic: there is no threshold to tune and no argument to have about where to set it. Zero means inside the allowance and anything else means past it.
How can I be charged when nobody called the tool?
Attach a file to a request and it is preloaded onto a container, which starts accruing time whether or not the model ever reaches for code execution. That is the single most common cause of a surprising number here, because attaching a document just in case looks free from the call site. Find the routes that attach unconditionally and make the attachment conditional on the question.
Can I get container hours per workspace straight from the API?
No. The report gives dollars, and hours are dollars divided by the published $0.05 rate. The script keeps the rate as an overridable constant rather than pretending it read it. The execution count is worse: with a five-minute minimum per execution, all you can honestly derive is a ceiling, so the script prints at most N executions and never a count.
Does the allowance reset, and does the window matter?
It resets monthly, per organization, which is why the script defaults to the first of the current calendar month rather than a rolling thirty days. A rolling window spans two allowances and the arithmetic stops meaning anything. You can still ask for a rolling read with --days, and the script warns you when you do.
Is there a way to make the charge go away rather than shrink it?
Sometimes. Code execution is free when it is bundled with a current web search or web fetch tool version, web_search_20260209 or web_fetch_20260209 or later, so a route that already uses both may be paying for something it could get at no charge by moving to a newer tool version string. That is worth checking before anyone starts optimising how often the model computes things.
Related field notes
- A tool fee charged per search, not per token
- Spend that is not denominated in tokens at all
- A week that cost materially more than the ones before it
Sources
Every figure in this note is traced to one of these. Prices are list rates and change — check them for your own region before acting.
- Code execution tool — Claude Docs
- Get cost report — Claude Docs
- Usage and Cost API — Claude Docs
- Pricing — Claude Docs
If your setup is misbehaving in a way this note does not cover, message me on LinkedIn with what you are seeing.