Take Control of Your Claude API Spending
Once you start running Claude API in production, certain questions inevitably arise: How much are we spending this month? Which models are consuming the most tokens? Are we getting good cache hit rates? Anthropic's Usage & Cost Admin API lets you answer all of these questions programmatically.
Instead of manually checking the Claude Console, you can build automated dashboards, set up spending alerts, and generate reports for your finance team — all through a clean REST API.
What Is the Usage & Cost API?
The Usage & Cost Admin API gives you programmatic access to the same data available on the Usage and Cost pages of the Claude Console. It's designed for organizations that need to track, analyze, and optimize their Claude API consumption at scale.
Here's what you can do with it:
- Accurate Usage Tracking: Get precise token counts rather than relying solely on response-level counting
- Cost Reconciliation: Automate matching between your internal records and Anthropic billing
- Performance Monitoring: Measure the impact of system changes and set up alerting
- Rate Limit Optimization: Analyze prompt caching efficiency and make the most of your allocated capacity
- Advanced Analysis: Run deeper queries than what's available in the Console UI
Two Endpoints, Two Purposes
The API provides two distinct endpoints:
| Endpoint | Path | Purpose |
|---|---|---|
| Usage API | /v1/organizations/usage_report/messages | Detailed token consumption breakdowns |
| Cost API | /v1/organizations/cost_report | USD cost breakdowns by service |
Prerequisites: Getting Your Admin API Key
The Usage & Cost API requires an Admin API key (sk-ant-admin...), which is different from the standard API key you use for sending messages.
Here's how to get one:
- Log in to the Claude Console
- Navigate to Settings → Organization and set up your organization (not available for individual accounts)
- Go to Settings → Admin Keys to generate an Admin API key
- Only members with the admin role can create these keys
# Set your Admin API key as an environment variable
export ADMIN_API_KEY="sk-ant-admin-your-key-here"Important: Admin API keys cannot be used to send messages — they're strictly for management operations. In production, always store them in environment variables or a secrets manager, never in source code.
Usage API: Tracking Token Consumption in Detail
Getting Started
Let's start with the simplest possible query — getting daily usage for the last 7 days:
# Fetch daily usage for the past week
curl "https://api.anthropic.com/v1/organizations/usage_report/messages?\
starting_at=2026-03-17T00:00:00Z&\
ending_at=2026-03-24T00:00:00Z&\
bucket_width=1d" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ADMIN_API_KEY"Choosing Your Time Granularity
You can aggregate data at three different levels:
| Granularity | Default Buckets | Max Buckets | Best For |
|---|---|---|---|
1m (1 minute) | 60 | 1,440 | Real-time monitoring |
1h (1 hour) | 24 | 168 | Daily pattern analysis |
1d (1 day) | 7 | 31 | Weekly/monthly reports |
Breaking Down Usage by Model
# Daily token consumption grouped by model
curl "https://api.anthropic.com/v1/organizations/usage_report/messages?\
starting_at=2026-03-01T00:00:00Z&\
ending_at=2026-03-24T00:00:00Z&\
group_by[]=model&\
bucket_width=1d" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ADMIN_API_KEY"The group_by parameter supports the following dimensions:
model— By model (Claude Opus 4.6, Sonnet 4.6, Haiku 4.5, etc.)workspace_id— By workspaceapi_key_id— By API keyservice_tier— By service tier (standard, batch, priority)context_window— By context window sizeinference_geo— By data residency regionspeed— By speed mode (standard, fast) — beta feature
Advanced Filtering
Here's an example that filters down to a specific model and service tier at hourly granularity:
# Hourly batch usage for Claude Opus 4.6 only
curl "https://api.anthropic.com/v1/organizations/usage_report/messages?\
starting_at=2026-03-24T00:00:00Z&\
ending_at=2026-03-24T23:59:59Z&\
models[]=claude-opus-4-6&\
service_tiers[]=batch&\
context_window[]=0-200k&\
bucket_width=1h" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ADMIN_API_KEY"Tracking Data Residency
If your organization uses data residency controls, you can verify where inference is running:
# Usage grouped by region and model
curl "https://api.anthropic.com/v1/organizations/usage_report/messages?\
starting_at=2026-03-01T00:00:00Z&\
ending_at=2026-03-24T00:00:00Z&\
group_by[]=inference_geo&\
group_by[]=model&\
bucket_width=1d" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ADMIN_API_KEY"The valid filter values for inference_geo are global, us, and not_available. Models released before February 2026 (prior to Claude Opus 4.6) return not_available.
Cost API: Getting Dollar Amounts
Basic Usage
# Monthly costs grouped by workspace and service
curl "https://api.anthropic.com/v1/organizations/cost_report?\
starting_at=2026-03-01T00:00:00Z&\
ending_at=2026-03-31T00:00:00Z&\
group_by[]=workspace_id&\
group_by[]=description" \
--header "anthropic-version: 2023-06-01" \
--header "x-api-key: $ADMIN_API_KEY"Key Differences from the Usage API
The Cost API has several characteristics that set it apart:
- All costs are in USD, reported as decimal strings in the lowest unit (cents)
- Tracks token usage costs, web search costs, and code execution costs separately
- Time granularity is daily only (
1d) - Grouping by
descriptionincludes parsed fields likemodelandinference_geo - Priority Tier costs are not included (track them via the
service_tierfilter in the Usage API)
Building a Cost Monitoring Script in Python
Let's put this into practice with a reusable Python monitoring script:
import requests
import json
from datetime import datetime, timedelta, timezone
# Configuration
ADMIN_API_KEY = "sk-ant-admin-your-key-here"
BASE_URL = "https://api.anthropic.com/v1/organizations"
HEADERS = {
"anthropic-version": "2023-06-01",
"x-api-key": ADMIN_API_KEY
}
def get_usage_report(days=7, group_by="model"):
"""Fetch usage report for the last N days."""
now = datetime.now(timezone.utc)
start = now - timedelta(days=days)
params = {
"starting_at": start.strftime("%Y-%m-%dT00:00:00Z"),
"ending_at": now.strftime("%Y-%m-%dT23:59:59Z"),
"group_by[]": group_by,
"bucket_width": "1d"
}
response = requests.get(
f"{BASE_URL}/usage_report/messages",
headers=HEADERS,
params=params
)
response.raise_for_status()
return response.json()
def get_cost_report(days=30):
"""Fetch cost report for the last N days."""
now = datetime.now(timezone.utc)
start = now - timedelta(days=days)
params = {
"starting_at": start.strftime("%Y-%m-%dT00:00:00Z"),
"ending_at": now.strftime("%Y-%m-%dT23:59:59Z"),
"group_by[]": ["workspace_id", "description"],
"bucket_width": "1d"
}
response = requests.get(
f"{BASE_URL}/cost_report",
headers=HEADERS,
params=params
)
response.raise_for_status()
return response.json()
def print_usage_summary(data):
"""Display a formatted usage summary."""
print("=" * 60)
print("Claude API Usage Summary")
print("=" * 60)
for bucket in data.get("data", []):
date = bucket.get("bucket_start", "N/A")
model = bucket.get("model", "all")
input_tokens = bucket.get("input_tokens", 0)
output_tokens = bucket.get("output_tokens", 0)
cached_tokens = bucket.get("cache_read_input_tokens", 0)
print(f"\nDate: {date[:10]} | Model: {model}")
print(f" Input tokens: {input_tokens:,}")
print(f" Output tokens: {output_tokens:,}")
print(f" Cache reads: {cached_tokens:,}")
# Example usage:
# usage = get_usage_report(days=7, group_by="model")
# print_usage_summary(usage)
# Expected output:
# ============================================================
# Claude API Usage Summary
# ============================================================
#
# Date: 2026-03-23 | Model: claude-sonnet-4-6
# Input tokens: 1,245,678
# Output tokens: 523,456
# Cache reads: 890,123Handling Pagination
When working with large datasets, you'll need to handle pagination:
def get_all_pages(endpoint, params):
"""Fetch all pages of data from a paginated endpoint."""
all_data = []
while True:
response = requests.get(
f"{BASE_URL}/{endpoint}",
headers=HEADERS,
params=params
)
response.raise_for_status()
result = response.json()
all_data.extend(result.get("data", []))
# Check if there are more pages
if result.get("has_more", False):
params["page"] = result["next_page"]
else:
break
return all_data
# Example: Fetch a full month of data across all pages
# all_usage = get_all_pages("usage_report/messages", {
# "starting_at": "2026-03-01T00:00:00Z",
# "ending_at": "2026-03-31T00:00:00Z",
# "bucket_width": "1d",
# "limit": 7
# })
# print(f"Total buckets retrieved: {len(all_usage)}")
# Expected output:
# Total buckets retrieved: 24Practical Optimization Strategies
Once you're collecting usage data, several optimization patterns become clear.
1. Measuring Prompt Cache Efficiency
def analyze_cache_efficiency(usage_data):
"""Analyze cache hit rates and identify optimization opportunities."""
total_input = 0
total_cached = 0
for bucket in usage_data.get("data", []):
total_input += bucket.get("input_tokens", 0)
total_cached += bucket.get("cache_read_input_tokens", 0)
if total_input > 0:
cache_rate = (total_cached / total_input) * 100
print(f"Cache hit rate: {cache_rate:.1f}%")
if cache_rate < 30:
print("Warning: Low cache rate detected.")
print(" Consider reviewing your system prompt")
print(" and tool definition caching strategy.")
elif cache_rate > 70:
print("Cache is performing efficiently.")
else:
print("No data available.")
# Expected output:
# Cache hit rate: 45.2%
# Warning: Low cache rate detected.
# Consider reviewing your system prompt
# and tool definition caching strategy.Using prompt caching effectively can dramatically reduce costs when you're repeatedly sending the same system prompts or tool definitions.
2. Optimizing Model Selection
Here's the current pricing for Claude models:
| Model | Input (per 1M tokens) | Output (per 1M tokens) |
|---|---|---|
| Claude Opus 4.6 | $5 | $25 |
| Claude Sonnet 4.6 | $3 | $15 |
| Claude Haiku 4.5 | $1 | $5 |
By analyzing your model-level usage data, you can identify tasks currently handled by Opus 4.6 that Sonnet 4.6 could handle just as well — potentially cutting costs significantly.
3. Leveraging Batch Processing for 50% Savings
Group your data by service_tier to find real-time requests that don't actually need immediate responses. Moving those to batch processing gives you an automatic 50% discount.
Partner Solutions: Monitoring Without Writing Code
If building a custom dashboard isn't feasible, Anthropic's official partner integrations have you covered:
- Grafana Cloud — Agentless setup with out-of-the-box dashboards and alerts
- Datadog — Automatic tracing and monitoring via LLM Observability
- CloudZero — Cloud intelligence platform with cost forecasting
- Honeycomb — Advanced querying and visualization through OpenTelemetry
- Vantage — FinOps platform for unified LLM cost management
These platforms only require your Admin API key to start collecting and visualizing data automatically.
Wrapping Up
The Claude API Usage & Cost API is an essential tool for anyone running Claude in production. Here are the key takeaways:
- The Usage API tracks token consumption at minute, hourly, or daily granularity
- The Cost API provides USD cost breakdowns by service
- You need an Admin API key (
sk-ant-admin...), which only organization admins can generate - Python scripts can automate reports and alerts, and partner solutions like Grafana and Datadog offer turnkey monitoring
- Analyzing cache efficiency and model selection patterns can lead to meaningful cost reductions
Getting visibility into your API spending is foundational to sustainable, long-term use of Claude. Start with a basic usage report, then gradually build out your monitoring infrastructure as your needs grow.