Querying Usage by API Key and Workspace with the Admin API
Filter historical usage and cost data down to a single API key or workspace.
Search across all documentation pages
Filter historical usage and cost data down to a single API key or workspace.
Most cost questions are not "what did we spend in total," they are "what did this team spend."
The Admin API answers that by letting you filter and group usage and cost reports on api_key_id and workspace_id.
Filtering narrows the result set to one or a few identifiers before the API even computes the report.
Grouping keeps every identifier's rows visible side by side, which is usually the better choice once you have more than one key or workspace to compare.
This page covers both, plus how to resolve a human-readable key or workspace name into the ID the API expects.
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_ADMIN_KEY from env
# Filter: usage for one specific API key
by_key = client.beta.usage.report(
starting_at="2026-06-01T00:00:00Z",
api_key_ids=["apikey_01A2B3C4"],
)
# Filter: usage for one specific workspace
by_workspace = client.beta.usage.report(
starting_at="2026-06-01T00:00:00Z",
workspace_ids=["wksp_01abc123"],
)
# Group: usage broken out per key, no filter applied
grouped = client.beta.usage.report(
starting_at="2026-06-01T00:00:00Z",
group_by=["api_key_id"],
)When to reach for this:
import anthropic
from datetime import datetime, timedelta, timezone
client = anthropic.Anthropic()
def usage_for_key(api_key_id: str, days: int = 30) -> dict:
"""Return summed token usage for one API key over the trailing N days."""
start = (datetime.now(timezone.utc) - timedelta(days=days)).isoformat()
report = client.beta.usage.report(
starting_at=start,
api_key_ids=[api_key_id],
bucket_width="1d",
)
totals = {
"uncached_input_tokens": 0,
"cached_input_tokens": 0,
"cache_creation_input_tokens": 0,
"output_tokens": 0,
}
for bucket in report.data:
totals["uncached_input_tokens"] += bucket.uncached_input_tokens
totals["cached_input_tokens"] += bucket.cached_input_tokens
totals["cache_creation_input_tokens"] += bucket.cache_creation_input_tokens
totals["output_tokens"] += bucket.output_tokens
return totals
def top_workspaces_by_output(days: int = 30, limit: int = 5) -> list[tuple[str, int]]:
"""Rank workspaces by output token volume over the trailing N days."""
start = (datetime.now(timezone.utc) - timedelta(days=days)).isoformat()
report = client.beta.usage.report(
starting_at=start,
group_by=["workspace_id"],
)
ranked = sorted(
((bucket.workspace_id, bucket.output_tokens) for bucket in report.data),
key=lambda pair: pair[1],
reverse=True,
)
return ranked[:limit]
if __name__ == "__main__":
print(usage_for_key("apikey_01A2B3C4"))
for workspace_id, output_tokens in top_workspaces_by_output():
print(workspace_id, output_tokens)What this demonstrates:
usage_for_key shows a single-identifier filter used to answer "how much did this one key cost."top_workspaces_by_output shows grouping used to rank many identifiers at once, without filtering any of them out.bucket_width="1d" keeps the report from returning one giant summed row when you might want a trend later.api_key_ids and workspace_ids are both list-typed filters; passing multiple values returns usage for any of them, not just the first.group_by and the filter parameters compose: you can filter to a handful of workspaces and still group the result by API key within them.wksp_01abc123 are different things, and only the ID is a valid filter value.| You have | You need | How to get it |
|---|---|---|
| A workspace name shown in the Console | workspace_id | List workspaces via the Admin API's workspace endpoint, or copy the ID from the workspace's settings page in the Console |
| A key's display label | api_key_id | List API keys via the Admin API's API keys endpoint; the label and the ID are returned together |
| Only a partial cost total from the Console UI | Both | Cross-reference the Console's usage page, which shows both name and ID when you click into a workspace or key |
import anthropic
client = anthropic.Anthropic()
# Resolve a workspace's human-readable name to the ID the usage API expects
workspaces = client.beta.admin.workspaces.list()
target = next(w for w in workspaces.data if w.name == "Growth Team")
print(target.id) # use this value as a workspace_ids filter# Building a filter list dynamically is a common pattern once key IDs
# come from your own database rather than being hardcoded.
active_key_ids: list[str] = load_active_key_ids_from_db()
report = client.beta.usage.report(
starting_at="2026-06-01T00:00:00Z",
api_key_ids=active_key_ids or None, # None means "no filter" to the SDK
)| Parameter | Type | Description |
|---|---|---|
api_key_ids | list[str] | Restrict the report to one or more API key IDs. |
workspace_ids | list[str] | Restrict the report to one or more workspace IDs. |
group_by | list[str] | Return one row per unique combination of these dimensions, e.g. ["api_key_id"]. |
starting_at | str (ISO 8601) | Start of the report window, required. |
ending_at | str (ISO 8601) | End of the report window, optional, defaults to now. |
api_key_ids only accepts the key's ID, not its display name. Fix: list API keys first and read the id field, or store the ID alongside the label in your own system.workspace_id, you will get a single-row grouped result, which is harmless but redundant. Fix: only group by a field when you have more than one value of it in scope.api_key_ids list to work around the lack of a "not in workspace X" filter. The API filters by inclusion, not exclusion. Fix: fetch all keys for the org, subtract the ones you want to exclude in Python, then pass the remainder as the filter.group_by=["api_key_id"] in a single call instead of N separate filtered calls.| Alternative | Use When | Don't Use When |
|---|---|---|
Filter by api_key_ids / workspace_ids | You already know exactly which key or workspace you care about | You need to compare more than a handful of identifiers, since grouping scales better |
Group by api_key_id / workspace_id | You want every identifier's usage visible at once, e.g. a leaderboard | You only care about one specific identifier and want the smallest possible response |
| Console Usage and Cost pages, filtered in the UI | A one-off manual lookup, no code needed | You need this on a recurring schedule or joined with other business data |
workspace_ids=[...]) restricts the report to only the workspaces you list, returning fewer rows.group_by=["workspace_id"]) keeps all workspaces in scope but breaks the result into one row per workspace.Yes. Both filters can be applied together, and the API returns usage that matches both conditions. This is useful for narrowing down to a specific key that belongs to a specific workspace when key IDs alone aren't unique enough for your use case.
import anthropic
client = anthropic.Anthropic()
keys = client.beta.admin.api_keys.list()
match = next(k for k in keys.data if k.name == "backend-prod")
print(match.id)Both. The same api_key_ids and workspace_ids filters apply to the cost report endpoint as well as the usage report endpoint, since cost is derived from the same underlying records.
The report call succeeds but returns no data for that ID, rather than raising an error. This is worth handling explicitly if you're building automation that should alert on a missing or mistyped key ID.
Yes. Historical usage records are tied to the key ID and persist independently of whether the key is still active, so revoked keys remain queryable for as long as your organization's data retention window covers.
The list accepts multiple IDs, but very large lists are better handled with grouping instead of an enormous filter list. If you're filtering to more than a few dozen keys, switch to group_by=["api_key_id"] and post-filter in Python.
report = client.beta.usage.report(
starting_at="2026-06-01T00:00:00Z",
workspace_ids=["wksp_01abc123"],
group_by=["model"],
)This filters to one workspace, then groups the remaining rows by model.
A single grouped call is one network round trip and one query against the underlying data, versus N calls for N keys. It also guarantees every key's data comes from the exact same time window, which matters when you're building a report that needs to add up correctly.
No, they use distinct prefixes (for example workspace IDs commonly begin with wksp_ and API key IDs with apikey_), which makes it easy to catch a mixed-up filter value at a glance during code review.
Not directly, the API only understands API key ID and workspace ID as identity filters. If your organization maps teams to workspaces or a set of keys, you build that mapping yourself and apply it in your own code after fetching the grouped report.
Filter server-side with api_key_ids or workspace_ids whenever you know the target ahead of time. It reduces the amount of data transferred and the amount of aggregation the API has to do, and it keeps your Python code simpler since it isn't re-implementing filtering logic the API already provides.
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