One read of Claude Developer Platformapi-20261021T193714Z
242 pages moved out of 808 read.
Pages moved
242
significant first
Pages read
808
in this capture
Captured
19:37 UTC
Corpus hash
851c97e9b6f0
corpus-hash
What this read moved
126-150 of 242, page 6 of 10This capture is too large to show at once. Changes 126-150 of 242 are below, significant first; the rest are on the following screens.
manage-claude/compliance-activity-feed Changed · +8 / -8 lines
from line 19
1919```bash cURL
2020curl --fail-with-body -sS \
2121 "https://api.anthropic.com/v1/compliance/activities?limit=1" \
22 --header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
23 --header "anthropic-version: 2023-06-01"
22 -H "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
23 -H "anthropic-version: 2023-06-01"
2424```
2525
2626```json Response
from line 61
6161 --data-urlencode "activity_types[]=claude_file_uploaded" \
6262 --data-urlencode "activity_types[]=claude_chat_created" \
6363 --data-urlencode "created_at.gte=2026-04-01T00:00:00Z" \
64 --header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
65 --header "anthropic-version: 2023-06-01"
64 -H "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
65 -H "anthropic-version: 2023-06-01"
6666```
6767
6868The Activity Feed produces hundreds of distinct activity types. See [Query compliance activities](https://platform.claude.com/docs/en/api/compliance/activities/list) in the API reference for the full list of values that `activity_types[]` accepts.
from line 102
102102# Fetch the first page (newest activities first).
103103curl --fail-with-body -sS \
104104 "https://api.anthropic.com/v1/compliance/activities?limit=2" \
105 --header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
106 --header "anthropic-version: 2023-06-01"
105 -H "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
106 -H "anthropic-version: 2023-06-01"
107107
108108# LAST_ID is the last_id from the first response. Pass it back unchanged to fetch the next (older) page.
109109curl --fail-with-body -sS -G \
110110 "https://api.anthropic.com/v1/compliance/activities" \
111 --header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
112 --header "anthropic-version: 2023-06-01" \
111 -H "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
112 -H "anthropic-version: 2023-06-01" \
113113 --data-urlencode "limit=2" \
114114 --data-urlencode "after_id=$LAST_ID"
115115```
manage-claude/compliance-api Changed · +4 / -4 lines
from line 15
1515```bash cURL
1616curl --fail-with-body -sS \
1717 "https://api.anthropic.com/v1/compliance/activities?limit=1" \
18 --header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
19 --header "anthropic-version: 2023-06-01"
18 -H "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
19 -H "anthropic-version: 2023-06-01"
2020```
2121
2222A successful response returns a JSON object containing `data` (an array of `Activity` records), `has_more`, `first_id`, and `last_id`:
from line 57
5757
5858A Claude Enterprise tenant has one parent organization (the top-level container that centralizes identity) with linked organizations of two kinds: claude.ai organizations, where users chat and store content, and Claude Console organizations, where users manage Claude API workloads. For a key that covers the parent organization, the directory endpoints (organizations, users, roles, and groups) return data from every linked organization of either kind. The content endpoints (chats, files, projects, project attachments, and sessions) serve Claude Enterprise data only. The chat, file, and project endpoints return claude.ai chats, files, and projects. The session endpoints return transcripts of sessions on users' machines (local sessions), captured while users are signed in with their Claude Enterprise account: sessions in Cowork, Claude Code, Claude Science, Claude for Microsoft 365, the Claude add-ins for Google Docs, Google Sheets, and Google Slides, and Claude in Chrome. They also return transcripts of Cowork sessions started on claude.ai web or mobile, which run in the cloud in Anthropic-managed environments (remote sessions). A standalone Claude Console organization (one with no parent organization) is not part of a Claude Enterprise tenant; it uses Admin API keys and can query the Activity Feed only.
5959
60All `/v1/compliance/*` endpoints share a rate limit of 600 requests per minute per parent organization (for a standalone Claude Console organization, per organization). The local session endpoints count only against that shared limit, and the remote session endpoints carry a second request budget on top. See [429 Too Many Requests](https://platform.claude.com/docs/en/manage-claude/compliance-errors#429-too-many-requests) for the response headers and retry contract.
60All `/v1/compliance/*` endpoints share a rate limit of 600 requests per minute for each parent organization (for a standalone Claude Console organization, for each organization). The local session endpoints count only against that shared limit, and the remote session endpoints carry a second request budget on top. See [429 Too Many Requests](https://platform.claude.com/docs/en/manage-claude/compliance-errors#429-too-many-requests) for the response headers and retry contract.
6161
6262***
6363
from line 81
8181
8282### OpenTelemetry logging
8383
84[Cowork's OpenTelemetry logging](https://support.claude.com/en/articles/14477985-monitor-claude-cowork-activity-with-opentelemetry) and [Claude Code monitoring](https://code.claude.com/docs/en/monitoring-usage) stream per-event telemetry, including token, cost, and host metadata, to a collector you run as activity happens, whereas the Compliance API returns retained per-session transcripts from Anthropic on request and works with your existing Compliance Access Key. OpenTelemetry logging can also capture prompts and responses, but Anthropic recommends the Compliance API for retrieving the content of Cowork and Claude Code sessions. For a table comparing local sessions, remote sessions, and OpenTelemetry logging, see the introduction to [Retrieve session transcripts](https://platform.claude.com/docs/en/manage-claude/compliance-sessions).
84[Cowork's OpenTelemetry logging](https://support.claude.com/en/articles/14477985-monitor-claude-cowork-activity-with-opentelemetry) and [Claude Code monitoring](https://code.claude.com/docs/en/monitoring-usage) stream per-event telemetry, including token, cost, and host metadata, to a collector you run as activity occurs, whereas the Compliance API returns retained per-session transcripts from Anthropic on request and works with your existing Compliance Access Key. OpenTelemetry logging can also capture prompts and responses, but Anthropic recommends the Compliance API for retrieving the content of Cowork and Claude Code sessions. For a table comparing local sessions, remote sessions, and OpenTelemetry logging, see the introduction to [Retrieve session transcripts](https://platform.claude.com/docs/en/manage-claude/compliance-sessions).
8585
8686### Inference hooks
8787
manage-claude/compliance-content-data Changed · +13 / -13 lines
from line 31
3131```bash cURL
3232curl --fail-with-body -sS -G \
3333 "https://api.anthropic.com/v1/compliance/apps/chats" \
34 --header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
35 --header "anthropic-version: 2023-06-01" \
34 -H "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
35 -H "anthropic-version: 2023-06-01" \
3636 --data-urlencode "order_by=updated_at" \
3737 --data-urlencode "updated_at.gte=2025-06-01T00:00:00Z" \
3838 --data-urlencode "limit=100"
from line 69
6969
7070A few constraints apply to these organization-wide queries. Cursors are opaque and bound to the sort key, so an `after_id` issued under one `order_by` value is rejected with a 400 error under the other. Time-filter bounds must match the sort key too: pair `updated_at.*` bounds with `order_by=updated_at`, and `created_at.*` bounds with the default `order_by=created_at`. Backward pagination with `before_id` is not supported, and the `project_ids[]` filter is not available. See [List chats](https://platform.claude.com/docs/en/api/compliance/apps/chats/list) for the full filter reference.
7171
72To scope the list to specific users instead (for example, a legal hold on named custodians), pass 1–10 `user_ids[]` values. Obtain the IDs from [List organization users](https://platform.claude.com/docs/en/manage-claude/compliance-org-data#list-organization-users). User-filtered queries always sort by `created_at` (passing `order_by=updated_at` returns a 400 error) and support both `after_id` and `before_id`. Filtering by `project_ids[]` is only available in this user-filtered form. Combining `user_ids[]` with any `updated_at.*` bound is deprecated and will be rejected with a 400 error after 2026-09-22; to keep a custodian set current by update time, run the org-wide `order_by=updated_at` walk without `user_ids[]` and select the custodians' chats from its results, and keep the user-filtered listing for `created_at`-ordered exports.
72To scope the list to specific users instead (for example, a legal hold on named custodians), pass 1–10 `user_ids[]` values. Obtain the IDs from [List organization users](https://platform.claude.com/docs/en/manage-claude/compliance-org-data#list-organization-users). User-filtered queries always sort by `created_at` (passing `order_by=updated_at` returns a 400 error) and support both `after_id` and `before_id`. Filtering by `project_ids[]` is only available in this user-filtered form. Combining `user_ids[]` with any `updated_at.*` bound is deprecated and will be rejected with a 400 error after 2026-09-22; to keep a custodian set current by update time, run the organization-wide `order_by=updated_at` walk without `user_ids[]` and select the custodians' chats from its results, and keep the user-filtered listing for `created_at`-ordered exports.
7373
7474```bash cURL
7575curl --fail-with-body -sS -G \
from line 86
8686```bash cURL
8787curl --fail-with-body -sS \
8888 "https://api.anthropic.com/v1/compliance/apps/chats/claude_chat_01H5CWunD7RpVJ5bHa8RCkja/messages" \
89 --header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
90 --header "anthropic-version: 2023-06-01"
89 -H "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
90 -H "anthropic-version: 2023-06-01"
9191```
9292
9393The messages endpoint returns the chat's metadata plus a `chat_messages` array sorted by `created_at`. When `limit` is omitted, the full message set is returned in one response; pass `limit`, `after_id`, or `before_id` to page through very long chats. The endpoint also accepts `created_at.*` and `updated_at.*` range bounds (`gt`, `gte`, `lt`, `lte`) and an `order` parameter (`asc` or `desc`). See [Get chat messages](https://platform.claude.com/docs/en/api/compliance/apps/chats/messages/list) for the full parameter list. For user messages, `created_at` is when the message was sent; for assistant messages, it is when Claude finished generating the message. Each message carries its text content and, when present, any uploaded files (typically on user messages), any tool-generated files, and any artifacts the assistant produced or updated (typically on assistant messages):
from line 195
195195```bash cURL
196196curl --fail-with-body -sS \
197197 "https://api.anthropic.com/v1/compliance/apps/chats/files/claude_file_01UaT9wBcDfGhJkLmNpQrSv7/content" \
198 --header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
199 --header "anthropic-version: 2023-06-01" \
198 -H "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
199 -H "anthropic-version: 2023-06-01" \
200200 --output "dashboard_mockup_v1.pdf"
201201```
202202
from line 228
228228```bash cURL
229229curl --fail-with-body -sS -G \
230230 "https://api.anthropic.com/v1/compliance/apps/projects/claude_proj_01KGp4eZNug9ri4kE35RSppq/attachments" \
231 --header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
232 --header "anthropic-version: 2023-06-01"
231 -H "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
232 -H "anthropic-version: 2023-06-01"
233233```
234234
235235```json Response
from line 265
265265
266266The Compliance API exposes hard-delete endpoints for chats, files, project documents, and entire projects. A hard-deleted chat cannot be restored, and it stops appearing in list responses afterward.
267267
268* [Delete chat](https://platform.claude.com/docs/en/api/compliance/apps/chats/delete): also removes the chat's messages and any files attached to those messages.
268* [Delete chat](https://platform.claude.com/docs/en/api/compliance/apps/chats/delete): also deletes the chat's messages and any files attached to those messages.
269269* [Delete file](https://platform.claude.com/docs/en/api/compliance/apps/chats/files/delete): handles both chat files and project files.
270* [Delete project document](https://platform.claude.com/docs/en/api/compliance/apps/projects/documents/delete): removes a single project document by ID.
270* [Delete project document](https://platform.claude.com/docs/en/api/compliance/apps/projects/documents/delete): deletes a single project document by ID.
271271* [Delete project](https://platform.claude.com/docs/en/api/compliance/apps/projects/delete): see [Detach chats before deleting a project](https://platform.claude.com/docs/en/manage-claude/compliance-content-data#detach-chats-before-deleting-a-project).
272272
273273For a chat in the unified Claude experience, [Delete chat](https://platform.claude.com/docs/en/api/compliance/apps/chats/delete) also deletes the sessions in the cloud that were started for the chat. It does not delete sessions that those sessions started.
from line 285
285285
286286curl --fail-with-body -sS -X DELETE \
287287 "https://api.anthropic.com/v1/compliance/apps/chats/claude_chat_01H5CWunD7RpVJ5bHa8RCkja" \
288 --header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
289 --header "anthropic-version: 2023-06-01"
288 -H "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
289 -H "anthropic-version: 2023-06-01"
290290```
291291
292292```json Response
manage-claude/compliance-faq Changed · +3 / -3 lines
from line 78
7878 </Accordion>
7979
8080 <Accordion title="What do session transcripts include?">
81 Local and remote session transcripts both carry user prompts, assistant responses, and tool calls and results. For local sessions (on users' machines), that is what Claude was asked to do and what it returned, not what happened on the device.
81 Local and remote session transcripts both carry user prompts, assistant responses, and tool calls and results. For local sessions (on users' machines), that is what Claude was asked to do and what it returned, not what occurred on the device.
8282
8383 | Data | Local sessions (on users' machines) | Remote sessions (in the cloud) |
8484 | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
from line 97
9797 </Accordion>
9898
9999 <Accordion title="How does session coverage compare with OpenTelemetry logging (OTEL) for Cowork and Claude Code?">
100 [Cowork's OpenTelemetry logging](https://support.claude.com/en/articles/14477985-monitor-claude-cowork-activity-with-opentelemetry) and [Claude Code monitoring](https://code.claude.com/docs/en/monitoring-usage) overlap with the session endpoints but answer different needs: OTEL streams per-event telemetry to infrastructure you run as activity happens, whereas the Compliance API lets you retrieve retained per-session transcripts from Anthropic after the fact. OTEL can also capture prompts and responses, but Anthropic recommends the Compliance API for retrieving the content of Cowork and Claude Code sessions. For a table comparing local sessions, remote sessions, and OTEL, see the introduction to [Retrieve session transcripts](https://platform.claude.com/docs/en/manage-claude/compliance-sessions).
100 [Cowork's OpenTelemetry logging](https://support.claude.com/en/articles/14477985-monitor-claude-cowork-activity-with-opentelemetry) and [Claude Code monitoring](https://code.claude.com/docs/en/monitoring-usage) overlap with the session endpoints but answer different needs: OTEL streams per-event telemetry to infrastructure you run as activity occurs, whereas the Compliance API lets you retrieve retained per-session transcripts from Anthropic after the fact. OTEL can also capture prompts and responses, but Anthropic recommends the Compliance API for retrieving the content of Cowork and Claude Code sessions. For a table comparing local sessions, remote sessions, and OTEL, see the introduction to [Retrieve session transcripts](https://platform.claude.com/docs/en/manage-claude/compliance-sessions).
101101
102102 OTEL events and Compliance API records share organization and user identifiers, so you can join them.
103103 </Accordion>
from line 123
123123 </Accordion>
124124
125125 <Accordion title="Can one customer have multiple organizations under one parent?">
126 Yes. A Claude Enterprise parent organization can have many linked organizations, including a mix of claude.ai organizations and Claude Console organizations (for example, separate production and staging Claude Console organizations). Identity, SSO, and SCIM are shared across the parent; billing, members, projects, and API keys remain separate for each organization. Compliance API enablement happens at the parent organization level and cascades to all linked organizations, and a Compliance Access Key that covers the parent organization and carries `read:compliance_org_data` can enumerate every organization beneath the parent through `GET /v1/compliance/organizations`.
126 Yes. A Claude Enterprise parent organization can have many linked organizations, including a mix of claude.ai organizations and Claude Console organizations (for example, separate production and staging Claude Console organizations). Identity, SSO, and SCIM are shared across the parent; billing, members, projects, and API keys remain separate for each organization. Compliance API enablement occurs at the parent organization level and cascades to all linked organizations, and a Compliance Access Key that covers the parent organization and carries `read:compliance_org_data` can enumerate every organization beneath the parent through `GET /v1/compliance/organizations`.
127127 </Accordion>
128128
129129 <Accordion title="Are activities returned in order, and how do I detect when I have caught up to real time?">
manage-claude/compliance-integration-patterns Changed · +6 / -6 lines
from line 29
2929* Activities are queryable within 1 minute of occurring and retained for 6 years. Recording is not retroactive: it begins when the Compliance API is first enabled for your organization, and activity from before enablement is not backfilled.
3030* The maximum `limit` for each page is 5,000.
3131* Cursor values are opaque strings that you must not parse.
32* Requests are limited to 600 per minute per [parent organization](https://platform.claude.com/docs/en/manage-claude/compliance-api#how-the-compliance-api-works), shared across every key, every linked organization, and every `/v1/compliance/*` endpoint; unlike the local session endpoints, the remote session endpoints carry a second request budget on top. See [429 Too Many Requests](https://platform.claude.com/docs/en/manage-claude/compliance-errors#429-too-many-requests) for the response headers and retry contract.
32* Requests are limited to 600 per minute for each [parent organization](https://platform.claude.com/docs/en/manage-claude/compliance-api#how-the-compliance-api-works), shared across every key, every linked organization, and every `/v1/compliance/*` endpoint; unlike the local session endpoints, the remote session endpoints carry a second request budget on top. See [429 Too Many Requests](https://platform.claude.com/docs/en/manage-claude/compliance-errors#429-too-many-requests) for the response headers and retry contract.
3333
3434| Pattern | Choose when |
3535| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
from line 43
4343```bash cURL
4444curl --fail-with-body -sS -G \
4545 "https://api.anthropic.com/v1/compliance/activities" \
46 --header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
47 --header "anthropic-version: 2023-06-01" \
46 -H "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
47 -H "anthropic-version: 2023-06-01" \
4848 --data-urlencode "created_at.gte=2026-04-20T07:00:00Z" \
4949 --data-urlencode "created_at.lt=2026-04-20T08:00:00Z" \
5050 --data-urlencode "limit=5000"
from line 64
6464# before_id is the first_id from a previous response
6565curl --fail-with-body -sS -G \
6666 "https://api.anthropic.com/v1/compliance/activities" \
67 --header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
68 --header "anthropic-version: 2023-06-01" \
67 -H "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
68 -H "anthropic-version: 2023-06-01" \
6969 --data-urlencode "limit=5000" \
7070 --data-urlencode "before_id=activity_01XyDMpzjS89pFZXqSFUBDr6"
7171```
7272
73Page through until `has_more` is `false`, then persist `first_id` from the final response and pass it unchanged as `before_id` on the next run to retrieve activities newer than the saved cursor. To walk in the opposite direction for a backfill, persist `last_id` and pass it as `after_id` instead. For the full cursor-vs-page-token reference and retry semantics, see [Paginate results](https://platform.claude.com/docs/en/manage-claude/compliance-activity-feed#paginate-results).
73Page through until `has_more` is `false`, then persist `first_id` from the final response and pass it unchanged as `before_id` on the next run to retrieve activities newer than the saved cursor. To walk in the opposite direction for a backfill, persist `last_id` and pass it as `after_id` instead. For the full cursor-versus-page-token reference and retry semantics, see [Paginate results](https://platform.claude.com/docs/en/manage-claude/compliance-activity-feed#paginate-results).
7474
7575A production **catch-up** loop fetches activities recorded since your last poll by driving iteration off `has_more` and `first_id`:
7676
manage-claude/compliance-sessions Changed · +21 / -21 lines
from line 47
4747| Setup | Works with your existing Compliance Access Key | Works with your existing Compliance Access Key | Admin configures an OTLP endpoint and content-capture settings |
4848| Infrastructure | Anthropic-hosted | Anthropic-hosted | You run the collector and storage |
4949| ID prefix | `clls_` | `cse_` | N/A |
50| `product_surface` values | `cowork`, `claude_code`, `claude_science`, `claude_in_chrome`, and values beginning with `office_agents` | `cowork_remote` | N/A |
50| `product_surface` values | `cowork`, `claude_code`, `claude_science`, `claude_in_chrome`, and values starting with `office_agents` | `cowork_remote` | N/A |
5151| Retention | 6 years by default, or your organization's custom conversation retention period when a finite one is set; 30 days in organizations with HIPAA readiness enabled; held by Anthropic | 6 years, unless a user deletes the session sooner; held by Anthropic | Your infrastructure, your policies |
5252| User prompts and assistant responses | Yes | Yes | Yes, subject to content-capture settings |
5353| Tool inputs | Truncated to 10,000 bytes per input by default; up to about 1 MiB on request | Truncated to 10,000 bytes per input by default; up to about 1 MiB on request | Truncated summaries |
from line 62
6262
6363The Compliance API exposes local sessions through three endpoints: `GET /v1/compliance/apps/sessions/local` lists session metadata, `GET /v1/compliance/apps/sessions/local/{session_id}` retrieves one session's metadata, and `GET /v1/compliance/apps/sessions/local/{session_id}/messages` returns one session's transcript. All three require the `read:compliance_user_data` scope and count only against the shared Compliance API rate limit; they are not subject to the second request budget that applies to the remote session endpoints. See [429 Too Many Requests](https://platform.claude.com/docs/en/manage-claude/compliance-errors#429-too-many-requests). If local sessions are not available to your parent organization, all three endpoints return 404 with the message `Local sessions are not available.` (see [Local session not found](https://platform.claude.com/docs/en/manage-claude/compliance-errors#local-session-not-found)); while session listings or captured content are temporarily unavailable, they return 503 (see [Local sessions temporarily unavailable](https://platform.claude.com/docs/en/manage-claude/compliance-errors#local-sessions-temporarily-unavailable)).
6464
65For local sessions, Anthropic records each conversation server-side as its requests reach the Claude API; nothing is installed on the device, and nothing is collected beyond the requests the client already sends to the Claude API. Local session transcripts show what Claude was asked to do and what it returned, not what happened on the device. File and network activity is visible only through the tool calls and tool results in the transcript, so activity that never reaches the API (for example, local files the session never sent) is not captured.
65For local sessions, Anthropic records each conversation server-side as its requests reach the Claude API; nothing is installed on the device, and nothing is collected beyond the requests the client already sends to the Claude API. Local session transcripts show what Claude was asked to do and what it returned, not what occurred on the device. File and network activity is visible only through the tool calls and tool results in the transcript, so activity that never reaches the API (for example, local files the session never sent) is not captured.
6666
6767In organizations that use [customer-managed encryption keys](https://platform.claude.com/docs/en/manage-claude/cmek), local session transcripts are encrypted under your customer-managed key and returned as usual. While that key cannot be used (for example, because you disabled or revoked it, or because it cannot be reached), the messages endpoint returns [503 Service Unavailable](https://platform.claude.com/docs/en/manage-claude/compliance-errors#local-sessions-temporarily-unavailable) for the affected pages instead of transcript content. Those messages are never reported as `not_captured` (see [Retrieve a local session transcript](https://platform.claude.com/docs/en/manage-claude/compliance-sessions#retrieve-a-local-session-transcript)). Listing sessions and retrieving session metadata are not affected.
6868
from line 71
7171```bash cURL
7272curl --fail-with-body -sS -G \
7373 "https://api.anthropic.com/v1/compliance/apps/sessions/local" \
74 --header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
75 --header "anthropic-version: 2023-06-01" \
74 -H "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
75 -H "anthropic-version: 2023-06-01" \
7676 --data-urlencode "created_at.gte=2026-07-01T00:00:00Z" \
7777 --data-urlencode "limit=100"
7878```
from line 111
111111}
112112```
113113
114Results are sorted in reverse chronological order (newest first) by `created_at`, with ties broken in a fixed server-side order, and capped at `limit` results per response (default 100, max 500). The endpoint paginates forward only with `page` and `next_page` tokens (see [Paginate results](https://platform.claude.com/docs/en/manage-claude/compliance-activity-feed#paginate-results)): pass the response's `next_page` value back as the `page` query parameter on the next request, and stop when `next_page` is `null`. The response has no `has_more` field. Complete a list walk within 24 hours of starting it; an older list cursor is still accepted but is re-evaluated against the current retention boundary, so sessions whose oldest retained activity is about to age out of the retention period can be skipped.
114Results are sorted in reverse chronological order (newest first) by `created_at`, with ties broken in a fixed server-side order, and capped at `limit` results for each response (default 100, max 500). The endpoint paginates forward only with `page` and `next_page` tokens (see [Paginate results](https://platform.claude.com/docs/en/manage-claude/compliance-activity-feed#paginate-results)): pass the response's `next_page` value back as the `page` query parameter on the next request, and stop when `next_page` is `null`. The response has no `has_more` field. Complete a list walk within 24 hours of starting it; an older list cursor is still accepted but is re-evaluated against the current retention boundary, so sessions whose oldest retained activity is about to age out of the retention period can be skipped.
115115
116116In each session object, `user.id` is always set and survives account deletion; `user.email_address` is `null` when the user's account has been deleted or the user is no longer a member of an organization your key can read. `workspace_id` is `null` when the session was not associated with a workspace. A local session corresponds to one client session ID: starting a new conversation in the client, or clearing its context, begins a new session record. For Claude Science, the list can also include separate sessions for the app's own background work (for example, naming the conversation; on newer app versions also its reviewer and delegation tracks), and on older app versions some of that background work appears as extra messages inside the conversation's own transcript. A Claude Science conversation that continues across some app updates appears as two sessions. These behaviors are expected. Treat `id` values as opaque strings; the format may change without notice.
117117
118For Claude for Microsoft 365 and the Claude add-ins for Google Docs, Google Sheets, and Google Slides, deleting a conversation in the add-in happens only on the client, so it is not reflected in the API: local sessions have no `deleted_at` field, and the session stays listed until retention removes it.
118For Claude for Microsoft 365 and the Claude add-ins for Google Docs, Google Sheets, and Google Slides, deleting a conversation in the add-in occurs only on the client, so it is not reflected in the API: local sessions have no `deleted_at` field, and the session stays listed until retention removes it.
119119
120120Local sessions carry an `updated_at` but no `status`: a local session has no server-side lifecycle status, and its visibility is governed by retention instead. A local session is captured as the series of Claude API calls (inference calls) that the client makes during the session, and retention applies to each captured call individually. `created_at` is the timestamp of the session's earliest retained call and `updated_at` the timestamp of its last, both UTC. As older calls age past the retention period, `created_at` advances accordingly, and once every call in a session has aged out, the session is no longer returned; `updated_at` tracks the most recent call and is unaffected until then. Because `created_at` can shift between runs, deduplicate on `id` when you re-walk the list over time. To keep transcripts current as sessions gain messages, poll with the `updated_at.gte` filter, overlapping consecutive windows. On the list endpoint `updated_at` is a lower bound: for a session still active at a page boundary or the upper end of a `created_at` window, it can momentarily lag the session's true last activity, and a new call only becomes queryable after the short processing delay noted earlier. Because of that lag, set each run's `updated_at.gte` a few minutes before your previous run's start time, not to the previous run's time exactly. A bound set to the exact previous time silently and permanently drops a session whose final call was still indexing at that moment, because once the bound advances past that call no later run returns it. Deduplicate the returned sessions on `id`, re-fetch their transcripts, and deduplicate messages on `id`. Retrieving a session, or its messages, always reflects the exact latest retained call, so a periodic reconciliation pass over an older window is a more thorough alternative to widening the overlap.
121121
122The list is built from session activity metadata, so it can include sessions whose transcript content was not captured, for example sessions that ran before capture began for your organization (as far back as your retention period allows); the transcript of such a session returns each message with its content marked unavailable (see [Retrieve a local session transcript](https://platform.claude.com/docs/en/manage-claude/compliance-sessions#retrieve-a-local-session-transcript)).
122The list is built from session activity metadata, so it can include sessions whose transcript content was not captured, for example sessions that ran before capture started for your organization (as far back as your retention period allows); the transcript of such a session returns each message with its content marked unavailable (see [Retrieve a local session transcript](https://platform.claude.com/docs/en/manage-claude/compliance-sessions#retrieve-a-local-session-transcript)).
123123
124124Captured local session content is stored for 6 years from capture by default. If the organization that ran the session has set a finite custom conversation retention period in [claude.ai > Organization settings > Data and privacy](https://claude.ai/admin-settings/data-privacy-controls), that period applies instead, whether it is shorter or longer than the default; when the organization has more than one custom retention period configured, the shortest applies. A change to that setting takes effect in two different ways: the endpoints stop returning activity older than the organization's current period as soon as the setting changes, whereas each captured message is stored for the period that was in effect when it was captured, so lengthening the period later does not restore content that has already expired. In organizations with [HIPAA readiness](https://platform.claude.com/docs/en/manage-claude/api-and-data-retention#hipaa-readiness) enabled, captured local session content is stored for 30 days from capture, or for the organization's custom conversation retention period when that is shorter; the 6-year default does not apply.
125125
from line 137
137137
138138If a session shows `office_agents` on its own, nothing else in the response tells you which of these products it came from. Do not assume it is Claude for Microsoft 365.
139139
140The value comes from the session's most recent activity, so it can change over time. The API started recording the Google values on October 5, 2026. A Google add-in session whose most recent activity happened on or before that date can show `office_agents`. If the user works in that session again, it switches to the matching Google value.
140The value comes from the session's most recent activity, so it can change over time. The API started recording the Google values on October 5, 2026. A Google add-in session whose most recent activity occurred on or before that date can show `office_agents`. If the user works in that session again, it switches to the matching Google value.
141141
142142New values appear as these endpoints cover more products.
143143
from line 150
150150The messages endpoint returns the session's transcript, reconstructed from the captured Claude API calls: user prompts, assistant text, tool calls, and the text portions of tool results, all returned as they were sent apart from size truncation. Nothing masks URLs, credentials, or personal data in that content, so treat transcripts as sensitive. The transcript omits or replaces the following:
151151
152152* Thinking blocks are never included.
153* The request's system prompt is never returned. A marker message reading `[system prompt content not shown]` stands in for it (normally once per session; a session with no captured content carries no marker).
153* The request's system prompt is never returned. A marker message reading `[system prompt content not shown]` stands in for it (normally once for each session; a session with no captured content carries no marker).
154154* Tool definitions and MCP server configuration are not part of the transcript.
155155* Images, PDFs, and other binary or structured blocks are not returned. Each appears as a `text` block reading `[<block type> content not shown]` (for example, `[image content not shown]`) with `truncated` set to `true`. Non-text items inside a tool result, such as [web search](https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-search-tool) results or the output of the [code execution tool](https://platform.claude.com/docs/en/agents-and-tools/tool-use/code-execution-tool), are replaced by one `[N non-text item(s) not shown]` entry, and the tool result block's `truncated` is `true`. The matching tool call, with the search query or the code in its `input`, is still returned.
156156* Citation metadata on `text` blocks, such as the source citations on an answer that draws on web search results, is omitted. The text itself is returned, and the block carries `truncated` set to `true`.
from line 160
160160```bash cURL
161161curl --fail-with-body -sS \
162162 "https://api.anthropic.com/v1/compliance/apps/sessions/local/clls_01HxKpLmNoPqRsTuVwXyZaBc/messages" \
163 --header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
164 --header "anthropic-version: 2023-06-01"
163 -H "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
164 -H "anthropic-version: 2023-06-01"
165165```
166166
167167```json Response
from line 289
289289
290290Each message also carries a `provenance` field describing how its content was captured. `provenance` is `null` for verified content captured by the Claude API, which is the common case. Otherwise it is an object whose `type` marks the exception:
291291
292* `content_unavailable` means the content cannot be returned. The `content` array is empty, and `provenance.reason` states why. `not_captured` means no content is available for the turn. It does not prove that no record was stored: content that Anthropic's data-handling policies withhold from the Compliance API is reported with the same reason, and so are individual turns within an otherwise captured session that are unavailable for such reasons. An unusable customer-managed key is the one exception and returns [503 Service Unavailable](https://platform.claude.com/docs/en/manage-claude/compliance-errors#local-sessions-temporarily-unavailable) instead. `client_aborted` means the client closed the connection or cancelled the request before the response completed, so the turn's response was not captured; any partial output already streamed to the client is not included, and this reason applies to assistant-role turns only. `cmek_key_revoked` is reserved for content encrypted under your organization's customer-managed key when that key is unavailable (for example, revoked). It is not currently returned, because an unusable key produces a 503 instead, but handle it for forward compatibility. `retention_elapsed` means the content aged past retention. `oversize` means a single message exceeded the per-message size bound; the message is still returned, with an empty `content` array.
292* `content_unavailable` means the content cannot be returned. The `content` array is empty, and `provenance.reason` states why. `not_captured` means no content is available for the turn. It does not prove that no record was stored: content that Anthropic's data-handling policies withhold from the Compliance API is reported with the same reason, and so are individual turns within an otherwise captured session that are unavailable for such reasons. An unusable customer-managed key is the one exception and returns [503 Service Unavailable](https://platform.claude.com/docs/en/manage-claude/compliance-errors#local-sessions-temporarily-unavailable) instead. `client_aborted` means the client closed the connection or canceled the request before the response completed, so the turn's response was not captured; any partial output already streamed to the client is not included, and this reason applies to assistant-role turns only. `cmek_key_revoked` is reserved for content encrypted under your organization's customer-managed key when that key is unavailable (for example, revoked). It is not currently returned, because an unusable key produces a 503 instead, but handle it for forward compatibility. `retention_elapsed` means the content aged past retention. `oversize` means a single message exceeded the per-message size bound; the message is still returned, with an empty `content` array.
293293* `client_asserted` marks assistant messages that the client supplied as conversation history and that could not be matched to a captured response; their authorship is not verified.
294294* `synthetic_marker` marks records generated by the endpoint itself, such as the marker that stands in for the system prompt. When the client rewrites or compacts its conversation history mid-session (for example, after context compaction), the transcript inserts a marker message at that point and continues with the new content the client sent. When your organization has a finite retention period and that new content includes assistant messages, the transcript withholds the new content up to and including its last assistant message (a second marker notes this) and shows only the user messages after that point, followed by the rest of the session.
295295
296Marker and client-asserted messages begin with a bracketed explanatory `text` block flagged `truncated: true`, for example `[system prompt content not shown]`. Treat these records as present but unavailable or unverified rather than missing, and tolerate unrecognized `provenance` types and reasons.
296Marker and client-asserted messages start with a bracketed explanatory `text` block flagged `truncated: true`, for example `[system prompt content not shown]`. Treat these records as present but unavailable or unverified rather than missing, and tolerate unrecognized `provenance` types and reasons.
297297
298Two parameters cap how many bytes of each tool block are returned: `tool_use_input_max_bytes` and `tool_result_max_bytes`, both defaulting to 10,000 bytes. Pass `-1` for the server maximum (about 1 MiB per string); `0` returns [400 Bad Request](https://platform.claude.com/docs/en/manage-claude/compliance-errors#400-bad-request), and values above the maximum are clamped to it. A string cut off by either cap is cut on a character boundary and has an in-band suffix appended (for example, `…[truncated; pass tool_result_max_bytes=-1 for the server max]`), and its block carries `"truncated": true`. A truncated `tool_use` `input` is therefore no longer valid JSON, so parse tool inputs only from untruncated blocks (or raise the cap and refetch). Blocks of type `text` are always capped at the same server maximum of about 1 MiB; no parameter raises it, and a `text` block at the bound also carries `"truncated": true`.
298Two parameters cap how many bytes of each tool block are returned: `tool_use_input_max_bytes` and `tool_result_max_bytes`, both defaulting to 10,000 bytes. Pass `-1` for the server maximum (about 1 MiB for each string); `0` returns [400 Bad Request](https://platform.claude.com/docs/en/manage-claude/compliance-errors#400-bad-request), and values above the maximum are clamped to it. A string cut off by either cap is cut on a character boundary and has an in-band suffix appended (for example, `…[truncated; pass tool_result_max_bytes=-1 for the server max]`), and its block carries `"truncated": true`. A truncated `tool_use` `input` is therefore no longer valid JSON, so parse tool inputs only from untruncated blocks (or raise the cap and refetch). Blocks of type `text` are always capped at the same server maximum of about 1 MiB; no parameter raises it, and a `text` block at the bound also carries `"truncated": true`.
299299
300300Claude Science calls connectors (MCP servers) from code that it runs through its `repl` tool, not as separately named tools, so no block in a Claude Science transcript is named after a connector. Each connector call appears in the code inside a `repl` `tool_use` block's `input` (for example, a `host.mcp("<server>", "<tool>", ...)` call), and connector output appears in the matching `tool_result` only where that code printed it. Cowork and Claude Code sessions differ: they call each connector tool under its own `mcp__<server>__<tool>` name, which is the `name` of the `tool_use` block. To monitor connector use in Claude Science sessions, parse the `input` string and match on the code it contains rather than on a tool name. Pass `tool_use_input_max_bytes=-1` for these sessions so that a long code input is returned up to the server maximum instead of being cut at the 10,000-byte default before the connector call appears.
301301
from line 310
310310```bash cURL
311311curl --fail-with-body -sS -G \
312312 "https://api.anthropic.com/v1/compliance/apps/sessions/remote" \
313 --header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
314 --header "anthropic-version: 2023-06-01" \
313 -H "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
314 -H "anthropic-version: 2023-06-01" \
315315 --data-urlencode "created_at.gte=2026-06-01T00:00:00Z" \
316316 --data-urlencode "limit=100"
317317```
from line 354
354354}
355355```
356356
357Results are sorted in reverse chronological order (newest first) by `created_at` and capped at `limit` results per response (default 100, max 500). The endpoint paginates with `page` and `next_page` tokens (see [Paginate results](https://platform.claude.com/docs/en/manage-claude/compliance-activity-feed#paginate-results)): pass the response's `next_page` value back as the `page` query parameter on the next request, and stop when `next_page` is `null`.
357Results are sorted in reverse chronological order (newest first) by `created_at` and capped at `limit` results for each response (default 100, max 500). The endpoint paginates with `page` and `next_page` tokens (see [Paginate results](https://platform.claude.com/docs/en/manage-claude/compliance-activity-feed#paginate-results)): pass the response's `next_page` value back as the `page` query parameter on the next request, and stop when `next_page` is `null`.
358358
359359A session is owned by either a user or an agent, never both. For user-owned sessions, `user` carries the owner's ID and email address (`email_address` is `null` when the user is no longer a member of an organization your key can read) and `agent_id` is `null`. For agent-owned sessions (for example, runs of a Cowork scheduled task in the cloud), `user` is `null`, `agent_id` carries the agent's ID (prefix `cagt_`), and `started_by_user` identifies the human who initiated the run; on user-owned sessions, `started_by_user` is `null`. Each run of a scheduled task is a separate session, and no session field carries the task's name or schedule.
360360
from line 377
377377```bash cURL
378378curl --fail-with-body -sS \
379379 "https://api.anthropic.com/v1/compliance/apps/sessions/remote/cse_01WpQrStUvXyZaBcDeFgHjK6/messages" \
380 --header "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
381 --header "anthropic-version: 2023-06-01"
380 -H "x-api-key: $ANTHROPIC_COMPLIANCE_ACCESS_KEY" \
381 -H "anthropic-version: 2023-06-01"
382382```
383383
384384```json Response
from line 438
438438
439439Each message carries a `role` (`user` or `assistant`) and a `content` array of `text`, `tool_use`, and `tool_result` blocks. Message `created_at` values are commit timestamps: consecutive messages can share a timestamp or slightly invert, so preserve the returned order rather than re-sorting by `created_at`. On agent-owned sessions, `sent_by_user_id` records the user who sent a given user message when one is attributable; it is `null` otherwise, including on all assistant messages. When a message's content cannot be returned at all (for example, it exceeds size bounds), the message carries `content_unavailable` set to `true`.
440440
441Two parameters cap how many bytes of each tool block are returned: `tool_use_input_max_bytes` and `tool_result_max_bytes`, both defaulting to 10,000 bytes. Pass `-1` for the server maximum (about 1 MiB per string); `0` returns [400 Bad Request](https://platform.claude.com/docs/en/manage-claude/compliance-errors#400-bad-request). A block cut off by either cap carries `"truncated": true`, and a truncated `tool_use` input is no longer valid JSON, so parse tool inputs only from untruncated blocks (or raise the cap and refetch).
441Two parameters cap how many bytes of each tool block are returned: `tool_use_input_max_bytes` and `tool_result_max_bytes`, both defaulting to 10,000 bytes. Pass `-1` for the server maximum (about 1 MiB for each string); `0` returns [400 Bad Request](https://platform.claude.com/docs/en/manage-claude/compliance-errors#400-bad-request). A block cut off by either cap carries `"truncated": true`, and a truncated `tool_use` input is no longer valid JSON, so parse tool inputs only from untruncated blocks (or raise the cap and refetch).
442442
443443The messages endpoint returns [404 Not Found](https://platform.claude.com/docs/en/manage-claude/compliance-errors#404-not-found) for `pending` sessions, sessions that do not exist or have been deleted, and sessions in organizations your key cannot read.
444444
manage-claude/data-residency Changed · +4 / -4 lines
from line 7
77Data residency controls let you manage where your data is processed and stored. Two independent settings govern this:
88
99* **Inference geo:** Controls where model inference runs, on a per-request basis. Set through the `inference_geo` API parameter or as a workspace default.
10* **Workspace geo:** Controls where data is stored at rest and where endpoint processing (such as image transcoding and code execution) happens. Configured at the workspace level in the [Claude Console](https://platform.claude.com).
10* **Workspace geo:** Controls where data is stored at rest and where endpoint processing (such as image transcoding and code execution) occurs. Configured at the workspace level in the [Claude Console](https://platform.claude.com).
1111
1212<Note>
1313 [Claude Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview) supports geographic pinning at the agent level: `inference_geo` on an [agent's model configuration](https://platform.claude.com/docs/en/managed-agents/agent-setup#pin-the-inference-geo) pins the geography that serves model requests for sessions running that agent, with [per-session overrides](https://platform.claude.com/docs/en/managed-agents/sessions#pin-the-inference-geo-for-a-session) at session create. Agents without a pin follow the workspace's default inference geo on each request. Managed Agents also respects the Workspace geo configured in Console, and with [self-hosted sandboxes](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes), tool execution and the sandbox filesystem stay on infrastructure you control; the contents of attached [memory stores](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes-memory) remain stored by Anthropic and are copied to your sandbox for the session.
from line 254
254254Data residency pricing varies by model generation:
255255
256256* **Claude 4.6 and later models:** US-only inference (`inference_geo: "us"`) is priced at 1.1x the standard rate across all token pricing categories (input tokens, output tokens, cache writes, and cache reads). On Claude Haiku 5.5, the multiplier also applies to the higher prices for prompts over 100,000 tokens (see [Long context pricing](https://platform.claude.com/docs/en/about-claude/pricing#long-context-pricing)).
257* **Global routing** (`inference_geo: "global"`): Standard pricing applies.
258* **Older models:** Don't support `inference_geo` (see [Model availability](https://platform.claude.com/docs/en/manage-claude/data-residency#model-availability)); standard pricing applies. Requests that include the parameter return a 400 error.
257* **Global routing (`inference_geo: "global"`):** Standard pricing applies.
258* **Older models:** Don't support `inference_geo` (see [Model availability](https://platform.claude.com/docs/en/manage-claude/data-residency#model-availability)). Standard pricing applies. Requests that include the parameter return a 400 error.
259259
260260This pricing applies to the Claude API (first-party) and Claude Platform on AWS. On Claude in Microsoft Foundry, the same 1.1x multiplier applies to deployments hosted on Azure that use the US Data Zone Standard deployment type. Partner-operated platforms (Bedrock and Google Cloud) have their own regional pricing. See [Data residency pricing](https://platform.claude.com/docs/en/about-claude/pricing#data-residency-pricing) for details.
261261
from line 271
271271
272272## Migration from legacy opt-outs
273273
274If your organization previously opted out of global routing to keep inference in the US, your workspace has been automatically configured with `allowed_inference_geos: ["us"]` and `default_inference_geo: "us"`. No code changes are required. Your existing data residency requirements continue to be enforced through the new geo controls.
274If your organization previously opted out of global routing to keep inference in the US, your workspace has been automatically configured with `allowed_inference_geos: ["us"]` and `default_inference_geo: "us"`. No code changes are needed. Your existing data residency requirements continue to be enforced through the new geo controls.
275275
276276### What changed
277277
manage-claude/inference-hooks Changed · +5 / -5 lines
from line 12
1212
1313Because the hook runs on Anthropic's servers, after the request leaves the client and before the model runs, it applies to every governed request uniformly, across all Claude products in your organization, with nothing to install or deploy on user devices.
1414
15There are two hook events. `prompt` fires once per governed inference request, before inference begins. `tool_call` fires when Claude's response contains tool calls, before any of them runs, in organizations that have **Validate tool calls** on.
15There are two hook events. `prompt` fires once for each governed inference request, before inference starts. `tool_call` fires when Claude's response contains tool calls, before any of them runs, in organizations that have **Validate tool calls** on.
1616
1717***
1818
from line 35
3535
3636The Inference hooks system doesn't keep its own copy of prompt or response content. It stores only your hook configuration and metadata about hook activity, such as verdicts, timestamps, and request identifiers. The Claude product you use stores prompts and responses under its own data retention rules, whether or not hooks are on. For example, a message that a hook blocks on claude.ai stays in the conversation.
3737
38If your AI security server is unreachable, returns an error, or doesn't respond within the timeout, your organization's failure handling setting decides the outcome: block the request, or allow it to proceed without inspection. Sustained failures attributable to your server trip a circuit breaker: Anthropic stops contacting it and applies your failure handling setting to every request, then resets the breaker automatically once it detects that your server is returning verdicts again; see [Circuit breaker](https://platform.claude.com/docs/en/manage-claude/inference-hooks-configuration#circuit-breaker).
38If your AI security server is unreachable, returns an error, or doesn't respond within the timeout, your organization's failure handling setting determines the outcome: block the request, or allow it to proceed without inspection. Sustained failures attributable to your server trip a circuit breaker: Anthropic stops contacting it and applies your failure handling setting to every request, then resets the breaker automatically once it detects that your server is returning verdicts again; see [Circuit breaker](https://platform.claude.com/docs/en/manage-claude/inference-hooks-configuration#circuit-breaker).
3939
4040Enforcement can roll out at your pace, so nobody has to be blocked on day one: shadow mode observes verdicts on live traffic without blocking anything, a rollout percentage inspects a chosen fraction of requests, and exclusions exempt members of chosen roles entirely. See [Configure Inference hooks](https://platform.claude.com/docs/en/manage-claude/inference-hooks-configuration).
4141
42For the full request and response schemas, signature verification, and operational details, see [Develop an integration](https://platform.claude.com/docs/en/manage-claude/inference-hooks-endpoint).
42For the full request and response schemas, signature verification, and operational details, see [Develop an Inference hooks integration](https://platform.claude.com/docs/en/manage-claude/inference-hooks-endpoint).
4343
4444***
4545
from line 69
6969
7070* Attachments are represented by metadata and extracted text. Raw file and image bytes are never sent, so image-only content (for example, a screenshot of a document) is not inspected.
7171* Verdicts are allow or deny. Rewriting or redacting a prompt is not supported.
72* Platform organizations (API access through the Claude Platform) are out of scope.
72* Claude Platform organizations (API access through the Platform) are out of scope.
7373
7474***
7575
from line 101
101101| What it does | Allows or denies each governed request in real time | Retrieves activity, chats, files, projects, session transcripts, and users for audit and export |
102102| Direction | Anthropic calls your AI security server | You call Anthropic's API |
103103
104Use Inference hooks to stop a request before it reaches the model, and the [Compliance API](https://platform.claude.com/docs/en/manage-claude/compliance-api) to audit what happened afterward.
104Use Inference hooks to stop a request before it reaches the model, and the [Compliance API](https://platform.claude.com/docs/en/manage-claude/compliance-api) to audit what occurred afterward.
105105
106106***
107107
manage-claude/inference-hooks-configuration Changed · +13 / -13 lines
## Before you start ## Before you begin
from line 10
1010
1111Inference hooks send prompts from your organization to an AI security server you choose, and hold each request for an allow or deny verdict before Claude processes it. This page walks through turning the feature on, connecting your server, and controlling enforcement. To learn what Inference hooks are and when to use them, see the [Inference hooks overview](https://platform.claude.com/docs/en/manage-claude/inference-hooks). To build the AI security server itself, see [Develop an Inference hooks integration](https://platform.claude.com/docs/en/manage-claude/inference-hooks-endpoint).
1212
13## Before you begin
13## Before you start
1414
1515You need:
1616
from line 29
2929 </Step>
3030
3131 <Step title="Open the Inference hooks settings page">
32 Still in **Data and privacy**, open the **Inference hooks** section to reach the Inference hooks settings page. It lives under Data and privacy rather than as its own entry in the settings nav, so its breadcrumb reads **Data and privacy / Inference hooks**. Until you save an endpoint, the page warns that prompts aren't being inspected yet, and **Enforce verdicts** stays off with a **Requires endpoint** badge.
32 Still in **Data and privacy**, open the **Inference hooks** section to reach the Inference hooks settings page. It lives under **Data and privacy** rather than as its own entry in the settings nav, so its breadcrumb reads **Data and privacy / Inference hooks**. Until you save an endpoint, the page warns that prompts aren't being inspected yet, and **Enforce verdicts** stays off with a **Requires endpoint** badge.
3333 </Step>
3434
3535 <Step title="Configure your endpoint">
36 Click **Configure** to open the **Set up endpoint** dialog and enter the **Endpoint URL**: the `https://` URL that receives verdict requests. Only `https://` URLs are accepted.
36 Click **Configure** to open the **Set up endpoint** dialog box and enter the **Endpoint URL**: the `https://` URL that receives verdict requests. Only `https://` URLs are accepted.
3737
38 The dialog asks for nothing else at this point: custom request headers come in step 5, and failure handling in step 6. Click **Next** to save. Once an endpoint is saved, the button reads **Edit**.
38 The dialog box asks for nothing else at this point: custom request headers come in step 5, and failure handling in step 6. Click **Next** to save. Once an endpoint is saved, the button reads **Edit**.
3939 </Step>
4040
4141 <Step title="Store your signing secret">
from line 45
4545 </Step>
4646
4747 <Step title="Add request headers and test the connection">
48 Clicking **Next** on the signing secret dialog reopens the endpoint dialog, now with two more controls:
48 Clicking **Next** on the signing secret dialog box reopens the endpoint dialog box, now with two more controls:
4949
5050 * **Custom request headers:** up to 16 static headers sent with every verdict request so your AI security server can authenticate the caller. Header values are stored encrypted and never shown again; after saving, only the header names are displayed. Because values are write-only, saving any change to the headers requires re-entering every value. Changing the endpoint URL clears all stored header values so your credentials are never sent to a new destination; re-enter them after a URL change. Header names must use standard HTTP token characters with `-` rather than `_`, and must not collide with reserved names (request-framing headers such as `Content-*` and `Host`, proxy and cookie headers, client-address headers such as `X-Forwarded-*`, the `webhook-*` signature headers, and the `X-Anthropic-*` prefix). Values must be printable ASCII.
5151 * **Test connection:** Claude sends a synthetic test prompt to the URL and headers currently in the form, not the saved values, so re-enter any stored header values before testing. On success, the result reports whether your AI security server returned an allow or a deny verdict for the test prompt, which surfaces a deny-everything default before you start enforcing. It also shows the round-trip time Anthropic measured for that one test request, on a new connection, including the connection handshake.
from line 66
6666 </Step>
6767
6868 <Step title="Choose failure handling and timeout">
69 Under **Failure handling**, set **Mode** to choose what happens while the AI security server is unreachable or verdicts time out:
69 Under **Failure handling**, set **Mode** to choose what occurs while the AI security server is unreachable or verdicts time out:
7070
7171 * **Block the request:** stop inference when your AI security server can't deliver a verdict (fail closed).
7272 * **Allow the request:** let the request proceed to the model without inspection (fail open).
7373
74 The dropdown's third option, **Shadow mode**, is a rollout tool rather than a failure policy; see [Shadow mode](https://platform.claude.com/docs/en/manage-claude/inference-hooks-configuration#shadow-mode).
74 The dropdown menu's third option, **Shadow mode**, is a rollout tool rather than a failure policy; see [Shadow mode](https://platform.claude.com/docs/en/manage-claude/inference-hooks-configuration#shadow-mode).
7575
7676 Then set **Prompt verdict timeout (ms)**: 1 to 10,000ms, with a default of 5,000ms. The budget covers the entire exchange, and a slower verdict counts as an unreachable server, so set the lowest value your server can reliably meet.
7777
from line 87
8787 <Step title="Turn on Enforce verdicts">
8888 To evaluate verdicts against live traffic without blocking anyone at first, set **Mode** to **Shadow mode** (step 6) before turning on enforcement; see [Shadow mode](https://platform.claude.com/docs/en/manage-claude/inference-hooks-configuration#shadow-mode).
8989
90 Turn on **Enforce verdicts** to gate Claude on your AI security server's verdict for every governed prompt, then confirm in the dialog, which restates your failure handling choice. Allow about a minute for the change to reach every Anthropic server; requests already in flight finish under the old setting. Turning it off stops prompts from being sent to your AI security server, again within about a minute; your configuration is kept.
90 Turn on **Enforce verdicts** to gate Claude on your AI security server's verdict for every governed prompt, then confirm in the dialog box, which restates your failure handling choice. Allow about a minute for the change to reach every Anthropic server; requests already in flight complete under the old setting. Turning it off stops prompts from being sent to your AI security server, again within about a minute; your configuration is kept.
9191
9292 **Validate tool calls**, below **Enforce verdicts**, also sends the tool calls in each of Claude's responses to your AI security server and waits for its verdict before they run; see [The tool call frame](https://platform.claude.com/docs/en/manage-claude/inference-hooks-endpoint#the-tool-call-frame). It is on by default in a new configuration. It has no effect while **Enforce verdicts** is off, and a change to it takes about a minute to reach every Anthropic server, as with **Enforce verdicts**.
9393 </Step>
from line 101
101101
102102## Exclusions
103103
104Under **Exclusions**, select roles whose members are not covered by Inference hooks: their prompts are never sent to your AI security server. Only custom roles your organization created can be excluded; the built-in roles aren't offered. Pick them in the role selector, whose placeholder reads **Select roles to exclude**, and manage who holds each role from the roles admin page (**Manage roles**); changing exclusions requires identity management permission. The list is empty by default, and with no roles excluded, every governed request is inspected.
104Under **Exclusions**, select roles whose members are not covered by Inference hooks: their prompts are never sent to your AI security server. Only custom roles your organization created can be excluded; the built-in roles aren't offered. Select them in the role selector, whose placeholder reads **Select roles to exclude**, and manage who holds each role from the roles admin page (**Manage roles**); changing exclusions requires identity management permission. The list is empty by default, and with no roles excluded, every governed request is inspected.
105105
106106Exclusion applies to a user's interactive sessions. Changes to the exclusion list are recorded in your organization's [Activity Feed](https://platform.claude.com/docs/en/manage-claude/compliance-activity-feed) as role permission changes (`rbac_role_permission_added` and `rbac_role_permission_removed`).
107107
from line 114
114114The endpoint health area of the Inference hooks settings page shows:
115115
116116* **Endpoint status:** Healthy, Tripped, Not enforcing, or Not configured before an endpoint is saved.
117* **Failures per minute:** webhook failures over the last two minutes, averaged.
117* **Failures per minute:** webhook failures over the last 2 minutes, averaged.
118118* **Block rate:** denials as a share of your AI security server's verdicts, shown while the rollout percentage is below 100.
119119* **Circuit breaker tripped:** when the breaker last tripped, if it has.
120* **Recent errors:** each entry shows when the failure happened, an error type, and a category. The category is `webhook_error` for a problem with your endpoint or the connection to it, or `relay_error` for a failure inside Anthropic's systems. Entries never include request content or your endpoint URL. The list keeps the 10 most recent failures and clears an hour after the last one.
120* **Recent errors:** each entry shows when the failure occurred, an error type, and a category. The category is `webhook_error` for a problem with your endpoint or the connection to it, or `relay_error` for a failure inside Anthropic's systems. Entries never include request content or your endpoint URL. The list keeps the 10 most recent failures and clears an hour after the last one.
121121
122122The error types in **Recent errors** mean:
123123
from line 139
139139
140140Sustained webhook failures attributable to your AI security server trip the circuit breaker, which stops enforcement: your server is no longer contacted, and your **Failure handling** choice applies to every inspected request. With **Block the request** selected, users in your organization are blocked until the breaker resets. When the breaker trips, administrators are also notified in the claude.ai notification center.
141141
142Each trip is also recorded in your organization's [Activity Feed](https://platform.claude.com/docs/en/manage-claude/compliance-activity-feed) as an `inference_hooks_circuit_breaker_tripped` activity, so your security team or vendor can alert on trips from monitoring they already run, such as a SIEM that ingests the feed. One activity is recorded per trip, not one per affected request. Recording requires the Compliance API to be enabled for your organization; see [Set up the Compliance API](https://platform.claude.com/docs/en/manage-claude/compliance-api-access).
142Each trip is also recorded in your organization's [Activity Feed](https://platform.claude.com/docs/en/manage-claude/compliance-activity-feed) as an `inference_hooks_circuit_breaker_tripped` activity, so your security team or vendor can alert on trips from monitoring they already run, such as a SIEM that ingests the feed. One activity is recorded for each trip, not one for each affected request. Recording requires the Compliance API to be enabled for your organization; see [Set up the Compliance API](https://platform.claude.com/docs/en/manage-claude/compliance-api-access).
143143
144144To recover, fix the server, then turn **Enforce verdicts** back on to reset the breaker.
145145
from line 161
161161
162162There are two levels of off:
163163
164* **Enforce verdicts** off, on the Inference hooks settings page: within about a minute, prompts from your organization stop being sent to your AI security server; requests already in flight finish under the old setting. The settings page stays available, so use this to pause enforcement while you work on your AI security server.
164* **Enforce verdicts** off, on the Inference hooks settings page: within about a minute, prompts from your organization stop being sent to your AI security server; requests already in flight complete under the old setting. The settings page stays available, so use this to pause enforcement while you work on your AI security server.
165165* **Allow for your organization** off, in **Data and privacy** settings: prompts are no longer inspected, and the Inference hooks settings become unavailable until you turn it back on. Your endpoint configuration, custom headers, and signing secret are kept either way; turning it back on forces **Enforce verdicts** off and clears a tripped circuit breaker, so turn enforcement on again when you are ready.
166166
167167## Next steps
manage-claude/inference-hooks-endpoint Changed · +9 / -9 lines
from line 158
158158| `User-Agent` | `anthropic-dlp/1` |
159159| `Accept-Encoding` | `identity` |
160160
161There are two hook events, told apart by the top-level `type` field. The prompt frame is sent once per governed inference request, before inference begins. The tool call frame is sent when a model response contains tool calls, before any of them runs, in organizations that have **Validate tool calls** on. Either way, Anthropic waits until your AI security server responds or the verdict timeout elapses.
161There are two hook events, told apart by the top-level `type` field. The prompt frame is sent once for each governed inference request, before inference starts. The tool call frame is sent when a model response contains tool calls, before any of them runs, in organizations that have **Validate tool calls** on. Either way, Anthropic waits until your AI security server responds or the verdict timeout elapses.
162162
163163## The prompt frame
164164
from line 236
236236
237237A turn whose every block is excluded is omitted entirely, so don't assume strict user and assistant alternation.
238238
239Transcripts are sent untruncated, so a long conversation with large attachments produces a large request body. In practice the model's context window keeps bodies under about 10 MB, but the protocol allows up to 64 MiB. Several common defaults are much smaller, including nginx `client_max_body_size` at 1 MB and Express `express.json()` at 100 kB, and a rejected body counts as a webhook failure, so under **Allow the request** failure handling an oversized prompt would reach the model uninspected.
239Transcripts are sent untruncated, so a long conversation with large attachments produces a large request body. In practice the model's context window keeps bodies under about 10 MB, but the protocol allows up to 64 MiB. Several common defaults are much smaller, including nginx `client_max_body_size` at 1 MB and Express `express.json()` at 100 kB, and a rejected body counts as a webhook failure, so under **Allow the request** failure handling an oversized prompt would reach the model uninspected.
240240
241241### Source values
242242
from line 251
251251It differs from the prompt frame in three ways:
252252
253253* `type` is `"tool_call"`.
254* `messages` holds only the latest message, the `assistant` message Claude just produced: any `text` blocks and one `tool_use` block per tool call the frame lists, in the order the model produced them. Earlier conversation is left out, because the prompt frame sent before that model call carried it. Read the last entry of `messages`, because the protocol may later add earlier messages before it.
254* `messages` holds only the latest message, the `assistant` message Claude just produced: any `text` blocks and one `tool_use` block for each tool call the frame lists, in the order the model produced them. Earlier conversation is left out, because the prompt frame sent before that model call carried it. Read the last entry of `messages`, because the protocol may later add earlier messages before it.
255255* Each `tool_use` block carries a `tool_info` object that says who runs or provides the tool.
256256
257257Where `session_id` is set, it is the same on both frames. The tool call frame has its own `request_id`, which is opaque like the prompt frame's.
from line 407
407407* Don't signal a deny with an error status. A non-200 response is a failure, not a deny.
408408* Any `action` value other than `allow` or `deny` is treated as a webhook failure.
409409
410Anthropic reads at most 64 KiB of the response body, and the body must be uncompressed. Redirects are not followed, and cookies are ignored. Unknown fields in the verdict body are ignored, so you can return a richer object alongside the fields documented here.
410Anthropic reads at most 64 KiB of the response body, and the body must be uncompressed. Redirects are not followed, and cookies are ignored. Unknown fields in the verdict body are ignored, so you can return a richer object alongside the fields documented here.
411411
412412A tool call frame takes the same verdict body, and every rule in this section applies to it unchanged. `allow` lets the tool calls run and the response continues; `deny` stops all of them and ends the response with the same error as a denied prompt, including your `deny_reason`. Text delivered before the first tool call is not taken back.
413413
414414## Verify the signature
415415
416Requests are signed per the [Standard Webhooks](https://www.standardwebhooks.com/) specification, using three headers. Anthropic sends the header names in lowercase, and proxies are free to re-case them, so look them up case-insensitively.
416Requests are signed according to the [Standard Webhooks](https://www.standardwebhooks.com/) specification, using three headers. Anthropic sends the header names in lowercase, and proxies are free to re-case them, so look them up case-insensitively.
417417
418418| Header | Contents |
419419| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
420420| `webhook-id` | Unique identifier for this delivery. Equals the body's `request_id`. Use it as an idempotency key and as the first component of the signed payload. |
421| `webhook-timestamp` | Unix time in seconds, as a decimal string, when the request was signed. Reject a timestamp more than five minutes from your server's clock, in either direction. |
421| `webhook-timestamp` | Unix time in seconds, as a decimal string, when the request was signed. Reject a timestamp more than 5 minutes from your server's clock, in either direction. |
422422| `webhook-signature` | One or more space-separated `v1,<base64>` values, each an HMAC-SHA256 over `{webhook-id}.{webhook-timestamp}.{raw body bytes}`. Accept the request if any value matches yours, using a constant-time comparison. |
423423
424424Two details cause most verification bugs:
from line 813
813813
814814### Webhook failures
815815
816Timeouts, non-200 statuses (redirects included), unparseable or oversized response bodies, and unreachable endpoints are all webhook failures. A webhook failure never becomes a deny; instead, your organization's [failure handling](https://platform.claude.com/docs/en/manage-claude/inference-hooks-configuration) setting decides whether the affected request is blocked or proceeds without inspection.
816Timeouts, non-200 statuses (redirects included), unparseable or oversized response bodies, and unreachable endpoints are all webhook failures. A webhook failure never becomes a deny; instead, your organization's [failure handling](https://platform.claude.com/docs/en/manage-claude/inference-hooks-configuration) setting determines whether the affected request is blocked or proceeds without inspection.
817817
818818### Circuit breaker
819819
from line 823
823823
824824Prompt frames and tool call frames share one circuit breaker, and failures on either count toward it.
825825
826Each trip is recorded as an `inference_hooks_circuit_breaker_tripped` activity in the [Activity Feed](https://platform.claude.com/docs/en/manage-claude/compliance-activity-feed), one activity per trip. While the breaker is tripped, no per-request Inference hooks activities are recorded, so the trip activity is the feed's only record of the tripped window.
826Each trip is recorded as an `inference_hooks_circuit_breaker_tripped` activity in the [Activity Feed](https://platform.claude.com/docs/en/manage-claude/compliance-activity-feed), one activity for each trip. While the breaker is tripped, no per-request Inference hooks activities are recorded, so the trip activity is the feed's only record of the tripped window.
827827
828828### Latency
829829
from line 852
852852
853853A production AI security server makes a few design choices beyond the wire protocol.
854854
855**Deduplicate on `webhook-id`.** The `webhook-id` header is unique per delivery and equals the body's `request_id`, and a connection-failure retry reuses it, so it works as an idempotency key. If you record verdicts, key the records on it.
855**Deduplicate on `webhook-id`.** The `webhook-id` header is unique for each delivery and equals the body's `request_id`, and a connection-failure retry reuses it, so it works as an idempotency key. If you record verdicts, key the records on it.
856856
857857**Record verdicts and join denials.** Store each verdict you return along with its `reference_id`. Every denial is recorded as an `inference_hooks_request_denied` compliance activity carrying the `reference_id` your server returned, so you can join denials in the [Activity Feed](https://platform.claude.com/docs/en/manage-claude/compliance-activity-feed) to the matching records in your own system.
858858
manage-claude/opentelemetry Changed · +3 / -3 lines
from line 45
4545| --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
4646| [Messages API](https://platform.claude.com/docs/en/api/messages/create) requests | ✅ | ✅ | ✅ |
4747| [Sessions API](https://platform.claude.com/docs/en/managed-agents/sessions) requests | ✅ | ✅ | ✅ |
48| [Token usage](https://platform.claude.com/docs/en/manage-claude/opentelemetry#count-tokens) | ✅ | ✅ | ✅ |
48| [Token counts](https://platform.claude.com/docs/en/manage-claude/opentelemetry#count-tokens) | ✅ | ✅ | ✅ |
4949| Each model run within a request | — | ✅ | ✅ |
5050| [Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview) turns | — | ✅ | ✅ |
5151| [Server tools](https://platform.claude.com/docs/en/agents-and-tools/tool-use/server-tools), run by the API | — | ✅ | ✅ |
from line 132
132132
133133### Content limits
134134
135With Full content, each span holds up to 0.75 MB of content by default. That fits a 200k-token context window of English text and stays below the 1 MB request limit of many observability platforms and proxies. If you choose a partner platform as the **Provider**, the Console sets a limit that the platform accepts.
135With Full content, each span holds up to 0.75 MB of content by default. That fits a 200k-token context window of English text and stays below the 1 MB request limit of many observability platforms and proxies. If you select a partner platform as the **Provider**, the Console sets a limit that the platform accepts.
136136
137137When content is over the limit, the SDK or the API shortens it and sets `anthropic.content.truncated` to `true`. Request spans drop the system prompt and the earliest messages first, and tool spans drop blocks from the end of the result. Images and documents appear only as their type.
138138
from line 182
1821826. The session tool runner runs `list_issues` (shown as the `anthropic.custom_tool_use list_issues` span) and sends its result by calling `sessions.events.send`.
1831837. A second turn starts with the tool result (shown as the last `anthropic.session.turn` span).
184184
185
185
186186
187187The SDK in your application emits the [`anthropic.custom_tool_use`](https://platform.claude.com/docs/en/api/opentelemetry/spans#anthropic-tool-use-anthropic-mcp-tool-use-and-anthropic-custom-tool-use) span and each request's client span. To get these spans, [set up SDK tracing](https://platform.claude.com/docs/en/manage-claude/opentelemetry/sdk).
188188
manage-claude/opentelemetry/api Changed · +8 / -8 lines
from line 42
4242
4343 | Field | What to enter |
4444 | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
45 | **Provider** | Your observability platform, or **Custom** if it isn't listed. If you choose a listed platform, the Console asks for its details, such as an API key, instead of the **OTLP endpoint URL**, **Request headers**, and **OTLP traces endpoint URL**. |
45 | **Provider** | Your observability platform, or **Custom** if it isn't listed. If you select a listed platform, the Console asks for its details, such as an API key, instead of the **OTLP endpoint URL**, **Request headers**, and **OTLP traces endpoint URL**. |
4646 | **Name** | Optional. For example, "Acme's observability platform." |
4747 | **OTLP endpoint URL** | The base HTTPS URL of your observability platform's OTLP/HTTP endpoint, with a hostname, not an IP address. It can have a path prefix, for example `https://gateway.example.com/otlp`. Don't add `/v1/traces`, because the API adds it. If you set an **OTLP traces endpoint URL**, the API uses that URL instead. |
4848 | **Request headers** | The headers that the API sends with each request, for example your observability platform's API key header. Put credentials only in header values. After you save, the Console doesn't show the values again. |
from line 49
4949 | **Content mode** | **Metadata only** or **Full content**. See [how the content modes differ](https://platform.claude.com/docs/en/manage-claude/opentelemetry#choose-a-content-mode). |
5050 | **Workspaces** | The workspaces whose spans the destination receives: **All**, **Selected**, or **All except**. See [Choose workspaces for a destination](https://platform.claude.com/docs/en/manage-claude/opentelemetry/api#choose-workspaces-for-a-destination). |
5151 | **OTLP traces endpoint URL** | Optional, under **Advanced settings**. The full URL of your observability platform's traces endpoint, for example `https://gateway.example.com/custom/traces`. Set it when the platform receives traces at a URL other than the OTLP endpoint URL plus `/v1/traces`. It must have the same host as the OTLP endpoint URL. |
52 | **Maximum content size in MB** | Optional, under **Advanced settings** when the content mode is **Full content**. The most content that the API sends in each span. The Console fills it in based on the **Provider** you choose. Change it to fit what your observability platform accepts. Leave it empty to send as much as the API allows. See [Content limits](https://platform.claude.com/docs/en/manage-claude/opentelemetry#content-limits). |
52 | **Maximum content size in MB** | Optional, under **Advanced settings** when the content mode is **Full content**. The most content that the API sends in each span. The Console fills it in based on the **Provider** you select. Change it to fit what your observability platform accepts. Leave it empty to send as much as the API allows. See [Content limits](https://platform.claude.com/docs/en/manage-claude/opentelemetry#content-limits). |
5353
54543. Click **Add destination** to save.
5555
from line 677
677677
678678## Troubleshooting
679679
680| Problem | What to check |
681| ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
682| No API spans | The workspace needs an enabled destination that covers it. See [Choose workspaces for a destination](https://platform.claude.com/docs/en/manage-claude/opentelemetry/api#choose-workspaces-for-a-destination). After a destination starts covering a workspace, spans from that workspace can take up to a minute to arrive. |
683| The **Delivery** column shows **Failing** | Hover over it to see the last error. To find the likely cause, see [Delivery errors](https://platform.claude.com/docs/en/manage-claude/opentelemetry/api#delivery-errors). |
684| The **Delivery** column shows **Disabled** | Hover over it to see why. For the reasons, see [when the API disables a destination](https://platform.claude.com/docs/en/manage-claude/opentelemetry/api#delivery-and-health). |
685| Spans have no prompts or responses, but the destination is set to **Full content** | Your organization might have a HIPAA, FERPA, or K-12 data-handling restriction with Anthropic. See [Content modes](https://platform.claude.com/docs/en/manage-claude/opentelemetry#choose-a-content-mode). |
680| Problem | What to check |
681| ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
682| No API spans | The workspace needs an enabled destination that covers it. See [Choose workspaces for a destination](https://platform.claude.com/docs/en/manage-claude/opentelemetry/api#choose-workspaces-for-a-destination). After a destination starts covering a workspace, spans from that workspace can take up to a minute to arrive. The API emits no span for a request whose `traceparent` header has the sampled flag off, or for a request to the [OpenAI-compatible endpoint](https://platform.claude.com/docs/en/cli-sdks-libraries/libraries/openai-sdk). |
683| The **Delivery** column shows **Failing** | Hover over it to see the last error. To find the likely cause, see [Delivery errors](https://platform.claude.com/docs/en/manage-claude/opentelemetry/api#delivery-errors). |
684| The **Delivery** column shows **Disabled** | Hover over it to see why. For the reasons, see [when the API disables a destination](https://platform.claude.com/docs/en/manage-claude/opentelemetry/api#delivery-and-health). |
685| Spans have no prompts or responses, but the destination is set to **Full content** | Your organization might have a HIPAA, FERPA, or K-12 data-handling restriction with Anthropic. See [Content modes](https://platform.claude.com/docs/en/manage-claude/opentelemetry#choose-a-content-mode). |
686686
687687### Delivery errors
688688
manage-claude/opentelemetry/sdk Changed · +4 / -4 lines
from line 594
594594
595595 `@opentelemetry/auto-instrumentations-node`
596596
597 includes. Otherwise, a
597 includes. Otherwise, an
598598
599 [bug in that package](https://github.com/open-telemetry/opentelemetry-js-contrib/issues/3711)
599 [undici instrumentation bug](https://github.com/open-telemetry/opentelemetry-js-contrib/issues/3711)
600600
601601 adds a second
602602
from line 646
646646| ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
647647| No spans from the SDK | Register the tracer provider before the first call. Make sure that you installed the SDK as in [Install the SDK](https://platform.claude.com/docs/en/manage-claude/opentelemetry/sdk#install-the-sdk) and that `ANTHROPIC_OPEN_TELEMETRY` isn't `false`. |
648648| The Java SDK doesn't send spans to the registered tracer provider, or doesn't send trace context to the API | Another dependency sets `opentelemetry-api` below 1.57.0. Upgrade `opentelemetry-api` to 1.57.0 or later. |
649| The SDK spans don't nest under your spans, or the spans that your tool creates don't nest under the tool span | Call the SDK from inside one of your own spans, as the [example in Connect API spans to your own traces](https://platform.claude.com/docs/en/manage-claude/opentelemetry/api#connect-api-spans-to-your-own-traces) does. In Go, start the spans that your tool creates from the `ctx` that the tool runner passes to it. |
649| The SDK spans don't nest under your spans, or the spans that your tool creates don't nest under the tool span | Call the SDK from inside one of your own spans, as the example in [Connect API spans to your own traces](https://platform.claude.com/docs/en/manage-claude/opentelemetry/api#connect-api-spans-to-your-own-traces) does. In Go, start the spans that your tool creates from the `ctx` that the tool runner passes to it. |
650650| Requests from the SDK don't have a `traceparent` header | Register the W3C trace context propagator with OpenTelemetry. The SDK writes the headers with your application's propagator. |
651651| A stream's SDK span doesn't end or doesn't have the response | See [Tracing a stream](https://platform.claude.com/docs/en/manage-claude/opentelemetry/sdk#trace-a-stream). |
652652| Each request appears twice | A request's SDK span and API span have the same name. Use the span kind or `service.name` to tell them apart. If another library also traces the SDK, turn off one of the two. |
manage-claude/organization-webhooks Changed · +1 / -1 lines
## Before you start ## Before you begin
from line 17
1717
1818Any member of your organization can list and view organization webhook endpoints with the API, but only members with the admin, owner, or primary owner role can create, change, or delete them, or replace their signing secret.
1919
20## Before you begin
20## Before you start
2121
2222You need the following:
2323
manage-claude/plugins-api Changed · +69 / -69 lines
from line 17
1717<Note>
1818 The Plugins API is in **beta** and is available to Claude Enterprise organizations only. It is not available to Claude Platform (Claude Console) organizations, or to organizations with HIPAA readiness enabled.
1919
20 Every request must include the [beta header](https://platform.claude.com/docs/en/api/beta-headers) `anthropic-beta: ce-plugins-2026-09-01` (the SDKs and the `ant` CLI send it for you). A request without it returns `404`, exactly as if the endpoint did not exist.
20 Every request must include the [beta header](https://platform.claude.com/docs/en/api/beta-headers) `anthropic-beta: ce-plugins-2026-09-01` (the SDK and the `ant` CLI send it for you). A request without it returns `404`, exactly as if the endpoint did not exist.
2121</Note>
2222
2323## Endpoints
from line 40
4040* Your primary owner creates an Admin API key with the `read:plugins` scope, the `write:plugins` scope, or both in [claude.ai > Organization settings > API](https://claude.ai/admin-settings/api-access). See [Create an Admin API key](https://platform.claude.com/docs/en/manage-claude/admin-api-keys#create-a-key-for-a-claude-enterprise-organization).
4141* Every request carries three headers: `x-api-key`, `anthropic-version: 2023-06-01`, and `anthropic-beta: ce-plugins-2026-09-01`.
4242
43The Python, TypeScript, C#, Go, Java, PHP, and Ruby SDKs expose these endpoints under `client.beta.organization` (csharp, go: `client.Beta.Organization`; java: `client.beta().organization()`; php: `$client->beta->organization`), and the [`ant` CLI](https://platform.claude.com/docs/en/cli-sdks-libraries/cli/quickstart) under `ant beta:organization`; they send the `anthropic-version` and `anthropic-beta` headers for you. The examples on this page use each SDK's default client, which, like the CLI, reads the Admin API key from the `ANTHROPIC_API_KEY` environment variable; the curl examples read the key from the same variable and pass it in the `x-api-key` header. Where an SDK or the CLI fetches more pages for you, `limit` sets the page size, not the total (see [Pagination](https://platform.claude.com/docs/en/manage-claude/plugins-api#pagination)).
43The SDK exposes these endpoints under `client.beta.organization` (csharp, go: `client.Beta.Organization`; java: `client.beta().organization()`; php: `$client->beta->organization`), and the [`ant` CLI](https://platform.claude.com/docs/en/cli-sdks-libraries/cli/quickstart) under `ant beta:organization`; they send the `anthropic-version` and `anthropic-beta` headers for you. The examples on this page use the SDK's default client, which, like the CLI, reads the Admin API key from the `ANTHROPIC_API_KEY` environment variable; the curl examples read the key from the same variable and pass it in the `x-api-key` header. Where an SDK or the CLI fetches more pages for you, `limit` sets the page size, not the total (see [Pagination](https://platform.claude.com/docs/en/manage-claude/plugins-api#pagination)).
4444
4545API keys belong to the organization and keep working after the person who created them leaves. Do not share them or check them into source control.
4646
from line 224
224224
225225A **plugin** is a package that extends Claude for your organization's members. It contains any combination of these components:
226226
227| Component | What it is |
228| ---------- | -------------------------------------------------------------------------------------------------------- |
229| Skill | Instructions and files that Claude loads when a task calls for it. |
230| Command | A saved prompt a member runs by typing `/` followed by the command's name. |
231| Agent | A helper assistant with its own instructions, to which Claude can hand part of a task. |
232| Hook | A command that runs automatically when an event happens in a session, such as before Claude uses a tool. |
233| MCP server | A connection from Claude to tools and data in another system (Model Context Protocol). |
234| CLI | A command-line program that the plugin lets Claude run. |
227| Component | What it is |
228| ---------- | ------------------------------------------------------------------------------------------------------- |
229| Skill | Instructions and files that Claude loads when a task calls for it. |
230| Command | A saved prompt a member runs by typing `/` followed by the command's name. |
231| Agent | A helper assistant with its own instructions, to which Claude can hand part of a task. |
232| Hook | A command that runs automatically when an event occurs in a session, such as before Claude uses a tool. |
233| MCP server | A connection from Claude to tools and data in another system (Model Context Protocol). |
234| CLI | A command-line program that the plugin lets Claude run. |
235235
236236Every plugin has a manifest at `.claude-plugin/plugin.json`. The manifest's `name` becomes the plugin's `name`: a lowercase identifier that is unique within its marketplace.
237237
from line 257
257257
258258Every upload creates a new, immutable **version**, whether it comes from this API, from claude.ai, or from a Git synchronization. A plugin has two pointers to its versions:
259259
260* `latest_version_id`: the newest version.
261* `served_version_id`: the version members are served.
260* `latest_version_id`: the newest version
261* `served_version_id`: the version members are served
262262
263263By default `served_version_pinned` is `false`: the served version normally follows the newest one. When content scanning is on, a version saved in claude.ai might **wait** for its content scan: it is stored but not served at once. This page uses "wait" only for a version saved in claude.ai, not for one that a member or an administrator publishes into a plugin. While scanning is on, a waiting version is served only after its scan completes with `pass` or `warn`. While a version waits, `served_version_id` names an earlier version than `latest_version_id`. Do not poll until the two IDs match: they might stay different, for example when the scan fails.
264264
265265Choosing a version with `POST /v1/organizations/plugins/{plugin_id}` **pins** the plugin (`served_version_pinned: true`). So does an administrator choosing a version in claude.ai, or accepting a member's request to publish into the plugin. From then on, new uploads are stored and advance `latest_version_id`, but members keep the pinned version until you point `served_version_id` at another one. A plugin whose two pointers differ has a stored version that is not being served.
266266
267This lets a release pipeline upload each build, test it, and then promote it. To have your pipeline decide when each build is served, pin the plugin once by setting `served_version_id` to its current version; from then on, promote each build you want served. With content scanning on, that first pin returns `409 scan_pending` until the current version's scan completes, and `400 scan_failed` if the scan completed with `fail` or `unknown`, or errored (`warn` is accepted). A pinned plugin cannot currently be unpinned, here or in claude.ai.
267This lets a release pipeline upload each build, test it, and then promote it. To have your pipeline determine when each build is served, pin the plugin once by setting `served_version_id` to its current version; from then on, promote each build you want served. With content scanning on, that first pin returns `409 scan_pending` until the current version's scan completes, and `400 scan_failed` if the scan completed with `fail` or `unknown`, or errored (`warn` is accepted). A pinned plugin cannot currently be unpinned, here or in claude.ai.
268268
269269To roll back, set `served_version_id` to an earlier version. Roll forward the same way.
270270
from line 272
272272
273273### Installation settings
274274
275**Installation settings** decide who can use an organization-owned plugin. Each setting has one of four values, carried in the fields named `installation_preference` (and, on the plugin and marketplace objects, `organization_installation_preference` and `default_installation_preference`):
275**Installation settings** determine who can use an organization-owned plugin. Each setting has one of four values, carried in the fields named `installation_preference` (and, on the plugin and marketplace objects, `organization_installation_preference` and `default_installation_preference`):
276276
277277| Value | Members see |
278278| --------------- | ---------------------------------------------- |
from line 281
281281| `available` | The plugin can be installed on request. |
282282| `not_available` | The plugin is hidden. |
283283
284A plugin can hold an organization-wide setting and one setting per group (the role-based access control groups managed in [User management](https://platform.claude.com/docs/en/manage-claude/user-management#groups)). A member gets a value by these rules:
284A plugin can hold an organization-wide setting and one setting for each group (the role-based access control groups managed in [User management](https://platform.claude.com/docs/en/manage-claude/user-management#groups)). A member gets a value by these rules:
285285
2862861. The organization-wide value is the plugin's own organization-wide setting if it has one, otherwise its marketplace's default, otherwise `not_available`. The plugin reports this value in `organization_installation_preference`, with `organization_installation_preference_inherited: true` while it comes from the marketplace default.
2872872. A member who belongs to no group holding a setting for the plugin gets the organization-wide value.
from line 293
293293
294294### Shares
295295
296**Shares** decide who can use a member-owned plugin. The owner shares it in claude.ai with every member, with a group, or with named members. This API lists shares but cannot change them.
296**Shares** determine who can use a member-owned plugin. The owner shares it in claude.ai with every member, with a group, or with named members. This API lists shares but cannot change them.
297297
298298If your organization has turned off a kind of sharing in its claude.ai settings, shares of that kind still appear in the list but give no one access while that setting is off; the list itself does not show whether it is.
299299
from line 334
334334* No file may be under a top-level `bin/` directory.
335335* No nested `.zip` files. Packaged MCP servers (`.mcpb`, `.dxt`) are allowed.
336336* File paths must be relative, contain no `..`, and use only letters, digits, spaces, and `_ . - / ( ) ,`.
337* The request body and the uncompressed archive are each at most 200 MB; a request body over the limit returns `413` (`request_too_large`) rather than `400`. An upload has at most 5,000 files, a path depth of 12, paths of 472 characters, and file or folder names of 255 characters.
337* The request body and the uncompressed archive are each at most 200 MB; a request body over the limit returns `413` (`request_too_large`) rather than `400`. An upload has at most 5,000 files, a path depth of 12, paths of 472 characters, and file or folder names of 255 characters.
338338* ZIP archives must use DEFLATE or STORE compression, and cannot be encrypted or contain symbolic links.
339339* A marketplace holds at most 500 items, counting its plugins and any standalone skills members keep in it. This limit and the 5,000-file limit are current values that may be raised.
340340
from line 342
342342
343343### Publish each build from a release pipeline
344344
345Upload every tagged build from CI, and let the pipeline decide when a build is served.
345Upload every tagged build from CI, and let the pipeline determine when a build is served.
346346
3473471. Find the marketplace to upload into with `GET /v1/organizations/plugin_marketplaces?owner_type=organization`, or omit `marketplace_id` to use the library marketplace.
3483482. On the first release, [create the plugin](https://platform.claude.com/docs/en/manage-claude/plugins-api#create-a-plugin) with `POST /v1/organizations/plugins`. On every later release, record the plugin's `latest_version_id`, then [upload a version](https://platform.claude.com/docs/en/manage-claude/plugins-api#create-a-version) with `POST /v1/organizations/plugins/{plugin_id}/versions`. If the upload's response is lost, read the plugin and retry only if `latest_version_id` is unchanged (see [Retrying uploads](https://platform.claude.com/docs/en/manage-claude/plugins-api#retrying-uploads)).
from line 504
504504
505505The plugin object describes a plugin in one of your organization's marketplaces or in a member's personal marketplace (the [Quick start](https://platform.claude.com/docs/en/manage-claude/plugins-api#quick-start) response shows a complete one). Its `display_name`, `description`, `manifest_version`, `content_scan`, `components`, and `reach` describe its **served** version, so one list call shows what members are being served.
506506
507| Field | Description |
508| ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
509| `id` | Prefixed `plugin_`. |
510| `name` | From the manifest. Unique within its marketplace, not across the organization. Fixed for an organization-owned plugin; changes if a member renames their own plugin in claude.ai. |
511| `display_name`, `description`, `manifest_version` | The served version's manifest `displayName`, `description`, and `version`; each is `null` when the manifest declares none. `manifest_version` is normalized for display: one leading `v` or `V` is dropped, so a manifest `version` of `"v1.4.0"` is returned as `"1.4.0"`. It is also `null` for a value that does not look like a version number, such as `"latest"`, and for a plugin version created before claude.ai began recording this field in August 2026. An upload is never refused because of its `version`, and `manifest_version` is not unique. |
512| `served_version_id`, `latest_version_id` | Prefixed `pluginver_`: the version members are served, and the newest version. See [Versions and the served version](https://platform.claude.com/docs/en/manage-claude/plugins-api#versions-and-the-served-version). |
513| `served_version_pinned` | `false` until the plugin is pinned (see [Versions and the served version](https://platform.claude.com/docs/en/manage-claude/plugins-api#versions-and-the-served-version)); `true` from then on. |
514| `owner` | `{"type": "organization"}`, or `{"type": "user", "user_id": "user_..."}` for a member's personal marketplace. |
515| `marketplace_id` | Prefixed `marketplace_`. |
516| `created_by` | Who created the plugin: `{"type": "user_actor", "user_id": "user_...", "email_address": "..."}` for a person in claude.ai (`email_address` may be `null`), or `{"type": "api_actor", "api_key_id": "apikey_..."}` for an API key. Other actor types may appear. `null` when no creator is recorded, such as for plugins synchronized from Git. |
517| `organization_installation_preference`, `organization_installation_preference_inherited` | Organization-owned: the organization-wide value, and whether it comes from the marketplace's default (see [Installation settings](https://platform.claude.com/docs/en/manage-claude/plugins-api#installation-settings)). Member-owned: both `null`. |
518| `content_scan` | The served version's scan result, an object with `status`, `assessment`, and `reason` (described after this table). `null` when it was never scanned. |
519| `components` | The served version's components, each `{"type", "name", "description"}` with `type` one of `skill`, `mcp_server`, `command`, `agent`, `hook`, or `cli`, listed in that type order and then by name. For an MCP server, `name` is its key in the manifest; for a hook, the event it runs on; for a CLI, the executable's name. `description` is always `null` for MCP servers, hooks, and CLIs. `null` when not recorded. |
520| `reach` | `contained`, `privileged`, or `remote`. See [Reach](https://platform.claude.com/docs/en/manage-claude/plugins-api#reach). |
521| `updated_at` | Changes when the served version changes, when the plugin becomes pinned, and normally when a new version is stored. A waiting version (see [Versions and the served version](https://platform.claude.com/docs/en/manage-claude/plugins-api#versions-and-the-served-version)) changes it when it becomes the served version, not necessarily when it is stored. It does not change for installation settings, shares, or new scan results. |
507| Field | Description |
508| ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
509| `id` | Prefixed `plugin_`. |
510| `name` | From the manifest. Unique within its marketplace, not across the organization. Fixed for an organization-owned plugin; changes if a member renames their own plugin in claude.ai. |
511| `display_name`, `description`, `manifest_version` | The served version's manifest `displayName`, `description`, and `version`; each is `null` when the manifest declares none. `manifest_version` is normalized for display: one leading `v` or `V` is dropped, so a manifest `version` of `"v1.4.0"` is returned as `"1.4.0"`. It is also `null` for a value that does not look like a version number, such as `"latest"`, and for a plugin version created before claude.ai started recording this field in August 2026. An upload is never refused because of its `version`, and `manifest_version` is not unique. |
512| `served_version_id`, `latest_version_id` | Prefixed `pluginver_`: the version members are served, and the newest version. See [Versions and the served version](https://platform.claude.com/docs/en/manage-claude/plugins-api#versions-and-the-served-version). |
513| `served_version_pinned` | `false` until the plugin is pinned (see [Versions and the served version](https://platform.claude.com/docs/en/manage-claude/plugins-api#versions-and-the-served-version)); `true` from then on. |
514| `owner` | `{"type": "organization"}`, or `{"type": "user", "user_id": "user_..."}` for a member's personal marketplace. |
515| `marketplace_id` | Prefixed `marketplace_`. |
516| `created_by` | Who created the plugin: `{"type": "user_actor", "user_id": "user_...", "email_address": "..."}` for a person in claude.ai (`email_address` might be `null`), or `{"type": "api_actor", "api_key_id": "apikey_..."}` for an API key. Other actor types may appear. `null` when no creator is recorded, such as for plugins synchronized from Git. |
517| `organization_installation_preference`, `organization_installation_preference_inherited` | Organization-owned: the organization-wide value, and whether it comes from the marketplace's default (see [Installation settings](https://platform.claude.com/docs/en/manage-claude/plugins-api#installation-settings)). Member-owned: both `null`. |
518| `content_scan` | The served version's scan result, an object with `status`, `assessment`, and `reason` (described after this table). `null` when it was never scanned. |
519| `components` | The served version's components, each `{"type", "name", "description"}` with `type` one of `skill`, `mcp_server`, `command`, `agent`, `hook`, or `cli`, listed in that type order and then by name. For an MCP server, `name` is its key in the manifest; for a hook, the event it runs on; for a CLI, the executable's name. `description` is always `null` for MCP servers, hooks, and CLIs. `null` when not recorded. |
520| `reach` | `contained`, `privileged`, or `remote`. See [Reach](https://platform.claude.com/docs/en/manage-claude/plugins-api#reach). |
521| `updated_at` | Changes when the served version changes, when the plugin becomes pinned, and normally when a new version is stored. A waiting version (see [Versions and the served version](https://platform.claude.com/docs/en/manage-claude/plugins-api#versions-and-the-served-version)) changes it when it becomes the served version, not necessarily when it is stored. It does not change for installation settings, shares, or new scan results. |
522522
523523The `content_scan` object:
524524
from line 536
536536 | `remote-code-instruction-loader` | Tells Claude to download and run, or follow instructions from, outside content that can change after the plugin is installed. |
537537 | `credential-exposure` | Contains live credentials, or collects credentials or tokens from the member's environment. |
538538 | `guardrail-tampering` | Weakens the member's safeguards, for example by pre-approving every permission prompt. |
539 | `system-prompt-spoofing` | Imitates or tries to replace Claude's system instructions. |
539 | `system-prompt-spoofing` | Imitates or attempts to replace Claude's system instructions. |
540540 | `covert-record-tampering` | Quietly changes, hides, or deletes information the member would otherwise see. |
541541 | `covert-behavior-override` | Changes Claude's behavior beyond the plugin's purpose and hides the change from the member. |
542542 | `hidden-code-execution` | Runs bundled code while telling Claude not to reveal what it does. |
from line 551
551551
552552### List plugins
553553
554`GET /v1/organizations/plugins` lists every plugin in your organization, in the organization's marketplaces and in members' personal marketplaces, ordered by `created_at` descending. Filter by `owner_type` (`organization` or `user`), `owner_user_id` (prefixed `user_`; a member's plugins, including after the member leaves the organization), `marketplace_id`, and `created_at[gte]`, `created_at[gt]`, `created_at[lte]`, `created_at[lt]` (RFC 3339 timestamps). Filters combine with AND. A `marketplace_id` or `owner_user_id` that matches nothing in your organization returns an empty page, not an error. The response has the shape shown in [Quick start](https://platform.claude.com/docs/en/manage-claude/plugins-api#quick-start). Requires the `read:plugins` scope.
554`GET /v1/organizations/plugins` lists every plugin in your organization, in the organization's marketplaces and in members' personal marketplaces, ordered by `created_at` descending. Filter by `owner_type` (`organization` or `user`), `owner_user_id` (an ID prefixed `user_`, which finds a member's plugins, including after the member leaves the organization), `marketplace_id`, and `created_at[gte]`, `created_at[gt]`, `created_at[lte]`, `created_at[lt]` (RFC 3339 timestamps). Filters combine with AND. A `marketplace_id` or `owner_user_id` that matches nothing in your organization returns an empty page, not an error. The response has the shape shown in [Quick start](https://platform.claude.com/docs/en/manage-claude/plugins-api#quick-start). Requires the `read:plugins` scope.
555555
556556<CodeGroup>
557557 ```bash cURL
from line 668
668668
669669### Create a plugin
670670
671`POST /v1/organizations/plugins` creates an organization-owned plugin and its first version in one call; the version becomes the served version. The body is `multipart/form-data`: `files[]` is either one `.zip` or `.plugin` archive, or one part per file, where each part's filename is the file's path within the plugin (for example `.claude-plugin/plugin.json`). Optional fields are `marketplace_id` (an organization-owned `manual` marketplace; defaults to your library marketplace, which is created on first use) and `release_notes` (up to 5,000 characters, shown in claude.ai's version history and returned on the version). The plugin's `name`, `display_name`, `description`, and `manifest_version` come from the uploaded manifest, and the upload must meet the [upload requirements](https://platform.claude.com/docs/en/manage-claude/plugins-api#upload-requirements). When content scanning is on, the response's `content_scan.status` is `processing` and the verdict arrives asynchronously. Returns the plugin. Requires the `write:plugins` scope.
671`POST /v1/organizations/plugins` creates an organization-owned plugin and its first version in one call; the version becomes the served version. The body is `multipart/form-data`: `files[]` is either one `.zip` or `.plugin` archive, or one part for each file, where each part's file name is the file's path within the plugin (for example `.claude-plugin/plugin.json`). Optional fields are `marketplace_id` (an organization-owned `manual` marketplace, or by default your library marketplace, which is created on first use) and `release_notes` (up to 5,000 characters, shown in claude.ai's version history and returned on the version). The plugin's `name`, `display_name`, `description`, and `manifest_version` come from the uploaded manifest, and the upload must meet the [upload requirements](https://platform.claude.com/docs/en/manage-claude/plugins-api#upload-requirements). When content scanning is on, the response's `content_scan.status` is `processing` and the verdict arrives asynchronously. Returns the plugin. Requires the `write:plugins` scope.
672672
673673Upload an archive:
674674
from line 836
836836}
837837```
838838
839Upload individual files into a named marketplace. Attach each file under its path within the plugin (the `;filename=` suffix in the cURL example, the filename arguments in the SDK examples); a file sent under its base name alone means the manifest is not found. The TypeScript and Java SDKs and the `ant` CLI cannot yet attach files under a path, so those examples upload the plugin as one archive into the marketplace instead:
839Upload individual files into a named marketplace. Attach each file under its path within the plugin (the `;filename=` suffix in the cURL example, the file name arguments in the SDK examples); a file sent under its base name alone means the manifest is not found. The TypeScript and Java SDKs and the `ant` CLI cannot yet attach files under a path, so those examples upload the plugin as one archive into the marketplace instead:
840840
841841<CodeGroup>
842842 ```bash cURL
from line 1030
10301030 ```
10311031</CodeGroup>
10321032
1033Besides a `400` for an upload that breaks the [upload requirements](https://platform.claude.com/docs/en/manage-claude/plugins-api#upload-requirements) (`413` for a request body over 200 MB) and the shared responses (a `403` when `marketplace_id` is a member's personal marketplace; see [Error responses](https://platform.claude.com/docs/en/manage-claude/plugins-api#error-responses)), a create can fail with:
1033Besides a `400` for an upload that breaks the [upload requirements](https://platform.claude.com/docs/en/manage-claude/plugins-api#upload-requirements) (`413` for a request body over 200 MB) and the shared responses (a `403` when `marketplace_id` is a member's personal marketplace; see [Error responses](https://platform.claude.com/docs/en/manage-claude/plugins-api#error-responses)), a create can fail with:
10341034
10351035| Status | Cause | What to do |
10361036| -------------------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
from line 1395
13951395
13961396## Plugin versions
13971397
1398A plugin version is an immutable snapshot of a plugin's files from one upload (the [Create a version](https://platform.claude.com/docs/en/manage-claude/plugins-api#create-a-version) response shows a complete object). Its fields mirror the plugin's served-version fields (`display_name`, `description`, `manifest_version`, `content_scan`, `components`, `reach`) for this version, plus `release_notes` (as supplied with the upload; shown in claude.ai's version history) and `created_by` (who uploaded it).
1398A plugin version is an immutable snapshot of a plugin's files from one upload (the [Create a version](https://platform.claude.com/docs/en/manage-claude/plugins-api#create-a-version) response shows a complete object). Its fields mirror the plugin's served-version fields (`display_name`, `description`, `manifest_version`, `content_scan`, `components`, `reach`) for this version, plus `release_notes` (as supplied with the upload and shown in claude.ai's version history) and `created_by` (who uploaded it).
13991399
14001400A `{version}` that lacks the `pluginver_` prefix returns `400` (except the literal `latest` where noted). One that carries the prefix but does not identify a version of that plugin returns `404`.
14011401
from line 1700
17001700}
17011701```
17021702
1703Besides a `400` for an upload that breaks the [upload requirements](https://platform.claude.com/docs/en/manage-claude/plugins-api#upload-requirements) (`413` for a request body over 200 MB) and the shared responses (a `403` for a member-owned plugin; see [Error responses](https://platform.claude.com/docs/en/manage-claude/plugins-api#error-responses)), the request can fail with:
1703Besides a `400` for an upload that breaks the [upload requirements](https://platform.claude.com/docs/en/manage-claude/plugins-api#upload-requirements) (`413` for a request body over 200 MB) and the shared responses (a `403` for a member-owned plugin; see [Error responses](https://platform.claude.com/docs/en/manage-claude/plugins-api#error-responses)), the request can fail with:
17041704
17051705| Status | Cause | What to do |
17061706| -------------------------- | -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
from line 1822
18221822
18231823### Download a version's files
18241824
1825`GET /v1/organizations/plugins/{plugin_id}/versions/{version}/content` downloads a version's files as the stored `.zip` archive (`Content-Type: application/zip`). The archive is returned whatever its content-scan result, so you can inspect versions withheld from members. It is served exactly as stored, so for an organization-owned plugin in a `manual` marketplace you can re-upload it unchanged as a new version, provided it meets the current upload requirements. `{version}` must be a version ID, not `latest`: read the plugin's `served_version_id` or `latest_version_id` first, or resolve `latest` with `GET /v1/organizations/plugins/{plugin_id}/versions/latest`. The `Content-Disposition` filename is derived from the plugin's name and is not unique; name saved files by plugin and version ID. Requires the `read:plugins` scope.
1825`GET /v1/organizations/plugins/{plugin_id}/versions/{version}/content` downloads a version's files as the stored `.zip` archive (`Content-Type: application/zip`). The archive is returned whatever its content-scan result, so you can inspect versions withheld from members. It is served exactly as stored, so for an organization-owned plugin in a `manual` marketplace you can re-upload it unchanged as a new version, provided it meets the current upload requirements. `{version}` must be a version ID, not `latest`: read the plugin's `served_version_id` or `latest_version_id` first, or resolve `latest` with `GET /v1/organizations/plugins/{plugin_id}/versions/latest`. The `Content-Disposition` file name is derived from the plugin's name and is not unique; name saved files by plugin and version ID. Requires the `read:plugins` scope.
18261826
18271827Downloading a **member-owned** plugin's archive records a `claude_plugin_archive_accessed` event on the Compliance API Activity Feed, identifying the key (as `api_actor`), the plugin and its marketplace, the version, and the owning member by ID; it carries no names. Downloading an organization-owned plugin's archive records nothing.
18281828
from line 2483
24832483}
24842484```
24852485
2486| Field | Description |
2487| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2488| `name` | The marketplace's name. Fixed for its lifetime. |
2489| `owner` | Same shape as on the plugin. |
2490| `source` | `manual`, `github`, `gitlab`, or `public_git`. See [Marketplaces](https://platform.claude.com/docs/en/manage-claude/plugins-api#marketplaces). |
2491| `sync_status` | Outcome of the most recent synchronization: `success`, `in_progress`, `failed_content`, `failed_transient`, `failed_auth`, or `failed_limits`. `null` until a synchronization is first attempted, which never happens for a marketplace whose source is `manual`. |
2492| `last_sync_ended_at` | When the most recent synchronization attempt finished, whatever its outcome; for a connected repository that has not synchronized yet, when the marketplace was created. `null` for a marketplace that is not synchronized. |
2493| `last_sync_read_sha` | The commit the last synchronization read from the repository. Not necessarily the commit the served versions came from. `null` for a marketplace that is not synchronized. |
2494| `default_installation_preference` | Organization marketplaces: the organization-wide value for every plugin in it without its own setting (`not_available` if never set). Personal marketplaces: `null`. |
2486| Field | Description |
2487| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2488| `name` | The marketplace's name. Fixed for its lifetime. |
2489| `owner` | Same shape as on the plugin. |
2490| `source` | `manual`, `github`, `gitlab`, or `public_git`. See [Marketplaces](https://platform.claude.com/docs/en/manage-claude/plugins-api#marketplaces). |
2491| `sync_status` | Outcome of the most recent synchronization: `success`, `in_progress`, `failed_content`, `failed_transient`, `failed_auth`, or `failed_limits`. `null` until a synchronization is first attempted, which never occurs for a marketplace whose source is `manual`. |
2492| `last_sync_ended_at` | When the most recent synchronization attempt finished, whatever its outcome; for a connected repository that has not synchronized yet, when the marketplace was created. `null` for a marketplace that is not synchronized. |
2493| `last_sync_read_sha` | The commit the last synchronization read from the repository. Not necessarily the commit the served versions came from. `null` for a marketplace that is not synchronized. |
2494| `default_installation_preference` | Organization marketplaces: the organization-wide value for every plugin in it without its own setting (`not_available` if never set). Personal marketplaces: `null`. |
24952495
24962496A `marketplace_id` that lacks the `marketplace_` prefix returns `400`. One that carries the prefix but does not resolve, or belongs to another organization, returns `404`.
24972497
from line 2838
28382838
28392839### Validate marketplace content
28402840
2841Two endpoints report what a synchronization of the given marketplace content would do, without connecting or storing anything: `POST /v1/organizations/plugin_marketplaces/validate_repository` reads a public GitHub repository, and `POST /v1/organizations/plugin_marketplaces/validate_archive` reads a `.zip` of the marketplace directory that you upload. Both return the same report: whether `marketplace.json` is well-formed, which plugins would be skipped and why, and which plugins would synchronize with some contents left out. The checks are the ones a real synchronization runs. Problems with the content come back in the report, not as HTTP errors: the request succeeds with `valid: false`, even when the repository or archive cannot be read at all. A validation counts as a read, and the two endpoints together are additionally limited to 10 validations a minute per organization (see [Rate limiting](https://platform.claude.com/docs/en/manage-claude/plugins-api#rate-limiting)); they record nothing on the Activity Feed. A validation can take up to 120 seconds before it returns, so set your client's timeout above that. Both endpoints require the `read:plugins` or `write:plugins` scope (`read:org_audit` and `read:compliance_org_data` do not grant them).
2841Two endpoints report what a synchronization of the given marketplace content would do, without connecting or storing anything: `POST /v1/organizations/plugin_marketplaces/validate_repository` reads a public GitHub repository, and `POST /v1/organizations/plugin_marketplaces/validate_archive` reads a `.zip` of the marketplace directory that you upload. Both return the same report: whether `marketplace.json` is well-formed, which plugins would be skipped and why, and which plugins would synchronize with some contents left out. The checks are the ones a real synchronization runs. Problems with the content come back in the report, not as HTTP errors: the request succeeds with `valid: false`, even when the repository or archive cannot be read at all. A validation counts as a read, and the two endpoints together are additionally limited to 10 validations a minute for each organization (see [Rate limiting](https://platform.claude.com/docs/en/manage-claude/plugins-api#rate-limiting)); they record nothing on the Activity Feed. A validation can take up to 120 seconds before it returns, so set your client's timeout above that. Both endpoints require the `read:plugins` or `write:plugins` scope (`read:org_audit` and `read:compliance_org_data` do not grant them).
28422842
28432843The repository, and any plugin source outside it on GitHub, are read anonymously, so a private repository or private plugin source is reported as not found. Plugin sources on hosts other than GitHub are not fetched; such a plugin normally gets a `marketplace_validate_source_not_checked` warning and is checked when the marketplace actually synchronizes. If the repository is, or the archive names, a marketplace that Anthropic synchronizes into every organization, stricter rules apply: every plugin source outside the marketplace must be pinned to a full commit SHA, unpinned or unsupported-host sources are reported as plugin errors, and the branch read defaults to the one that marketplace synchronizes from.
28442844
2845`validate_repository` takes a JSON body with two fields: `repository_url`, the `https://` URL of a public repository on github.com (required), and `ref`, a branch name or a full 40-character commit SHA (optional; when it is omitted or `null`, the branch a synchronization would read, usually the repository's default branch). `validate_archive` takes `multipart/form-data` with exactly one part, `archive`, sent as a file part with a filename: a `.zip` of the marketplace directory, at most 32 MB, with its contents at the root or wrapped in one folder (as a Git host's download produces), DEFLATE or STORE compression only. No other form field is accepted.
2845`validate_repository` takes a JSON body with two fields: `repository_url`, the `https://` URL of a public repository on github.com (required), and `ref`, a branch name or a full 40-character commit SHA (optional; when it is omitted or `null`, the branch a synchronization would read, usually the repository's default branch). `validate_archive` takes `multipart/form-data` with exactly one part, `archive`, sent as a file part with a file name: a `.zip` of the marketplace directory, at most 32 MB, with its contents at the root or wrapped in one folder (as a Git host's download produces), DEFLATE or STORE compression only. No other form field is accepted.
28462846
28472847Validate a public repository at a branch:
28482848
from line 2991
29912991}
29922992```
29932993
2994| Field | Description |
2995| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2996| `valid` | `true` when `marketplace.json` is well-formed and no plugin would be skipped. Warnings do not make it `false`. |
2997| `ref` | The branch that was read, by name; `null` when no branch was named and the default branch was read, for a commit SHA, or for an archive. |
2998| `commit_sha` | The commit that was validated. For an archive downloaded from a Git host, the commit the host recorded in the ZIP file's comment field, if any (not verified). |
2999| `total_plugin_count` | How many plugins `marketplace.json` declares; `0` when it could not be read. |
3000| `manifest_error`, `manifest_error_code` | Set when nothing could be validated: the source could not be read, or `marketplace.json` is missing, malformed, or over a limit. Validation that did not finish within 120 seconds reports `manifest_error_code: "marketplace_validate_deadline_exceeded"`. |
3001| `plugin_errors` | One `{name, error, error_code}` per plugin a synchronization would skip. |
3002| `plugin_warnings` | One `{name, warnings: [{message, error_code}]}` per plugin that would synchronize with some contents left out. |
2994| Field | Description |
2995| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2996| `valid` | `true` when `marketplace.json` is well-formed and no plugin would be skipped. Warnings do not make it `false`. |
2997| `ref` | The branch that was read, by name; `null` when no branch was named and the default branch was read, for a commit SHA, or for an archive. |
2998| `commit_sha` | The commit that was validated. For an archive downloaded from a Git host, the commit the host recorded in the ZIP file's comment field, if any (not verified). |
2999| `total_plugin_count` | How many plugins `marketplace.json` declares; `0` when it could not be read. |
3000| `manifest_error`, `manifest_error_code` | Set when nothing could be validated: the source could not be read, or `marketplace.json` is missing, malformed, or over a limit. Validation that did not complete within 120 seconds reports `manifest_error_code: "marketplace_validate_deadline_exceeded"`. |
3001| `plugin_errors` | One `{name, error, error_code}` per plugin a synchronization would skip. |
3002| `plugin_warnings` | One `{name, warnings: [{message, error_code}]}` per plugin that would synchronize with some contents left out. |
30033003
30043004Validate a local copy of the marketplace directory instead, as a `.zip`; the response is the same report:
30053005
from line 3120
31203120
31213121Problems with the content never fail the request. Apart from the responses every endpoint shares (a `403` for a key that has only `read:org_audit` or `read:compliance_org_data`; see [Error responses](https://platform.claude.com/docs/en/manage-claude/plugins-api#error-responses) and [Rate limiting](https://platform.claude.com/docs/en/manage-claude/plugins-api#rate-limiting)), the request itself can fail with:
31223122
3123| Status | Cause | What to do |
3124| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------ |
3125| 400 | On `validate_repository`: the body is not a JSON object; `repository_url` is missing, is longer than 2,048 characters, carries credentials, or is not of the form `https://github.com/{owner}/{repo}` (a `.git` suffix is accepted; another host, a longer path such as a branch page's `/tree/main`, or a port other than 443 or 80 is not); `ref` is empty, is longer than 255 characters, contains `..`, or contains a character other than ASCII letters, digits, `.`, `_`, `-`, `+`, and `/`; or another field is present. A `ref` that passes these checks but names a branch the repository does not have is not refused: the request succeeds with `valid: false` and `manifest_error` says the branch was not found. On `validate_archive`: the body is not `multipart/form-data`, the `archive` part is missing, repeated, or not sent as a file part with a filename, or another form field is present. | Fix the request and resend. |
3126| 413 | On `validate_archive`: the `archive` part, or the request's declared body length, exceeds 32 MB. | Validate the repository by URL instead, or trim the archive. |
3123| Status | Cause | What to do |
3124| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
3125| 400 | On `validate_repository`: the body is not a JSON object; `repository_url` is missing, is longer than 2,048 characters, carries credentials, or is not of the form `https://github.com/{owner}/{repo}` (a `.git` suffix is accepted; another host, a longer path such as a branch page's `/tree/main`, or a port other than 443 or 80 is not); `ref` is empty, is longer than 255 characters, contains `..`, or contains a character other than ASCII letters, digits, `.`, `_`, `-`, `+`, and `/`; or another field is present. A `ref` that passes these checks but names a branch the repository does not have is not refused: the request succeeds with `valid: false` and `manifest_error` says the branch was not found. On `validate_archive`: the body is not `multipart/form-data`, the `archive` part is missing, repeated, or not sent as a file part with a file name, or another form field is present. | Fix the request and resend. |
3126| 413 | On `validate_archive`: the `archive` part, or the request's declared body length, exceeds 32 MB. | Validate the repository by URL instead, or trim the archive. |
31273127
31283128#### Report codes
31293129
3130Each finding in a report has a stable code: `manifest_error_code` when nothing could be validated, `error_code` on each `plugin_errors` entry, and `error_code` on each warning. When a plugin has several problems, `error_code` is the first one's and `error` joins their messages. New codes may be added; an unrecognized `manifest_error_code` still means the content could not be validated, one on a `plugin_errors` entry still means the plugin would be skipped, and one on a warning still means the plugin would synchronize. These codes indicate transient conditions, so the same request may succeed later: `marketplace_host_rate_limited`, `marketplace_host_server_error`, `marketplace_host_timeout`, `marketplace_host_unreachable`, `marketplace_repo_access_denied`, `marketplace_sync_transient_fetch_budget_exhausted`, `marketplace_validate_network_error`, and usually `marketplace_validate_deadline_exceeded`.
3130Each finding in a report has a stable code: `manifest_error_code` when nothing could be validated, `error_code` on each `plugin_errors` entry, and `error_code` on each warning. When a plugin has several problems, `error_code` is the first one's and `error` joins their messages. New codes may be added; an unrecognized `manifest_error_code` still means the content could not be validated, one on a `plugin_errors` entry still means the plugin would be skipped, and one on a warning still means the plugin would synchronize. These codes indicate transient conditions, so the same request might succeed later: `marketplace_host_rate_limited`, `marketplace_host_server_error`, `marketplace_host_timeout`, `marketplace_host_unreachable`, `marketplace_repo_access_denied`, `marketplace_sync_transient_fetch_budget_exhausted`, `marketplace_validate_network_error`, and usually `marketplace_validate_deadline_exceeded`.
31313131
31323132## Unrecognized values
31333133
from line 3135
31353135
31363136## Rate limiting
31373137
3138Read requests (every `GET` endpoint on this page) share a limit of **300 requests per minute** per organization, and write requests (creating a plugin or version, changing the served version, deleting, setting or removing an installation setting, and updating a marketplace) share a limit of **60 requests per minute** per organization. A marketplace validation (either endpoint) counts as a read, and validations are additionally limited to 10 a minute per organization across both endpoints; both limits are checked before the request body is read. These limits are counted across all of your organization's keys and are separate from your organization's other Admin API limits. Requests over a limit return **429 Too Many Requests** with a `retry-after` header. Responses include `anthropic-ratelimit-requests-*` headers for the limit that applies (on marketplace validation, its 10-per-minute limit; on a `429`, whichever limit refused the request).
3138Read requests (every `GET` endpoint on this page) share a limit of **300 requests per minute** for each organization, and write requests (creating a plugin or version, changing the served version, deleting, setting or removing an installation setting, and updating a marketplace) share a limit of **60 requests per minute** for each organization. A marketplace validation (either endpoint) counts as a read, and validations are additionally limited to 10 a minute for each organization across both endpoints; both limits are checked before the request body is read. These limits are counted across all of your organization's keys and are separate from your organization's other Admin API limits. Requests over a limit return **429 Too Many Requests** with a `retry-after` header. Responses include `anthropic-ratelimit-requests-*` headers for the limit that applies (on marketplace validation, its 10-per-minute limit; on a `429`, whichever limit refused the request).
31393139
31403140An upload, a served-version change, or a validation can also return `429` with `retry-after` when the service briefly has no capacity for another one, and an upload returns `429` when your organization has exceeded its content-scan rate. Handle all of these the same way: wait for `retry-after`, then retry. Separately from these limits, send installation-setting writes for the same plugin one at a time: when several arrive at the same time, some can be answered with `503` and `x-should-retry: true`, and those are safe to send again after a second or two (see [Set an installation setting](https://platform.claude.com/docs/en/manage-claude/plugins-api#set-an-installation-setting)).
31413141
from line 3147
31473147
31483148## Error responses
31493149
3150Error responses follow the standard shape documented in [Errors](https://platform.claude.com/docs/en/api/errors). Quote the `request_id` from the response body when contacting support.
3150Error responses follow the standard shape documented in [Claude API errors](https://platform.claude.com/docs/en/api/errors). Quote the `request_id` from the response body when contacting support.
31513151
31523152| Status | Meaning |
31533153| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
from line 3216
32163216| `marketplace_updated` | A marketplace's default installation setting changes, or an administrator or owner starts a synchronization in claude.ai. |
32173217| `marketplace_deleted` | A marketplace is deleted in claude.ai together with its plugins (no per-plugin events). |
32183218| `claude_plugin_archive_accessed` | A member-owned plugin's archive is downloaded. |
3219| `claude_plugin_security_scan_completed` | A content scan finishes. |
3219| `claude_plugin_security_scan_completed` | A content scan completes. |
32203220
32213221The plugin, version, and marketplace IDs in these events are the same IDs this API returns. `plugin_installation_preference_updated` identifies the plugin by its `name` and `marketplace_id` rather than its `id`.
32223222
manage-claude/rate-limits-api Changed · +3 / -3 lines
from line 153
153153
154154* **Rate limit groups:** Each entry in the response represents one rate limit group. Model rate limits are grouped so that several model versions share a single set of limits, and other groups cover resources such as the Message Batches API, the Files API, the Token Counting API, agent skills, and the web search tool.
155155* **`group` object:** Present on every entry, it identifies the rate limit group the entry applies to. It always has `type`, which is one of the `group_type` values, and `id`, an opaque identifier with the `rlg_` prefix. On `model_group` entries it also has `display_name`, Anthropic's current label for the group, such as `Claude Sonnet 4.x`. The label is for display only and may change. Other group types have no `display_name`.
156* **The `id` inside `group`:** A group has the same `id` in every organization and on every workspace override, and it never changes. Use it to match entries across organizations or against your own catalog. The entry's own `id` differs per organization, and `models` changes when Anthropic moves a model between groups. Neither is a stable key for the group.
156* **The `id` inside `group`:** A group has the same `id` in every organization and on every workspace override, and it never changes. Use it to match entries across organizations or against your own catalog. The entry's own `id` differs for each organization, and `models` changes when Anthropic moves a model between groups. Neither is a stable key for the group.
157157* **`group_type`:** The query parameter that restricts the list to one group type. See [Filtering by group type](https://platform.claude.com/docs/en/manage-claude/rate-limits-api#filtering-by-group-type) for the values.
158158* **`models` list:** For `model_group` entries, the `models` field lists every model ID and alias that counts against that group's limits. Use this list to look up which group any model string falls under. For other group types, `models` is `null`.
159159* **`limits` list:** Each group carries a list of `{type, value}` pairs. The `type` field identifies the limiter (such as `requests_per_minute`, `input_tokens_per_minute`, or `output_tokens_per_minute`) and `value` is the configured limit. See [Rate limits](https://platform.claude.com/docs/en/api/rate-limits) for how each limiter is measured and enforced.
from line 493
493493For complete parameter details and response schemas, see the [Workspace Rate Limits API reference](https://platform.claude.com/docs/en/api/organization/workspaces/rate_limits/list).
494494
495495<Tip>
496 To retrieve your organization's workspace IDs, use the [List Workspaces](https://platform.claude.com/docs/en/api/organization/workspaces/list) endpoint, or find them in the [Claude Console](https://platform.claude.com/settings/workspaces). List Workspaces includes the default workspace only when you pass `include_default=true`.
496 To retrieve your organization's workspace IDs, use the [List Workspaces](https://platform.claude.com/docs/en/api/organization/workspaces/list) endpoint, or find them on the [Workspaces](https://platform.claude.com/settings/workspaces) page in the Claude Console. List Workspaces includes the default workspace only when you pass `include_default=true`.
497497</Tip>
498498
499499<CodeGroup>
from line 1101
11011101
11021102Every model ID and alias that counts against the group, including dated IDs (such as `claude-sonnet-4-5-20250929`) and undated aliases (such as `claude-sonnet-4-5`). Look up any model string you pass to the Messages API and you'll find it in exactly one `model_group` entry.
11031103
1104The list covers the models your organization has access to, so it can differ between organizations.
1104The list covers the models your organization has access to, so it can differ among organizations.
11051105
11061106### What does it mean if a group is missing from the workspace response?
11071107
manage-claude/spend-limits-api Changed · +17 / -17 lines
from line 186
186186
187187### Amounts and currency
188188
189All monetary values are strings in **minor units of the organization's billing currency** (cents, for USD). For example, `"50000"` represents 500.00 USD. Parse as a decimal and divide by 100 to display dollars; avoid binary floating-point for large values.
189All monetary values are strings in **minor units of the organization's billing currency** (cents, for USD). For example, `"50000"` represents $500.00 USD. Parse as a decimal and divide by 100 to display dollars; avoid binary floating-point for large values.
190190
191191`amount` is **nullable**. In a member's effective row, `null` means **unlimited** (no spend limit) and `"0"` means the member cannot use Claude beyond their plan's included usage. On a configured spend-limit row (as returned by `GET /v1/organizations/spend_limits/{id}`), `null` only means no numeric spend limit is set; read the member's effective row to distinguish unlimited from included-usage-only.
192192
193`period_to_date_spend` is the member's spend accrued since the start of the current `period`, in the same minor-unit format; it may include a fractional part (for example, `"41280.125"`). It may read as `"0"` if the spend reading is temporarily unavailable; treat it as informational, not transactional.
193`period_to_date_spend` is the member's spend accrued since the start of the current `period`, in the same minor-unit format; it might include a fractional part (for example, `"41280.125"`). It might read as `"0"` if the spend reading is temporarily unavailable; treat it as informational, not transactional.
194194
195195### Increase request lifecycle
196196
197197A **spend limit increase request** is created when a member clicks **Request more usage** in claude.ai. Requests are not created through this API. A request's `status` is one of:
198198
199| Status | Meaning |
200| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
201| `pending` | Awaiting admin action. The request normally carries a live `spend_summary` so you can see the member's current effective spend limit and period-to-date spend while deciding; `spend_summary` may be `null` if it could not be computed. |
202| `approved` | The request was resolved with approval: either an admin approved it explicitly, another admin action raised the member's spend limit, or Anthropic support raised a spend limit on the organization's behalf. `spend_summary` is `null`. |
203| `denied` | An admin declined. `spend_summary` is `null`. The member can send a new request right away; only a `pending` request blocks a new one. An admin can still raise the member's spend limit directly at any time. |
199| Status | Meaning |
200| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
201| `pending` | Awaiting admin action. The request normally carries a live `spend_summary` so you can see the member's current effective spend limit and period-to-date spend while deciding; `spend_summary` might be `null` if it could not be computed. |
202| `approved` | The request was resolved with approval: either an admin approved it explicitly, another admin action raised the member's spend limit, or Anthropic support raised a spend limit on the organization's behalf. `spend_summary` is `null`. |
203| `denied` | An admin declined. `spend_summary` is `null`. The member can send a new request right away; only a `pending` request blocks a new one. An admin can still raise the member's spend limit directly at any time. |
204204
205205Both `approved` and `denied` are terminal. A member has at most one `pending` request at a time.
206206
from line 232
232232
233233## Error responses
234234
235Error responses follow the standard shape documented in [Errors](https://platform.claude.com/docs/en/api/errors). Quote the `request_id` from the response body when contacting support.
235Error responses follow the standard shape documented in [Claude API errors](https://platform.claude.com/docs/en/api/errors). Quote the `request_id` from the response body when contacting support.
236236
237237## Spend limits
238238
from line 1434
14341434
14351435Find members approaching their cap so you can raise it before they're blocked.
14361436
14371. Pull each member's month-to-date spend from the Analytics API (one row per member, highest spend first by default):
14371. Pull each member's month-to-date spend from the Analytics API (one row for each member, highest spend first by default):
14381438
14391439 <CodeGroup>
14401440 ```bash cURL
from line 1706
17061706
170717073. For each member with a positive cap, compute `period_to_date_spend / amount` and flag those at or above your threshold (for example, 80 percent). Treat a `"0"` cap as already at-limit. There is no server-side filter for this ratio.
17081708
17094. Act on flagged members: raise the cap with `POST /v1/organizations/spend_limits`, approve a pending increase request if one exists, or reach out to the member.
17094. Act on flagged members: raise the cap with `POST /v1/organizations/spend_limits`, approve a pending increase request if one exists, or contact the member.
17101710
17111711### Find members with rapidly changing usage
17121712
17131713Surface members whose spend has jumped week over week.
17141714
17151. Pull per-member daily cost for the trailing two weeks from the Analytics API:
17151. Pull per-member daily cost for the trailing 2 weeks from the Analytics API:
17161716
17171717 <CodeGroup>
17181718 ```bash cURL
from line 1851
18511851 ```
18521852 </CodeGroup>
18531853
1854 With `bucket_width` set, each member spans one row per day with usage. Page through `next_page` to collect every member's full series.
1854 With `bucket_width` set, each member spans one row for each day with usage. Page through `next_page` to collect every member's full series.
18551855
18562. Group rows by `actor.user_id`. For each member, sum the most recent seven days and the prior seven days. Flag members whose recent week exceeds the prior week by your chosen multiple (for example, three). Recent-day cost is provisional and can be revised upward; for repeatable comparisons, set `ending_at` at or before a previously returned `data_refreshed_at` (see [Data availability and freshness](https://platform.claude.com/docs/en/manage-claude/analytics-api#data-availability-and-freshness)).
18562. Group rows by `actor.user_id`. For each member, sum the most recent 7 days and the prior 7 days. Flag members whose recent week exceeds the prior week by your chosen multiple (for example, three). Recent-day cost is provisional and can be revised upward; for repeatable comparisons, set `ending_at` at or before a previously returned `data_refreshed_at` (see [Data availability and freshness](https://platform.claude.com/docs/en/manage-claude/analytics-api#data-availability-and-freshness)).
18571857
18583. Act on flagged members: adjust the cap with `POST /v1/organizations/spend_limits`, or reach out.
18583. Act on flagged members: adjust the cap with `POST /v1/organizations/spend_limits`, or contact the member.
18591859
18601860### Temporarily raise a member's spend limit during an incident
18611861
1862Give an incident responder room to work while an incident is open: raise their spend cap when the incident starts, and roll it back after the incident closes. Gate the raise on your incident management system, for example by requiring a live incident ID with the member assigned to it.
1862Give an incident responder room to work while an incident is open: raise their spend cap when the incident starts, and roll it back after the incident closes. Gate the raise on your incident management system, for example, by requiring a live incident ID with the member assigned to it.
18631863
186418641. Read the member's current cap, and record it for the rollback:
18651865
manage-claude/usage-cost-api Changed · +6 / -6 lines
from line 10
1010
1111The Usage and Cost Admin API provides programmatic and granular access to historical API usage and cost data for your organization. This data is similar to the information available on the [Usage](https://platform.claude.com/usage) page (`platform.claude.com/usage`) and the [Cost](https://platform.claude.com/cost) page (`platform.claude.com/cost`) in the Claude Console.
1212
13This API enables you to better monitor, analyze, and optimize your Claude implementations:
13This API allows you to better monitor, analyze, and optimize your Claude implementations:
1414
1515* **Accurate usage tracking:** Get precise token counts and usage patterns instead of relying solely on response token counting
1616* **Cost reconciliation:** Match internal records with Anthropic billing for finance and accounting teams
1717* **Product performance and improvement:** Monitor product performance while measuring if changes to the system have improved it, or set up alerting
18* **[Rate limit](https://platform.claude.com/docs/en/api/rate-limits) optimization:** Optimize features like [prompt caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching) or specific prompts to make the most of your allocated capacity.
18* **[Rate limit](https://platform.claude.com/docs/en/api/rate-limits) optimization:** Optimize features such as [prompt caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching) or specific prompts to use your allocated capacity efficiently.
1919* **Advanced analysis:** Perform deeper data analysis than what's available in Console
2020
2121<Check>
from line 286
286286
287287## Common use cases
288288
289Explore detailed implementations in [Claude Cookbook](https://platform.claude.com/cookbook):
289Explore detailed implementations in the [Claude Cookbook](https://platform.claude.com/cookbook):
290290
291291* **Daily usage reports:** Track token consumption trends
292292* **Cost attribution:** Allocate expenses by workspace for chargebacks
from line 298
298298
299299### How fresh is the data?
300300
301Usage and cost data typically appears within 5 minutes of API request completion, though delays may occasionally be longer.
301Usage and cost data typically appears within 5 minutes of API request completion, though delays might occasionally be longer.
302302
303303### What's the recommended polling frequency?
304304
from line 314
314314
315315### What happens with playground usage?
316316
317API usage from playground in the Claude Console (and from the legacy Workbench before it) is not associated with an API key, so `api_key_id` will be `null` even when grouping by that dimension.
317API usage from playground in the Claude Console (and from the legacy Anthropic Workbench before it) is not associated with an API key, so `api_key_id` is `null` even when grouping by that dimension.
318318
319319### How is the default workspace represented?
320320
from line 333
333333* [Analytics APIs](https://platform.claude.com/docs/en/manage-claude/analytics-api) - Which analytics API and key type your organization needs
334334* [Pricing](https://platform.claude.com/docs/en/about-claude/pricing)
335335* [Prompt caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching) - Optimize costs with caching
336* [Batch processing](https://platform.claude.com/docs/en/build-with-claude/batch-processing) - 50% discount on batch requests
336* [Batch processing](https://platform.claude.com/docs/en/build-with-claude/batch-processing) - 50 percent discount on batch requests
337337* [Rate limits](https://platform.claude.com/docs/en/api/rate-limits) - Understand usage tiers
338338* [Rate Limits API](https://platform.claude.com/docs/en/manage-claude/rate-limits-api) - Read your configured rate limits
339339* [Data residency](https://platform.claude.com/docs/en/manage-claude/data-residency) - Control inference geography
manage-claude/user-management Changed · +4 / -4 lines
from line 12
1212
1313## Which endpoints can your organization use?
1414
15The Admin API is a single set of endpoints under `https://api.anthropic.com/v1/organizations/`. Claude Console and Claude Enterprise organizations authenticate with [different keys](https://platform.claude.com/docs/en/manage-claude/admin-api-keys) and each have access to a different subset of the endpoints:
15The Admin API is a single set of endpoints under `https://api.anthropic.com/v1/organizations/`. Claude Console and Claude Enterprise organizations authenticate with different [Admin API keys](https://platform.claude.com/docs/en/manage-claude/admin-api-keys) and each have access to a different subset of the endpoints:
1616
1717| Endpoints | Claude Console (Claude Platform) | Claude Enterprise (claude.ai) |
1818| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
from line 189
189189
190190### Groups and roles
191191
192Groups connect members to custom roles (role-based access control, the `rbac` in the endpoint paths and scope names). Groups are owned by your enterprise as a whole (the parent organization together with every organization under it) rather than by a single organization, so the group scopes (`read:rbac_groups` and `write:rbac_groups`) require a key created for all linked organizations. Each group carries a `source_type`: `direct` for groups created in claude.ai, `scim` for groups provisioned by your identity provider. A group's `role_ids` field lists the IDs of the custom roles attached to it; resolve them to names and permissions with the [custom role endpoints](https://platform.claude.com/docs/en/manage-claude/user-management#custom-roles), noting that the role catalog is per-organization while groups are enterprise-wide, so fetching a role that belongs to a different organization of your enterprise returns 404 for your key. The field is `null` (rather than `[]`) when role data was temporarily unavailable, so retry to distinguish a degraded read from a group with no roles.
192Groups connect members to custom roles (role-based access control, the `rbac` in the endpoint paths and scope names). Groups are owned by your enterprise as a whole (the parent organization together with every organization under it) rather than by a single organization, so the group scopes (`read:rbac_groups` and `write:rbac_groups`) require a key created for all linked organizations. Each group carries a `source_type`: `direct` for groups created in claude.ai, `scim` for groups provisioned by your identity provider. A group's `role_ids` field lists the IDs of the custom roles attached to it; resolve them to names and permissions with the [custom role endpoints](https://platform.claude.com/docs/en/manage-claude/user-management#custom-roles), noting that the role catalog is per-organization whereas groups are enterprise-wide, so fetching a role that belongs to a different organization of your enterprise returns 404 for your key. The field is `null` (rather than `[]`) when role data was temporarily unavailable, so retry to distinguish a degraded read from a group with no roles.
193193
194194## Versioning
195195
from line 201
201201
202202## Pagination
203203
204Member and invite lists use ID-based pagination: pass `limit` (default 20, max 1000) plus at most one of `before_id` or `after_id`, and page using the `first_id` and `last_id` fields of each response until `has_more` is `false`. Group and custom-role lists use an **opaque cursor** instead: the response's `next_page` value is passed unchanged as the `page` parameter on the next request, until `next_page` is `null`.
204Member and invite lists use ID-based pagination: pass `limit` (default 20, max 1000) plus at most one of `before_id` or `after_id`, and page using the `first_id` and `last_id` fields of each response until `has_more` is `false`. Group and custom-role lists use an **opaque cursor** instead: pass the response's `next_page` value unchanged as the `page` parameter on the next request, until `next_page` is `null`.
205205
206206When you iterate over the results, the SDK fetches more pages for you, so `limit` sets the page size, not the total. In the CLI, `--limit` sets the total number of results for member and invite lists. For group and role lists, it sets the page size.
207207
208208## Error responses
209209
210Error responses follow the standard shape documented in [Errors](https://platform.claude.com/docs/en/api/errors).
210Error responses follow the standard shape documented in [Claude API errors](https://platform.claude.com/docs/en/api/errors).
211211
212212## Members
213213
manage-claude/user-profiles Changed · +3 / -3 lines
## Before you start ## Before you begin
from line 29
2929
3030Anthropic processes profiles in accordance with the [Privacy Policy](https://www.anthropic.com/legal/privacy).
3131
32## Before you begin
32## Before you start
3333
3434You create and manage profiles in one of the following:
3535
from line 421
421421}
422422```
423423
424`enforcement_status` sums up the open actions in one value:
424`enforcement_status` summarizes the open actions in one value:
425425
426426* `unrestricted`: No action is open.
427427* `restricted`: At least one action is open and none of them is a ban.
from line 458
458458* Each field you send in `external_user_details` replaces the stored value. You can't clear a value. The API rejects `null` with a `400`.
459459* `metadata` merges key by key. A key you send overwrites the stored value, an empty string deletes the key, and keys you leave out stay as they are.
460460
461The following example marks the account as suspended and removes a `trial_cohort` metadata key.
461The following example marks the account as suspended and deletes a `trial_cohort` metadata key.
462462
463463<CodeGroup>
464464 ```bash cURL
manage-claude/wif-admin-api Changed · +4 / -4 lines
from line 21
2121
2222Interactive tokens are short-lived; if requests start returning 401, re-run the export command (it refreshes the token automatically).
2323
24The SDKs and the `ant` CLI read `ANTHROPIC_AUTH_TOKEN` automatically; leave `ANTHROPIC_API_KEY` unset in the same shell, because these endpoints reject API keys and every client uses the key when both are set.
24The SDK and the `ant` CLI read `ANTHROPIC_AUTH_TOKEN` automatically; leave `ANTHROPIC_API_KEY` unset in the same shell, because these endpoints reject API keys and every client uses the key when both are set.
2525
2626### Workload (CI and automation)
2727
from line 41
4141 </Step>
4242
4343 <Step title="Exchange the workload's identity token">
44 A workload that uses one of the SDKs or the `ant` CLI does not perform the exchange itself. Point the client at the rule with the federation environment variables and construct it with no arguments, exactly as for inference in [Construct the SDK client](https://platform.claude.com/docs/en/manage-claude/workload-identity-federation#construct-the-sdk-client); the client exchanges the identity token on the first request and, before the resulting access token expires, re-reads the identity token and exchanges it again:
44 A workload that uses the SDK or the `ant` CLI does not perform the exchange itself. Point the client at the rule with the federation environment variables and construct it with no arguments, exactly as for inference in [Construct the SDK client](https://platform.claude.com/docs/en/manage-claude/workload-identity-federation#construct-the-sdk-client); the client exchanges the identity token on the first request and, before the resulting access token expires, re-reads the identity token and exchanges it again:
4545
4646 ```bash
4747 export ANTHROPIC_FEDERATION_RULE_ID=fdrl_... # the org:admin rule from step 1
from line 53
5353 unset ANTHROPIC_API_KEY ANTHROPIC_AUTH_TOKEN # both take precedence over federation
5454 ```
5555
56 The `ant` CLI reads the same variables, or takes `--federation-rule`, `--organization-id`, `--service-account-id`, and `--identity-token-file` flags. For a workload that runs more than one `ant` command, use a [federation profile](https://platform.claude.com/docs/en/manage-claude/wif-reference#profile-configuration-file) rather than flags or environment variables: with flags or variables the CLI exchanges the identity token again in every process, and identity tokens that carry a `jti` claim (GitHub Actions tokens do) are accepted only once, so a second command would be rejected; a profile is also the only way to give the CLI a `workspace_id` for the exchange when the rule is enabled for all workspaces or more than one, because unlike the SDKs the CLI does not pass `ANTHROPIC_WORKSPACE_ID` or `--workspace-id` into the exchange. Every SDK also accepts the same settings as explicit constructor arguments, shown per language in [Construct the SDK client](https://platform.claude.com/docs/en/manage-claude/workload-identity-federation#construct-the-sdk-client). See [Environment variables](https://platform.claude.com/docs/en/manage-claude/wif-reference#environment-variables) and [Credential precedence](https://platform.claude.com/docs/en/manage-claude/wif-reference#credential-precedence) for the full list and ordering.
56 The `ant` CLI reads the same variables, or takes `--federation-rule`, `--organization-id`, `--service-account-id`, and `--identity-token-file` flags. For a workload that runs more than one `ant` command, use a [federation profile](https://platform.claude.com/docs/en/manage-claude/wif-reference#profile-configuration-file) rather than flags or environment variables: with flags or variables the CLI exchanges the identity token again in every process, and identity tokens that carry a `jti` claim (GitHub Actions tokens do) are accepted only once, so a second command would be rejected; a profile is also the only way to give the CLI a `workspace_id` for the exchange when the rule is enabled for all workspaces or more than one, because unlike the SDK the CLI does not pass `ANTHROPIC_WORKSPACE_ID` or `--workspace-id` into the exchange. Every SDK also accepts the same settings as explicit constructor arguments, shown for each language in [Construct the SDK client](https://platform.claude.com/docs/en/manage-claude/workload-identity-federation#construct-the-sdk-client). See [Environment variables](https://platform.claude.com/docs/en/manage-claude/wif-reference#environment-variables) and [Credential precedence](https://platform.claude.com/docs/en/manage-claude/wif-reference#credential-precedence) for the full list and ordering.
5757
5858 A workload that calls the API with curl exchanges the JWT for a short-lived `org:admin` bearer token itself, using the same [token exchange](https://platform.claude.com/docs/en/manage-claude/workload-identity-federation#authenticate-from-your-workload) as any other federated workload, and sends it in the `authorization: Bearer` header.
5959 </Step>
from line 69
6969
7070All endpoints live under `https://api.anthropic.com/v1/organizations/`. Every request to the federation and service-account endpoints needs the API version header and the bearer token:
7171
72In the SDKs these endpoints are `client.organization.service_accounts` (typescript: `client.organization.serviceAccounts`; csharp, go: `client.Organization.ServiceAccounts`; java: `client.organization().serviceAccounts()`; php: `$client->organization->serviceAccounts`), `client.organization.federation.issuers` (csharp, go: `client.Organization.Federation.Issuers`; java: `client.organization().federation().issuers()`; php: `$client->organization->federation->issuers`), and `client.organization.federation.rules` (csharp, go: `client.Organization.Federation.Rules`; java: `client.organization().federation().rules()`; php: `$client->organization->federation->rules`) (`ant organization:service-accounts`, `federation:issuers`, and `federation:rules` in the CLI). The SDK and CLI examples construct the default client, which sends the bearer token from `ANTHROPIC_AUTH_TOKEN`, or, in an automated workload, performs the federation exchange itself as described in [Bootstrap a workload to manage WIF](https://platform.claude.com/docs/en/manage-claude/wif-admin-api#bootstrap-a-workload-to-manage-wif). SDK list methods fetch further pages on demand, so `limit` sets the page size; the PHP and Ruby examples read one page.
72In the SDK, these endpoints are `client.organization.service_accounts` (typescript: `client.organization.serviceAccounts`; csharp, go: `client.Organization.ServiceAccounts`; java: `client.organization().serviceAccounts()`; php: `$client->organization->serviceAccounts`), `client.organization.federation.issuers` (csharp, go: `client.Organization.Federation.Issuers`; java: `client.organization().federation().issuers()`; php: `$client->organization->federation->issuers`), and `client.organization.federation.rules` (csharp, go: `client.Organization.Federation.Rules`; java: `client.organization().federation().rules()`; php: `$client->organization->federation->rules`) (`ant organization:service-accounts`, `federation:issuers`, and `federation:rules` in the CLI). The SDK and CLI examples construct the default client, which sends the bearer token from `ANTHROPIC_AUTH_TOKEN`, or, in an automated workload, performs the federation exchange itself as described in [Bootstrap a workload to manage WIF](https://platform.claude.com/docs/en/manage-claude/wif-admin-api#bootstrap-a-workload-to-manage-wif). SDK list methods fetch further pages on demand, so `limit` sets the page size; the PHP and Ruby examples read one page.
7373
7474<CodeGroup>
7575 ```bash cURL
manage-claude/wif-providers/aws Changed · +11 / -11 lines
from line 23
2323
2424<Steps>
2525 <Step title="Enable outbound web identity federation for the account">
26 This is an account-level flag, off by default. In the AWS console, open **IAM**, choose **Account settings**, and enable **Outbound web identity federation**. To enable it programmatically:
26 This is an account-level flag, off by default. In the AWS console, open **IAM**, select **Account settings**, and enable **Outbound web identity federation**. To enable it programmatically:
2727
2828 ```bash
2929 python3 -c "import boto3; boto3.client('iam').enable_outbound_web_identity_federation()"
from line 50
5050 </Step>
5151
5252 <Step title="Find your account's STS issuer URL">
53 After enabling outbound federation, the **IAM > Account settings** page shows a **Get Token Issuer URL** field with a value of the form `https://<uuid>.tokens.sts.global.api.aws`. This URL is unique to your AWS account; copy it for the next step. To retrieve it programmatically:
53 After you enable outbound federation, the **IAM > Account settings** page shows a **Get Token Issuer URL** field with a value of the form `https://<uuid>.tokens.sts.global.api.aws`. This URL is unique to your AWS account; copy it for the next step. To retrieve it programmatically:
5454
5555 ```bash
5656 python3 -c "import boto3; print(boto3.client('iam').get_outbound_web_identity_federation_info())"
from line 95
9595
9696### Acquire and use the token
9797
98Call `GetWebIdentityToken` with `https://api.anthropic.com` as the audience, then pass the result to the SDK's federation credentials through `identity_token_provider` (typescript: `identityTokenProvider`; php: `identityProvider`; csharp: `IdentityTokenProvider`; go: `option.WithFederationTokenProvider`; java: `IdentityTokenConfig`). Except in Java (see the comment in that example), that option takes a token provider, so the SDK re-invokes STS on each refresh.
98Call `GetWebIdentityToken` with `https://api.anthropic.com` as the audience, then pass the result to the SDK's federation credentials through `identity_token_provider` (typescript: `identityTokenProvider`; php: `identityProvider`; csharp: `IdentityTokenProvider`; go: `option.WithFederationTokenProvider`; java: `IdentityTokenConfig`). Except in Java (see the comment in that example), that option takes a token provider, so the SDK calls STS again on each refresh.
9999
100100<Note>
101101 `GetWebIdentityToken` is available only on regional STS endpoints. If you receive `'STS' object has no attribute 'get_web_identity_token'` or a similar error, pin your STS client to a region (for example, `boto3.client("sts", region_name="us-east-1")`) and ensure your AWS SDK is recent enough to include the API.
from line 112
112112
113113 ACCESS_TOKEN=$(curl -sS https://api.anthropic.com/v1/oauth/token \
114114 -H "content-type: application/json" \
115 --data @- <<JSON | jq -r .access_token
115 -d @- <<JSON | jq -r .access_token
116116 {
117117 "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
118118 "assertion": "$JWT",
from line 371
371371 --duration-seconds 900 \
372372 --query WebIdentityToken --output text > "$ANTHROPIC_IDENTITY_TOKEN_FILE"
373373
374 # ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID, and
374 # ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
375375 # ANTHROPIC_SERVICE_ACCOUNT_ID, and ANTHROPIC_WORKSPACE_ID are read from the environment
376376 ant messages create \
377377 --model claude-opus-5-5 \
from line 466
466466
467467curl -sS https://api.anthropic.com/v1/oauth/token \
468468 -H "content-type: application/json" \
469 --data @- <<JSON
469 -d @- <<JSON
470470{
471471 "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
472472 "assertion": "$JWT",
from line 482
482482
483483## Use EKS projected service-account tokens
484484
485If your workload runs in an EKS pod, you can skip the STS call and read a Kubernetes-projected service-account token directly from disk. Kubernetes natively projects an OIDC-compatible token into the pod, and the SDK can read it from a file path, so no token-provider callable is required. This path has two fewer AWS configuration steps than the STS path but only works inside a pod; the underlying mechanism is the same as the [generic Kubernetes integration](https://platform.claude.com/docs/en/manage-claude/wif-providers/kubernetes).
485If your workload runs in an EKS pod, you can skip the STS call and read a Kubernetes-projected service-account token directly from disk. Kubernetes natively projects an OIDC-compatible token into the pod, and the SDK can read it from a file path. This path has two fewer AWS configuration steps than the STS path but only works inside a pod; the underlying mechanism is the same as the [generic Kubernetes integration](https://platform.claude.com/docs/en/manage-claude/wif-providers/kubernetes).
486486
487487This path additionally requires an EKS cluster with an [IAM OIDC provider enabled](https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html) and `kubectl` access to the cluster.
488488
from line 579
579579
580580The wizard creates these resources for you. Use the following values whether you enter them in the wizard or send them to the [Admin API](https://platform.claude.com/docs/en/manage-claude/wif-admin-api):
581581
582**Federation issuer:** EKS issuers expose a public JWKS endpoint, so use discovery mode. The issuer URL must exactly match the token's `iss` claim. Register one issuer per cluster.
582**Federation issuer:** EKS issuers expose a public JWKS endpoint, so use discovery mode. The issuer URL must exactly match the token's `iss` claim. Register one issuer for each cluster.
583583
584584```json
585585{
from line 618
618618
619619 ACCESS_TOKEN=$(curl -sS https://api.anthropic.com/v1/oauth/token \
620620 -H "content-type: application/json" \
621 --data @- <<JSON | jq -r .access_token
621 -d @- <<JSON | jq -r .access_token
622622 {
623623 "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
624624 "assertion": "$JWT",
from line 821
821821
822822curl -sS https://api.anthropic.com/v1/oauth/token \
823823 -H "content-type: application/json" \
824 --data @- <<JSON
824 -d @- <<JSON
825825{
826826 "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
827827 "assertion": "$JWT",
from line 846
846846* **Pin the full role ARN:** Use `subject_prefix: "arn:aws:iam::<account>:role/<role-name>"` with no trailing `*` so other roles in the account do not match.
847847* **Pin the account ID:** Match the `aws_account` field of the token's `https://sts.amazonaws.com/` claim with the `claims` map or a CEL `condition` as a defense-in-depth check against a misconfigured prefix.
848848* **Pin namespace and service account on EKS:** Use the exact `system:serviceaccount:<namespace>:<name>` value with no `*` after the `system:serviceaccount:` prefix.
849* **Use a separate rule per environment:** Create distinct rules for production, staging, and development workloads rather than widening one prefix to cover them all.
849* **Use a separate rule for each environment:** Create distinct rules for production, staging, and development workloads rather than widening one prefix to cover them all.
850850
851851## Next steps
852852
manage-claude/wif-providers/azure Changed · +53 / -53 lines
from line 7
77Azure workloads authenticate to the Claude API by presenting a JSON Web Token (JWT) issued by Microsoft Entra ID, then exchanging it for a short-lived Anthropic access token. The setup follows the same shape on every Azure platform:
88
991. **Register the token audience:** Create one app registration in your Microsoft Entra tenant to represent the Claude API audience. Every workload in the tenant requests Entra tokens for it.
102. **Set up the identity for your platform:** A managed identity on VMs, VM Scale Sets, App Service, Functions, and Container Apps, or Entra Workload Identity on AKS.
102. **Set up the identity for your platform:** Use a managed identity on VMs, VM Scale Sets, App Service, Functions, and Container Apps, or Entra Workload Identity on AKS.
11113. **Configure Anthropic:** Register your tenant's Entra issuer, create a service account, and write a federation rule that matches the token's claims.
12124. **Exchange at runtime:** Your workload exchanges its Entra-issued token at `POST /v1/oauth/token` for an `sk-ant-oat01-...` Anthropic access token and calls Claude with it.
1313
14On both paths the token you present to Anthropic carries your tenant-specific Entra issuer and the managed identity's object ID in the `sub` and `oid` claims; only how the workload obtains that token differs. Pick the section for where your workload runs: [Use a managed identity](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#use-a-managed-identity) for VMs, VM Scale Sets, App Service, Functions, or Container Apps; [Use Entra Workload Identity on AKS](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#use-entra-workload-identity-on-aks) for AKS.
14On both paths the token you present to Anthropic carries your tenant-specific Entra issuer and the managed identity's object ID in the `sub` and `oid` claims; only how the workload obtains that token differs. Pick the section for where your workload runs: [Use a managed identity](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#use-a-managed-identity) for VMs, VM Scale Sets, App Service, Functions, and Container Apps, or [Use Entra Workload Identity on AKS](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#use-entra-workload-identity-on-aks) for AKS.
1515
1616## Prerequisites
1717
from line 97
9797
9898The wizard creates these resources for you. Use the following values whether you enter them in the wizard or send them to the [Admin API](https://platform.claude.com/docs/en/manage-claude/wif-admin-api):
9999
100**Federation issuer:** Choose **v2.0 (login.microsoftonline.com)** in the wizard's **Token issuer** selector. (The selector defaults to v1; that default exists for tenants reusing older registrations that still emit v1.0 tokens.) Entra publishes an OIDC discovery document at the per-tenant issuer URL, so use discovery mode. Each Microsoft Entra tenant you federate needs its own issuer record.
100**Federation issuer:** Select **v2.0 (login.microsoftonline.com)** in the wizard's **Token issuer** selector. (The selector defaults to v1; that default exists for tenants reusing older registrations that still emit v1.0 tokens.) Entra publishes an OIDC discovery document at the per-tenant issuer URL, so use discovery mode. Each Microsoft Entra tenant you federate needs its own issuer record.
101101
102102```json
103103{
from line 109
109109```
110110
111111<Warning>
112 Managed identity workloads need `max_jwt_lifetime_seconds: 86400`. Azure issues managed identity tokens with up to 24 hours between `iat` and `exp` because it caches each resource's token for that window and offers no way to force an early refresh, and the issuer's 1-hour default rejects those tokens, so the exchange fails with the opaque `401` `authentication_error` response (message `Authentication failed`). The Connect workload wizard's Microsoft Entra tile creates the issuer with `max_jwt_lifetime_seconds` set to `7500` and provides no field to change it during creation, so finish the wizard, then open the **Issuers** tab on the [Workload identity](https://platform.claude.com/settings/workload-identity-federation) page in the Claude Console, edit the issuer, and raise the value to `86400`. You can also update the issuer through the Admin API.
112 Managed identity workloads need `max_jwt_lifetime_seconds: 86400`. Azure issues managed identity tokens with up to 24 hours between `iat` and `exp` because it caches each resource's token for that window and offers no way to force an early refresh, and the issuer's 1-hour default rejects those tokens, so the exchange fails with the opaque `401` `authentication_error` response (message `Authentication failed`). The **Connect workload** wizard's **Microsoft Entra** tile creates the issuer with `max_jwt_lifetime_seconds` set to `7500` and provides no field to change it during creation, so complete the wizard, then open the **Issuers** tab on the [Workload identity](https://platform.claude.com/settings/workload-identity-federation) page in the Claude Console, edit the issuer, and raise the value to `86400`. You can also update the issuer through the Admin API.
113113</Warning>
114114
115A longer accepted lifetime means a leaked Entra token stays exchangeable for longer. If a token leaks, the lever is disabling the federation rule; a tight `oid` match limits which identities can exchange a token in the first place, as described in [Scope your rule](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#scope-your-rule).
115A longer accepted lifetime means a leaked Entra token stays exchangeable for longer. If a token leaks, disable the federation rule; a tight `oid` match limits which identities can exchange a token in the first place, as described in [Scope your rule](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#scope-your-rule).
116116
117117**Federation rule:** Match on the managed identity's object ID and your tenant ID. For the v2.0 tokens this guide configures, the `audience` value is the audience app registration's client ID (the `<APP_ID>` GUID from [Register the token audience](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#register-the-token-audience)). Use the exact `aud` value from your decoded token.
118118
from line 196
196196 }'
197197 ```
198198
199 ```bash CLI
200 # Write the Entra-issued access token to a file the CLI can read.
201 # Shown for a VM or VM Scale Set (IMDS). On App Service, Functions, or
202 # Container Apps, fetch from "$IDENTITY_ENDPOINT?api-version=2019-08-01&resource=api://<APP_ID>"
203 # with -H "X-IDENTITY-HEADER: $IDENTITY_HEADER" instead.
204 # With multiple user-assigned identities, append &client_id=<IDENTITY_CLIENT_ID>.
205 export ANTHROPIC_IDENTITY_TOKEN_FILE=$(mktemp)
206 trap 'rm -f "$ANTHROPIC_IDENTITY_TOKEN_FILE"' EXIT
207 curl -sS -H "Metadata: true" \
208 "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=api://<APP_ID>" \
209 | jq -r .access_token > "$ANTHROPIC_IDENTITY_TOKEN_FILE"
210
211 # ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
212 # ANTHROPIC_SERVICE_ACCOUNT_ID, and ANTHROPIC_WORKSPACE_ID are read from the environment.
213 ant messages create \
214 --model claude-opus-5-5 \
215 --max-tokens 1024 \
216 --message '{role: user, content: "Hello from Azure"}'
217 ```
218
199219 ```python Python
200220 import os
201221
from line 610
590610 )
591611 puts message.content.find { it.type == :text }.text
592612 ```
593
594 ```bash CLI
595 # Write the Entra-issued access token to a file the CLI can read.
596 # Shown for a VM or VM Scale Set (IMDS). On App Service, Functions, or
597 # Container Apps, fetch from "$IDENTITY_ENDPOINT?api-version=2019-08-01&resource=api://<APP_ID>"
598 # with -H "X-IDENTITY-HEADER: $IDENTITY_HEADER" instead.
599 # With multiple user-assigned identities, append &client_id=<IDENTITY_CLIENT_ID>.
600 export ANTHROPIC_IDENTITY_TOKEN_FILE=$(mktemp)
601 trap 'rm -f "$ANTHROPIC_IDENTITY_TOKEN_FILE"' EXIT
602 curl -sS -H "Metadata: true" \
603 "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=api://<APP_ID>" \
604 | jq -r .access_token > "$ANTHROPIC_IDENTITY_TOKEN_FILE"
605
606 # ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
607 # ANTHROPIC_SERVICE_ACCOUNT_ID, and ANTHROPIC_WORKSPACE_ID are read from the environment.
608 ant messages create \
609 --model claude-opus-5-5 \
610 --max-tokens 1024 \
611 --message '{role: user, content: "Hello from Azure"}'
612 ```
613613</CodeGroup>
614614
615615### Verify the setup
from line 735
735735 }
736736 ```
737737
738 `sub` and `oid` are the managed identity's object ID, `aud` is the audience app registration's client ID, and `azp` is the managed identity's client ID (the value of `AZURE_CLIENT_ID`). The lifetime differs from the managed identity path: `client_credentials` tokens default to a random 60 to 90 minute window between `iat` and `exp`, not 24 hours.
738 `sub` and `oid` are the managed identity's object ID, `aud` is the audience app registration's client ID, and `azp` is the managed identity's client ID (the value of `AZURE_CLIENT_ID`). The lifetime differs from the managed identity path: `client_credentials` tokens default to a random 60- to 90-minute window between `iat` and `exp`, not 24 hours.
739739 </Step>
740740</Steps>
741741
from line 745
745745
746746The wizard creates these resources for you. Use the following values whether you enter them in the wizard or send them to the [Admin API](https://platform.claude.com/docs/en/manage-claude/wif-admin-api):
747747
748**Federation issuer:** Choose **v2.0 (login.microsoftonline.com)** in the wizard's **Token issuer** selector. (The selector defaults to v1; that default exists for tenants reusing older registrations that still emit v1.0 tokens.) Entra publishes an OIDC discovery document at the per-tenant issuer URL, so use discovery mode. Each Microsoft Entra tenant you federate needs its own issuer record.
748**Federation issuer:** Select **v2.0 (login.microsoftonline.com)** in the wizard's **Token issuer** selector. (The selector defaults to v1; that default exists for tenants reusing older registrations that still emit v1.0 tokens.) Entra publishes an OIDC discovery document at the per-tenant issuer URL, so use discovery mode. Each Microsoft Entra tenant you federate needs its own issuer record.
749749
750750```json
751751{
from line 757
757757```
758758
759759<Warning>
760 The Connect workload wizard's Microsoft Entra tile creates the issuer with `max_jwt_lifetime_seconds` set to `7500` (just over 2 hours), which covers the default 60 to 90 minute lifetime of `client_credentials` tokens. A tenant token-lifetime policy or Continuous Access Evaluation (CAE) can extend that lifetime. If your decoded token's `exp` minus `iat` exceeds 7500 seconds, edit the issuer on the **Issuers** tab of the [Workload identity](https://platform.claude.com/settings/workload-identity-federation) page in the Claude Console and raise `max_jwt_lifetime_seconds` to match, or exchanges fail with the opaque `401` `authentication_error` response (message `Authentication failed`). If your tenant also runs managed-identity workloads from [Use a managed identity](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#use-a-managed-identity), use that section's `86400` value, which covers both paths.
760 The **Connect workload** wizard's **Microsoft Entra** tile creates the issuer with `max_jwt_lifetime_seconds` set to `7500` (just over 2 hours), which covers the default 60- to 90-minute lifetime of `client_credentials` tokens. A tenant token-lifetime policy or Continuous Access Evaluation (CAE) can extend that lifetime. If your decoded token's `exp` minus `iat` exceeds 7,500 seconds, edit the issuer on the **Issuers** tab of the [Workload identity](https://platform.claude.com/settings/workload-identity-federation) page in the Claude Console and raise `max_jwt_lifetime_seconds` to match, or exchanges fail with the opaque `401` `authentication_error` response (message `Authentication failed`). If your tenant also runs managed-identity workloads from [Use a managed identity](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#use-a-managed-identity), use that section's `86400` value, which covers both paths.
761761</Warning>
762762
763A longer accepted lifetime means a leaked Entra token stays exchangeable for longer. If a token leaks, the lever is disabling the federation rule; a tight `oid` match limits which identities can exchange a token in the first place, as described in [Scope your rule](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#scope-your-rule).
763A longer accepted lifetime means a leaked Entra token stays exchangeable for longer. If a token leaks, disable the federation rule; a tight `oid` match limits which identities can exchange a token in the first place, as described in [Scope your rule](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#scope-your-rule).
764764
765765**Federation rule:** Match on the managed identity's object ID and your tenant ID. For the v2.0 tokens this guide configures, the `audience` value is the audience app registration's client ID (the `<APP_ID>` GUID from [Register the token audience](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#register-the-token-audience)). Use the exact `aud` value from your decoded token.
766766
from line 836
836836 }'
837837 ```
838838
839 ```bash CLI
840 # 1. Exchange the Kubernetes-projected token for an Entra-issued access
841 # token and write it to a temp file the CLI can read.
842 export ANTHROPIC_IDENTITY_TOKEN_FILE=$(mktemp)
843 trap 'rm -f "$ANTHROPIC_IDENTITY_TOKEN_FILE"' EXIT
844 curl -sS "https://login.microsoftonline.com/$AZURE_TENANT_ID/oauth2/v2.0/token" \
845 -d client_id="$AZURE_CLIENT_ID" \
846 -d grant_type=client_credentials \
847 --data-urlencode "scope=api://<APP_ID>/.default" \
848 -d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
849 --data-urlencode client_assertion@"$AZURE_FEDERATED_TOKEN_FILE" \
850 | jq -r .access_token > "$ANTHROPIC_IDENTITY_TOKEN_FILE"
851
852 # 2. Call the Claude API. ANTHROPIC_FEDERATION_RULE_ID,
853 # ANTHROPIC_ORGANIZATION_ID, ANTHROPIC_SERVICE_ACCOUNT_ID, and ANTHROPIC_WORKSPACE_ID are read
854 # from the environment.
855 ant messages create \
856 --model claude-opus-5-5 \
857 --max-tokens 1024 \
858 --message '{role: user, content: "Hello from Azure"}'
859 ```
860
839861 ```python Python
840862 import os
841863 from pathlib import Path
from line 1237
12151237 )
12161238 puts message.content.find { it.type == :text }.text
12171239 ```
1218
1219 ```bash CLI
1220 # 1. Exchange the Kubernetes-projected token for an Entra-issued access
1221 # token and write it to a temp file the CLI can read.
1222 export ANTHROPIC_IDENTITY_TOKEN_FILE=$(mktemp)
1223 trap 'rm -f "$ANTHROPIC_IDENTITY_TOKEN_FILE"' EXIT
1224 curl -sS "https://login.microsoftonline.com/$AZURE_TENANT_ID/oauth2/v2.0/token" \
1225 -d client_id="$AZURE_CLIENT_ID" \
1226 -d grant_type=client_credentials \
1227 --data-urlencode "scope=api://<APP_ID>/.default" \
1228 -d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
1229 --data-urlencode client_assertion@"$AZURE_FEDERATED_TOKEN_FILE" \
1230 | jq -r .access_token > "$ANTHROPIC_IDENTITY_TOKEN_FILE"
1231
1232 # 2. Call the Claude API. ANTHROPIC_FEDERATION_RULE_ID,
1233 # ANTHROPIC_ORGANIZATION_ID, ANTHROPIC_SERVICE_ACCOUNT_ID, and ANTHROPIC_WORKSPACE_ID are read
1234 # from the environment.
1235 ant messages create \
1236 --model claude-opus-5-5 \
1237 --max-tokens 1024 \
1238 --message '{role: user, content: "Hello from Azure"}'
1239 ```
12401240</CodeGroup>
12411241
12421242### Verify the setup
from line 1244
12441244From inside a labeled pod, run the cURL exchange shown in [Acquire and use the token](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#acquire-and-use-the-token-2) and confirm that `POST /v1/oauth/token` returns a `200` with an `access_token` beginning with `sk-ant-oat01-` and an `expires_in` value in seconds. If the exchange fails with the opaque `401` `authentication_error` response (message `Authentication failed`), check the [authentication history page](https://platform.claude.com/settings/workload-identity-federation?tab=history) in the Claude Console for the deny reason, then decode the Entra-issued token from step 1 (see [Troubleshoot a failed exchange](https://platform.claude.com/docs/en/manage-claude/wif-reference#troubleshoot-a-failed-exchange) for the command) and check the most common Azure-side causes:
12451245
12461246* **Issuer mismatch:** The registered `issuer_url` must match the token's `iss` claim exactly. A v2.0 token carries `https://login.microsoftonline.com/<TENANT_ID>/v2.0`; if the decoded `ver` claim is `1.0`, see [If your tokens are v1.0](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#if-your-tokens-are-v1-0).
1247* **Token lifetime:** If a tenant token-lifetime policy or CAE extends the `client_credentials` token past 7500 seconds, raise the issuer's `max_jwt_lifetime_seconds` as described in [Configure Anthropic](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#configure-anthropic-2).
1247* **Token lifetime:** If a tenant token-lifetime policy or CAE extends the `client_credentials` token past 7,500 seconds, raise the issuer's `max_jwt_lifetime_seconds` as described in [Configure Anthropic](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#configure-anthropic-2).
12481248* **Audience mismatch:** The rule's `audience` must equal the token's `aud` exactly: the audience app registration's client ID for the v2.0 tokens this guide configures.
12491249* **Claim name mismatch:** A rule that matches on a claim the token does not carry never passes. v1.0 tokens carry the client ID in `appid`, not `azp`; see [If your tokens are v1.0](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#if-your-tokens-are-v1-0).
12501250
from line 1253
12531253This guide configures the audience app registration with `api.requestedAccessTokenVersion: 2`, so every token it shows is v2.0. If you reuse an existing registration that leaves `requestedAccessTokenVersion` unset, Entra issues v1.0 tokens instead. Decode a sample token and check its `ver` claim; if it is `1.0`, four things change:
12541254
12551255* **Issuer:** The `iss` claim is `https://sts.windows.net/<TENANT_ID>/` instead of `https://login.microsoftonline.com/<TENANT_ID>/v2.0`. Register the issuer URL exactly as your token's `iss` claim carries it. The two URLs share the same JWKS, so discovery mode works for either.
1256* **Wizard selector:** Pick **v1 (sts.windows.net)** in the Connect workload wizard's **Token issuer** selector instead of **v2.0 (login.microsoftonline.com)**.
1256* **Wizard selector:** Select **v1 (sts.windows.net)** in the **Connect workload** wizard's **Token issuer** selector instead of **v2.0 (login.microsoftonline.com)**.
12571257* **Audience:** The `aud` claim is the identifier URI you passed as `resource` (for example, `api://<APP_ID>`), not the registration's client ID. Set the federation rule's `audience` to the exact `aud` value from your decoded token.
12581258* **Client ID claim:** The calling identity's client ID appears in `appid`, not `azp`. The two claims never appear in the same token, so a rule that matches on `azp` never passes against a v1.0 token.
12591259
manage-claude/wif-providers/gcp Changed · +6 / -6 lines
from line 63
6363 </Tab>
6464
6565 <Tab title="GKE with Workload Identity">
66 Enable [Workload Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity) on your cluster and bind your Kubernetes service account to a Google service account with the `iam.gke.io/gcp-service-account` annotation:
66 Enable [GKE Workload Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity) on your cluster and bind your Kubernetes service account to a Google service account with the `iam.gke.io/gcp-service-account` annotation:
6767
6868 ```yaml
6969 apiVersion: v1
from line 77
7777
7878 With this binding in place, the GKE metadata server returns a Google-signed token identical to the Cloud Run and GCE case: same `https://accounts.google.com` issuer, same `email` claim, same fetch URL. Configure Anthropic exactly as in the next section.
7979
80 A `format=full` token from GKE additionally includes `google.compute_engine.project_id`, `google.compute_engine.zone`, and `google.compute_engine.instance_name` claims, which you can reference in a federation rule's `condition` matcher (a CEL expression like `claims.google.compute_engine.project_id == "my-project"`) to scope access to a specific cluster or node pool.
80 A `format=full` token from GKE additionally includes `google.compute_engine.project_id`, `google.compute_engine.zone`, and `google.compute_engine.instance_name` claims, which you can reference in a federation rule's `condition` matcher (a CEL expression such as `claims.google.compute_engine.project_id == "my-project"`) to scope access to a specific cluster or node pool.
8181
8282 <Note>
8383 If you do not want to bind Kubernetes service accounts to Google service accounts, GKE pods can instead use the cluster's own OIDC issuer (`https://container.googleapis.com/v1/projects/PROJECT/locations/REGION/clusters/CLUSTER`) with a projected `serviceAccountToken` volume. That path uses a per-cluster issuer rather than `accounts.google.com`. See [Use WIF with Kubernetes](https://platform.claude.com/docs/en/manage-claude/wif-providers/kubernetes) for that pattern.
from line 137
137137 # Exchange it for an Anthropic access token
138138 RESPONSE=$(curl -sS https://api.anthropic.com/v1/oauth/token \
139139 -H "content-type: application/json" \
140 --data @- <<JSON
140 -d @- <<JSON
141141 {
142142 "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
143143 "assertion": "$JWT",
from line 380
380380 "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://api.anthropic.com&format=full" \
381381 > "$ANTHROPIC_IDENTITY_TOKEN_FILE"
382382
383 # ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID, and
383 # ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
384384 # ANTHROPIC_SERVICE_ACCOUNT_ID, and ANTHROPIC_WORKSPACE_ID are read from the environment.
385385 ant messages create \
386386 --model claude-opus-5-5 \
from line 453
453453 ```
454454</CodeGroup>
455455
456Google identity tokens expire after roughly one hour. Except in Java (see the comment in that example), the SDK re-invokes the token provider and re-exchanges automatically before expiry. For shell scripts that run longer than the access token's `expires_in`, refresh on a timer and repeat the exchange.
456Google identity tokens expire after roughly 1 hour. Except in Java (see the comment in that example), the SDK calls the token provider again and re-exchanges automatically before expiry. For shell scripts that run longer than the access token's `expires_in`, refresh on a timer and repeat the exchange.
457457
458458## Verify the setup
459459
from line 483
483483## Next steps
484484
485485* Read the [Workload Identity Federation](https://platform.claude.com/docs/en/manage-claude/workload-identity-federation) page for the full resource model and SDK credential precedence.
486* Add a separate federation rule per environment (production, staging) so you can revoke one without affecting the others.
486* Add a separate federation rule for each environment (production, staging) so you can revoke one without affecting the others.
487487
manage-claude/wif-providers/github-actions Changed · +4 / -4 lines
from line 6
66
77Every GitHub Actions workflow run can request a signed identity token from GitHub's hosted issuer at `https://token.actions.githubusercontent.com`. With Workload Identity Federation, your workflow exchanges that token for a short-lived Anthropic access token, so your CI jobs can call the Claude API without an `ANTHROPIC_API_KEY` secret stored in your repository.
88
9The token's `sub` claim encodes the repository and trigger context. For a push to a branch it has the form `repo:<owner>/<repo>:ref:refs/heads/<branch>`. Pull-request runs use `repo:<owner>/<repo>:pull_request`, and environment-gated deployments use `repo:<owner>/<repo>:environment:<name>`. Your federation rule matches against this claim (and others, such as `repository_owner` and `ref`) to decide which workflow runs are allowed to authenticate.
9The token's `sub` claim encodes the repository and trigger context. For a push to a branch it has the form `repo:<owner>/<repo>:ref:refs/heads/<branch>`. Pull-request runs use `repo:<owner>/<repo>:pull_request`, and environment-gated deployments use `repo:<owner>/<repo>:environment:<name>`. Your federation rule matches against this claim (and others, such as `repository_owner` and `ref`) to determine which workflow runs are allowed to authenticate.
1010
1111## Prerequisites
1212
from line 105
105105}
106106```
107107
108Be as specific as the workload allows. Loosen `subject_prefix` to `repo:your-org/your-repo:*` (paired with a `claims.ref` constraint) only if the rule must match multiple event types from the same repository, because the trailing segment of `sub` varies between `ref:...`, `environment:...`, and `pull_request` events.
108Be as specific as the workload allows. Loosen `subject_prefix` to `repo:your-org/your-repo:*` (paired with a `claims.ref` constraint) only if the rule must match multiple event types from the same repository, because the trailing segment of `sub` varies among `ref:...`, `environment:...`, and `pull_request` events.
109109
110110## Acquire and use a token
111111
from line 147
147147
148148 ACCESS_TOKEN=$(curl -sS https://api.anthropic.com/v1/oauth/token \
149149 -H "content-type: application/json" \
150 --data @- <<JSON | jq -r .access_token
150 -d @- <<JSON | jq -r .access_token
151151 {
152152 "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
153153 "assertion": "$JWT",
from line 307
307307 ```
308308</CodeGroup>
309309
310Each GitHub-issued identity token expires roughly five minutes after issuance. The token-request endpoint (`ACTIONS_ID_TOKEN_REQUEST_URL`) stays valid for the entire job, so you can fetch a fresh token at any point. The SDK exchanges the token on first use and caches the resulting Anthropic access token. For jobs that run longer than the Anthropic token's lifetime, the SDK re-reads `ANTHROPIC_IDENTITY_TOKEN_FILE` on each refresh, so re-run the fetch step periodically (or wrap it in a background loop) to keep the file current. Alternatively, except in Java, pass the SDK a token provider that calls `ACTIONS_ID_TOKEN_REQUEST_URL` directly instead of using the file path.
310Each GitHub-issued identity token expires roughly 5 minutes after issuance. The token-request endpoint (`ACTIONS_ID_TOKEN_REQUEST_URL`) stays valid for the entire job, so you can fetch a fresh token at any point. The SDK exchanges the token on first use and caches the resulting Anthropic access token. For jobs that run longer than the Anthropic token's lifetime, the SDK re-reads `ANTHROPIC_IDENTITY_TOKEN_FILE` on each refresh, so re-run the fetch step periodically (or wrap it in a background loop) to keep the file current. Alternatively, except in Java, pass the SDK a token provider that calls `ACTIONS_ID_TOKEN_REQUEST_URL` directly instead of using the file path.
311311
312312## Verify the setup
313313