---
title: User Cost Report
url: https://platform.claude.com/docs/en/api/php/beta/organization/analytics/user_cost_report
---

# User Cost Report

## Get Per-User Cost

`$client->beta->organization->analytics->userCostReport->list(\Datetime startingAt, ?BucketWidth bucketWidth, ?list<AnalyticsClaudeTagCategory> claudeTagCategories, ?list<string> claudeTagUserIDs, ?list<AnalyticsContextWindow> contextWindows, ?\Datetime endingAt, ?bool excludeDeletedUsers, ?list<GroupBy> groupBy, ?list<AnalyticsInferenceGeoFilter> inferenceGeos, ?int limit, ?list<string> models, ?Order order, ?OrderBy orderBy, ?string page, ?list<AnalyticsProductFilter> products, ?list<string> rbacGroupIDs, ?list<string> slackChannelIDs, ?list<Speed> speeds, ?list<string> userIDs): ReportPageCursor<AnalyticsCostUsersItem>`

**GET** `/v1/organizations/analytics/user_cost_report`

Get per-user cost in USD across a date range.

Returns one row per user, ranked by spend. Use this to see which users
account for the most cost. Only cost attributable to a seat user is
included; for organization-wide totals including direct API-key and
automation traffic, use the bucketed
`/v1/organizations/analytics/cost_report` endpoint. Available to
organizations on a Claude Enterprise plan. Requires an API key with the
`read:analytics` scope.

### Parameters

- `startingAt: \Datetime` (query parameter)

  Start of range, inclusive. RFC 3339 tz-aware. Must be within the last 365 days and no earlier than 2026-01-01T00:00:00Z.

- `bucketWidth?:optional BucketWidth` (query parameter)

  Time-bucket granularity. When set, each row's `starting_at` and `ending_at` are populated and one actor may span several rows (one per time bucket with usage). The time bucket counts toward `limit`, so one page can return multiple rows for the same actor. `ending_at` is required when `bucket_width` is set, and with `bucket_width="1m"` the range may span at most 24 hours. When omitted, each row aggregates the full `[starting_at, ending_at)` range.

- `claudeTagCategories?:optional list<AnalyticsClaudeTagCategory>` (query parameter)

  Filter to Claude Tag (Claude in Slack) usage in specific spend categories. Usage with no category never matches. `dm` usage is reported under the user's product rather than `claude-tag`, so combining this filter with `products[]=claude-tag` excludes it. Use `group_by[]=claude_tag_category` to break out per-category values.

- `claudeTagUserIDs?:optional list<string>` (query parameter)

  Filter to Claude Tag (Claude in Slack) usage attributed to specific Slack users, by Slack user ID (for example `U0123ABCDEF`), not claude.ai user ID. Usage that is not Claude Tag, and Claude Tag usage not attributed to a single user, never matches. Use `group_by[]=claude_tag_user_id` to break out per-user values.

- `contextWindows?:optional list<AnalyticsContextWindow>` (query parameter)

  Filter to specific context-window pricing tiers. Use `group_by[]=context_window` to break out per-tier values.

- `endingAt?:optional \Datetime` (query parameter)

  End of range, exclusive. When omitted, defaults to the earlier of now and `starting_at` + 31 days. The range may span at most 31 days.

- `excludeDeletedUsers?:optional bool` (query parameter)

  If true, omit rows for users who are deleted (`deleted: true`). A page may contain fewer than `limit` rows; use `has_more` and `next_page` to paginate as usual.

  default: false

- `groupBy?:optional list<GroupBy>` (query parameter)

  Break each actor's row out by the given dimensions. Accepts the same values as the bucketed `/cost_report` endpoint. The `product`, `model`, `context_window`, `inference_geo`, and `speed` dimensions — and the time bucket, when `bucket_width` is set — count toward `limit`. `cost_type` and `token_type` do not: `cost_type` returns one row per cost component (tokens, web search, code execution); `token_type` returns one row per token type, each with `cost_type: "tokens"`; combining both returns the per-token-type rows plus the web-search and code-execution rows. A page can therefore contain more rows than `limit` when `cost_type` or `token_type` is requested.

- `inferenceGeos?:optional list<AnalyticsInferenceGeoFilter>` (query parameter)

  Filter to specific inference regions. `not_available` matches rows where the region is unset. Use `group_by[]=inference_geo` to break out per-region values.

- `limit?:optional int` (query parameter)

  Number of rows per page (1-1000, default 20). One row per actor unless `group_by[]` or `bucket_width` splits an actor across rows; `cost_type`/`token_type` fan-out rows (cost endpoint only) are the exception — they do not count toward this limit, so `data` can exceed it.

  default: 20

