Filtering Usage by Model, Service Tier, and Data Residency
Slice usage data by model version, service tier, and data residency for compliance reporting.
Summary
Cost and compliance questions don't stop at "who spent this."
They often continue with "which model," "under what processing tier," and "in which region was this data handled."
The Admin API exposes all three as filter and grouping dimensions, alongside the API key and workspace filters covered elsewhere in this section.
Model and service tier are primarily cost questions: different models and tiers carry different per-token prices.
Data residency is primarily a compliance question: some organizations are contractually or legally required to know, and sometimes restrict, where their request data was processed.
Recipe
import anthropic
client = anthropic.Anthropic()
# Group usage by model, so you can see spend per model side by side
by_model = client.beta.usage.report(
starting_at="2026-06-01T00:00:00Z",
group_by=["model"],
)
# Filter to a single service tier
by_tier = client.beta.usage.report(
starting_at="2026-06-01T00:00:00Z",
service_tiers=["batch"],
)
# Group by data residency region for a compliance export
by_region = client.beta.usage.report(
starting_at="2026-06-01T00:00:00Z",
group_by=["data_residency"],
)When to reach for this:
- You want to see whether a costly month was driven by model mix (more Opus, less Haiku) rather than raw request volume.
- You're deciding whether batch or priority service tier is worth the price difference for a given workload.
- Legal or compliance needs a report of which regions processed your organization's request data over a given period.
- You're validating that a data-residency commitment made to a customer is actually being honored in practice.
Working Example
"""
model_tier_region_report.py
Produces three side-by-side breakdowns for a monthly review: spend by
model, spend by service tier, and request volume by data residency
region, useful for a combined cost-and-compliance monthly readout.
"""
import anthropic
client = anthropic.Anthropic()
START = "2026-06-01T00:00:00Z"
END = "2026-06-30T23:59:59Z"
def total_tokens(bucket) -> int:
return (
bucket.uncached_input_tokens
+ bucket.cached_input_tokens
+ bucket.cache_creation_input_tokens
+ bucket.output_tokens
)
def spend_by_model() -> list[tuple[str, int]]:
report = client.beta.usage.report(starting_at=START, ending_at=END, group_by=["model"])
return sorted(
((bucket.model, total_tokens(bucket)) for bucket in report.data),
key=lambda pair: pair[1],
reverse=True,
)
def spend_by_service_tier() -> list[tuple[str, int]]:
report = client.beta.usage.report(starting_at=START, ending_at=END, group_by=["service_tier"])
return sorted(
((bucket.service_tier, total_tokens(bucket)) for bucket in report.data),
key=lambda pair: pair[1],
reverse=True,
)
def volume_by_residency() -> list[tuple[str, int]]:
report = client.beta.usage.report(starting_at=START, ending_at=END, group_by=["data_residency"])
return sorted(
((bucket.data_residency, total_tokens(bucket)) for bucket in report.data),
key=lambda pair: pair[1],
reverse=True,
)
if __name__ == "__main__":
print("By model:")
for model, tokens in spend_by_model():
print(f" {model:<30}{tokens:>15,} tokens")
print("\nBy service tier:")
for tier, tokens in spend_by_service_tier():
print(f" {tier:<30}{tokens:>15,} tokens")
print("\nBy data residency region:")
for region, tokens in volume_by_residency():
print(f" {region:<30}{tokens:>15,} tokens")What this demonstrates:
- Each function groups by exactly one dimension, which keeps every function's output readable and independently useful.
total_tokenssums all four token types just for the sort key, while the underlying report still preserves the per-type breakdown if you need it later.- The three reports can run independently or be combined into a single monthly readout that covers both a cost angle and a compliance angle.
- Sorting descending surfaces the biggest cost or volume drivers first, which is usually what a reviewer wants to see at the top.
Deep Dive
How It Works
group_by=["model"]returns one row per distinct model string your organization actually used in the window, not a fixed list of every model that exists.service_tiersas a filter (plural, list-typed) narrows to specific tiers;group_by=["service_tier"]instead breaks out every tier you used.data_residencyreflects where a request was processed, which can matter independently of where your organization or its customers are located.- All three dimensions compose with each other and with the API key and workspace filters covered in Querying Usage by API Key and Workspace with the Admin API, so you can, for example, group by both model and region in one call.
Dimensions at a Glance
| Dimension | Primary use | Typical values you'll see |
|---|---|---|
model | Cost and capability mix analysis | Model identifiers such as those for Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5, Claude Haiku 4.5 |
service_tier | Cost and latency tradeoff analysis | Tiers like standard, priority, and batch |
data_residency | Compliance and data-handling verification | Region identifiers reflecting where processing occurred |
Python Notes
# Combine model and region grouping in a single call when you need to answer
# "which model ran in which region," not just each dimension independently.
report = client.beta.usage.report(
starting_at="2026-06-01T00:00:00Z",
group_by=["model", "data_residency"],
)
for bucket in report.data:
print(bucket.model, bucket.data_residency, bucket.output_tokens)Parameters & Return Values
| Parameter | Type | Description |
|---|---|---|
group_by | list[str] | Accepts "model", "service_tier", "data_residency" alongside "api_key_id" and "workspace_id". |
service_tiers | list[str] | Filter to specific service tier values. |
models | list[str] | Filter to specific model identifiers. |
starting_at / ending_at | str (ISO 8601) | Report time window. |
Gotchas
- Assuming model identifiers are stable, human-friendly names. The API reports the exact model identifier used at request time, which may not match the marketing name you'd expect. Fix: map identifiers to human-readable labels in your own code before presenting a report to a non-engineering audience.
- Comparing tier spend without also checking volume. A tier showing lower total cost might just have less traffic, not a better price. Fix: compute a per-token or per-request rate, not just a raw total, when comparing tiers.
- Treating "data residency" as the same thing as "where my organization is based." They're unrelated; residency reflects where the request was processed. Fix: read the actual
data_residencyfield rather than assuming it matches your account's billing address. - Forgetting that a model rename or retirement changes the grouping key going forward. Historical reports keep the old identifier, new usage uses the new one, so a naive month-over-month comparison can look like a model "disappeared." Fix: normalize identifiers in your own mapping layer, and note retirement dates when comparing across a boundary.
- Filtering by service tier when you meant to filter by model, or vice versa. These are separate parameters with separate accepted values. Fix: double check which dimension a given identifier belongs to before building the filter list, especially when both come from the same upstream config.
- Building a compliance report from a single ungrouped total. A flat total tells you nothing about which regions were involved. Fix: always group by
data_residencyexplicitly when the report's purpose is a residency attestation.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Group by model / tier / region via the Admin API | You need a recurring, scriptable, or exportable breakdown | A single manual check is enough |
| Console Usage and Cost pages, filtered by model or tier in the UI | A quick one-off look, no code needed | You need this combined with residency data, joined with other systems, or run on a schedule |
| Contact your account team for a formal residency attestation | You need a legally binding compliance statement, not just observed data | You just need engineering visibility into where traffic is landing |
FAQs
What's the difference between filtering by service_tiers and grouping by service_tier?
service_tiers=[...]restricts the report to only the tiers you list.group_by=["service_tier"]keeps all tiers in scope and returns one row per tier.- Use the filter when you already know which tier you're investigating; use grouping to compare tiers side by side.
Why would spend differ between models beyond just their per-token price?
A more capable model may also produce longer, more thorough outputs for the same prompt, or require fewer retries to get a usable answer, both of which change token volume independently of the per-token rate. Comparing models on total cost, not just price per token, captures that.
What is a service tier, concretely?
It reflects how a request was routed for processing, for example standard real-time processing versus a batch queue that trades latency for a lower price. Each tier is billed differently, which is why it's exposed as its own filterable and groupable dimension.
Can I filter to see only requests processed in a specific region?
report = client.beta.usage.report(
starting_at="2026-06-01T00:00:00Z",
data_residency=["eu"],
)Does grouping by model tell me about quality or capability differences, not just cost?
No, the usage report only reports token counts and derived cost, not quality or capability metrics. It can show you that a workload shifted from one model to another and how that changed spend, but evaluating whether that shift was a good trade requires your own quality metrics alongside this data.
Why might a compliance team specifically care about the data_residency dimension?
- Some contracts or regulations require knowing, and sometimes restricting, where request data is processed.
- A residency report lets a compliance team verify actual behavior rather than relying on configuration alone.
- It's useful evidence during an audit or a customer due-diligence review.
Can I combine model, tier, and region grouping in one request?
Yes, group_by accepts a list, so group_by=["model", "service_tier", "data_residency"] returns one row per unique combination of all three. Be aware that combining several high-cardinality dimensions can produce a large number of rows.
How do I know which model identifiers are currently valid to filter on?
The safest source is your own recent, ungrouped usage report: group by model over a recent window and read back the identifiers that actually appear, rather than hardcoding a list that can drift out of date as models are released or retired.
Is service tier the same thing as rate limits or priority access?
They're related but distinct. Service tier determines how a request is processed and priced; rate limits and priority access govern how much traffic you can send and how it's queued under load. A usage report reflects the tier a request actually used, not your account's configured limits.
What happens if I group by data_residency but my organization only uses one region?
The report simply returns a single row for that region, which is a normal and expected result. It's still useful as a positive confirmation for a compliance report, showing that all processed traffic stayed within the expected region.
Should model, tier, and residency reports be run together or separately?
Either works; it depends on the audience. A combined report with all three grouped together answers precise cross-cutting questions but produces more rows, while separate single-dimension reports (as shown in the working example) are easier to read individually and are usually what a monthly review actually needs.
Related
- How the Usage & Cost Admin API Models Your Spend - the token-bucket model each of these dimensions ultimately breaks down.
- Querying Usage by API Key and Workspace with the Admin API - the identity dimensions these filters compose with.
- Building a Cost Dashboard from Uncached, Cached, and Output Token Breakdowns - fold model and tier grouping into a full cost pipeline.
- Enterprise Analytics API: Per-User Cost Attribution Across Chat and Claude Code - a related API for attributing cost to individual users, not just model or tier.
- Comparison: Admin API vs Console Usage and Cost Pages - decide whether the Console's built-in filters are enough for these dimensions.
Stack versions: Written against the Claude model lineup current as of ~June 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (the default), and Claude Haiku 4.5 - and the official
anthropicPython SDK (latest 0.x release). Model names, pricing, and SDK versions move quickly - verify current specifics at platform.claude.com/docs before relying on them.