Filtering Usage by Model, Service Tier, and Data Residency
Slice usage data by model version, service tier, and data residency for compliance reporting.
Search across all documentation pages
Slice usage data by model version, service tier, and data residency for compliance reporting.
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.
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:
"""
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:
total_tokens sums all four token types just for the sort key, while the underlying report still preserves the per-type breakdown if you need it later.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_tiers as a filter (plural, list-typed) narrows to specific tiers; group_by=["service_tier"] instead breaks out every tier you used.data_residency reflects where a request was processed, which can matter independently of where your organization or its customers are located.| 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 |
# 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)| 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. |
data_residency field rather than assuming it matches your account's billing address.data_residency explicitly when the report's purpose is a residency attestation.| 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 |
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.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.
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.
report = client.beta.usage.report(
starting_at="2026-06-01T00:00:00Z",
data_residency=["eu"],
)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.
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.
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.
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.
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.
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.
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.
Reviewed by Chris St. John·Last updated Jul 19, 2026