- `models?:optional list<string>` (query parameter)

  Models to include. Defaults to all models. Use `group_by[]=model` to break out per-model values.

- `order?:optional Order` (query parameter)

  Sort direction. Defaults to `desc`.

  default: desc

- `orderBy?:optional OrderBy` (query parameter)

  Metric to rank actors by. Defaults to `amount`.

  default: amount

- `page?:optional string` (query parameter)

  Opaque cursor from a previous response's `next_page` field.

- `products?:optional list<AnalyticsProductFilter>` (query parameter)

  Product surfaces to include. Defaults to all products.

- `rbacGroupIDs?:optional list<string>` (query parameter)

  Filter to usage attributed to specific RBAC groups. Accepts tagged RBAC group IDs (`rbac_group_...`) or bare group UUIDs. A row matches when the user belonged to any of the listed groups on the (UTC) day the usage occurred; usage with no group attribution never matches.

- `slackChannelIDs?:optional list<string>` (query parameter)

  Filter to usage originating from specific Slack channels. Use `group_by[]=slack_channel_id` to break out per-channel values.

- `speeds?:optional list<Speed>` (query parameter)

  Filter to fast or standard inference mode. Use `group_by[]=speed` to break out per-mode values.

- `userIDs?:optional list<string>` (query parameter)

  Filter to specific users by tagged user ID.

### Returns

- `class AnalyticsCostUsersItem`

  - `AnalyticsUserActor actor`

    The user this row's usage or cost is attributed to. Always a `user_actor`.

  - `string amount`

    Amount (post-discount, pre-credit) in fractional cents (minor units).

  - `?AnalyticsClaudeTagCategory claudeTagCategory`

    Claude Tag (Claude in Slack) spend category: `engaged` (a person addressed Claude in a channel or thread), `proactive` (Claude responded without being addressed), `scheduled` (a scheduled routine ran), `monitoring` (Claude watching a channel it was asked to monitor), or `dm` (direct messages with Claude). Populated only when `claude_tag_category` is in `group_by[]`; null for usage that is not Claude Tag. Direct-message usage is billed to the individual user and is reported under that user's product, not under `claude-tag`. New categories may be added over time.

  - `?string claudeTagUserID`

    Slack user ID (for example `U0123ABCDEF`) of the member the Claude Tag (Claude in Slack) usage is attributed to, not a claude.ai user ID. Populated only when `claude_tag_user_id` is in `group_by[]`; null for usage that is not Claude Tag and for Claude Tag usage that is not attributed to a single user (for example `monitoring`, and `proactive` usage Claude initiated), so per-user rows can sum to less than the Claude Tag total. Cannot be combined with `group_by[]=rbac_group_id` or the `rbac_group_ids[]` filter.

  - `?AnalyticsContextWindow contextWindow`

    Context-window pricing tier of the usage or cost. Null unless `context_window` is in `group_by[]`; it can also be null on grouped rows with no context-window tier, such as code execution.

  - `?AnalyticsCostType costType`

    Cost component breakdown; null when returning the combined total.

  - `string currency`

    Currency code for the cost amount. Currently always `"USD"`.

  - `?\Datetime endingAt`

    End of the row's UTC time bucket (exclusive), as an RFC 3339 timestamp; equal to `starting_at` plus one `bucket_width`. Null unless `bucket_width` is set.

  - `?InferenceGeo inferenceGeo`

    Inference region of the usage or cost. Null unless `inference_geo` is in `group_by[]`; it can also be null on grouped rows where the region is not set (the rows that `inference_geos[]=not_available` matches).

  - `string listAmount`

    List-price amount (pre-discount) in fractional cents.

  - `?string model`

    Model that produced the usage or cost, as a model name in the form the `models[]` filter accepts (for example, `claude-opus-5-5`). Null unless `model` is in `group_by[]`; it can also be null on grouped rows whose usage or cost is not attributed to a specific model, such as code execution.

  - `?string product`

    Product surface that produced the usage or cost. Null unless product is in `group_by[]`; it can also be null on grouped rows whose usage cannot be attributed to a known surface. Values include `chat`, `claude_code`, `cowork`, `office_agent`, `claude_in_chrome`, `claude_design`, `claude-tag`, and `chat_cowork_unified`. `claude-tag` is Claude Tag, the Claude product in Slack. `chat_cowork_unified` is Chat and Cowork unified, Cowork's features inside claude.ai chat: chat and Cowork usage by a member who has it turned on is reported under this value instead of `chat` or `cowork`. It is accepted as a filter only on deployments that offer Chat and Cowork unified. Some unattributed usage is reported as "other".

  - `?string rbacGroupID`

    RBAC group (team) the usage is attributed to, in the public tagged `rbac_group_...` spelling — the same spelling the activity resources use for this key, so the same team has one id across resources and it round-trips as an `rbac_group_ids[]` filter value. Populated only when `rbac_group_id` is in `group_by[]`. Any-membership semantics: a user in several groups contributes their full usage to each of those groups' rows, so the named-group rows overlap and their sum can exceed the org total. A null value is the single unassigned row: users in no group on that (UTC) day. For the true org total, run the same query without `group_by[]`.

  - `?int requests`

    Number of API requests in this row's scope. Null when `group_by` includes `cost_type` or `token_type` (the count has no per-component attribution; read it from the ungrouped response). For sandbox / code-execution events, this counts execution spans rather than HTTP requests (these rows surface with `product: null`).

  - `?string slackChannelID`

    Slack channel the usage originated from. Populated only when `slack_channel_id` is in `group_by[]`; null for usage outside Slack (and for rows recorded before channel attribution was enabled).

  - `?Speed speed`

    Inference speed mode of the usage or cost: `fast` or `standard`. Null unless `speed` is in `group_by[]`.

  - `?\Datetime startingAt`

    Start of the row's UTC time bucket (inclusive), as an RFC 3339 timestamp. Null unless `bucket_width` is set; without `bucket_width`, each row aggregates the full requested range.

  - `?AnalyticsTokenType tokenType`

    Token type when `cost_type` is `tokens`; null otherwise.

### Example

```php
<?php

require_once dirname(__DIR__) . '/vendor/autoload.php';

use Anthropic\Client;
use Anthropic\Beta\Organization\Analytics\AnalyticsClaudeTagCategory;
use Anthropic\Beta\Organization\Analytics\AnalyticsContextWindow;
use Anthropic\Beta\Organization\Analytics\AnalyticsInferenceGeoFilter;
use Anthropic\Beta\Organization\Analytics\AnalyticsProductFilter;

$client = new Client(apiKey: 'my-anthropic-api-key');

$page = $client->beta->organization->analytics->userCostReport->list(
  startingAt: new \DateTimeImmutable('2019-12-27T18:11:19.117Z'),
  bucketWidth: '1d',
  claudeTagCategories: [AnalyticsClaudeTagCategory::ENGAGED],
  claudeTagUserIDs: ['U0123ABCDEF'],
  contextWindows: [AnalyticsContextWindow::FROM_0_TO_200K],
  endingAt: new \DateTimeImmutable('2019-12-27T18:11:19.117Z'),
  excludeDeletedUsers: true,
  groupBy: ['claude_tag_category'],
  inferenceGeos: [AnalyticsInferenceGeoFilter::GLOBAL],
  limit: 1,
  models: ['string'],
  order: 'asc',
  orderBy: 'amount',
  page: 'page',
  products: [AnalyticsProductFilter::CHAT],
  rbacGroupIDs: ['rbac_group_012rppKaSVsmTo6NqRDXQXNF'],
  slackChannelIDs: ['C0123ABCDEF'],
  speeds: ['fast'],
  userIDs: ['user_01AbCdEfGhIjKlMnOpQrSt'],
);

var_dump($page);
```

#### Response (200)

```json
{
  "data": [
    {
      "actor": {
        "deleted": true,
        "email_address": "jane@example.com",
        "name": "Jane Smith",
        "type": "user_actor",
        "user_id": "user_01AbCdEfGhIjKlMnOpQrSt"
      },
      "amount": "41280.000000",
      "claude_tag_category": "dm",
      "claude_tag_user_id": "U0123ABCDEF",
      "context_window": "0-200k",
      "cost_type": "code_execution",
      "currency": "USD",
      "ending_at": "2019-12-27T18:11:19.117Z",
      "inference_geo": "global",
      "list_amount": "51600.000000",
      "model": "claude-opus-5-5",
      "product": "chat",
      "rbac_group_id": "rbac_group_012rppKaSVsmTo6NqRDXQXNF",
      "requests": 128,
      "slack_channel_id": "C0123ABCDEF",
      "speed": "fast",
      "starting_at": "2019-12-27T18:11:19.117Z",
      "token_type": "cache_creation.ephemeral_1h_input_tokens"
    }
  ],
  "data_refreshed_at": "2019-12-27T18:11:19.117Z",
  "has_more": true,
  "next_page": "next_page",
  "organization_id": "org_013FP9SaFPBg7Kw7fetjn6cF"
}
```
