Follow Discord
Sweep 27 Oct 2026 · 19:04Z Build v2.1.312 531 read Stable v2.1.308 Latest v2.1.312 Next v2.1.312 Feeds RSS JSON llms.txt llms-full.txt Unofficial
One capture · api

One read of Claude Developer Platformapi-20261027T185806Z

260 pages moved out of 821 read.

Pages moved 260 significant first
Pages read 821 in this capture
Captured 18:58 UTC
Corpus hash 23cb04cc4921 corpus-hash

What this read moved

176-200 of 260, page 8 of 11

This capture is too large to show at once. Changes 176-200 of 260 are below, significant first; the rest are on the following screens.

manage-claude/wif-admin-api Changed · +8 / -8 lines

from line 12
1212 
1313### Interactive (your terminal)
1414 
15Log in with the [`ant` CLI](https://platform.claude.com/docs/en/cli-sdks-libraries/cli/quickstart) under a dedicated profile, requesting the `org:admin` scope (see [Admin access](https://platform.claude.com/docs/en/cli-sdks-libraries/cli/authentication#admin-access)), then export the bearer token. Logging in with `--profile admin` stores the `org:admin` credential under its own profile name and also makes it the CLI's active profile. The exported variable applies to every SDK and CLI call in that shell. For these reasons, use a shell you reserve for administration, unset the variable when you are done, and switch the CLI back with `ant profile activate default`:
15Log in with the [`ant` CLI](https://platform.claude.com/docs/en/cli-sdks-libraries/cli/quickstart) under a dedicated profile, requesting the `org:admin` scope (see [Admin access](https://platform.claude.com/docs/en/cli-sdks-libraries/cli/authentication#admin-access)), then export the bearer token. Logging in with `--profile admin` stores the `org:admin` credential under its own profile name and also makes it the CLI’s active profile. The exported variable applies to every SDK and CLI call in that shell. For these reasons, use a shell you reserve for administration, unset the variable when you are done, and switch the CLI back with `ant profile activate default`:
1616 
1717```bash CLI
1818ant auth login --profile admin --scope "org:admin"
from line 33
3333 
3434<Steps>
3535 <Step title="Create the org:admin rule in the Console">
36 In the Claude Console, go to the [Workload identity](https://platform.claude.com/settings/workload-identity-federation) page (`platform.claude.com/settings/workload-identity-federation`) and select **Connect workload** to create one federation rule for your automation workload, for example a GitHub Actions workflow in your infrastructure repository. Under **Advanced rule options**, set the rule's OAuth scope to `org:admin`: the wizard then creates the new service account with the Admin organization role (or asks you to pick an existing admin service account as the target).
36 In the Claude Console, go to the [Workload identity](https://platform.claude.com/settings/workload-identity-federation) page (`platform.claude.com/settings/workload-identity-federation`) and select **Connect workload** to create one federation rule for your automation workload, for example a GitHub Actions workflow in your infrastructure repository. Under **Advanced rule options**, set the rule’s OAuth scope to `org:admin`: the wizard then creates the new service account with the Admin organization role (or asks you to pick an existing admin service account as the target).
3737 
3838 <Warning>
3939 Match the rule to one exact workload identity, not a broad pattern. `subject_prefix` is an exact match unless it ends in `*`. For GitHub Actions, pin the subject to a protected branch, such as `repo:my-org/my-repo:ref:refs/heads/main`. A trailing wildcard such as `repo:my-org/my-repo:*` also matches `pull_request` runs, including runs triggered from forks, so anyone who could open a pull request against the repository could mint an `org:admin` token. See [Restrict which workflows can authenticate](https://platform.claude.com/docs/en/manage-claude/wif-providers/github-actions#restrict-which-workflows-can-authenticate).
from line 40
4040 </Warning>
4141 </Step>
4242 
43 <Step title="Exchange the workload's identity token">
43 <Step title="Exchange the workload’s identity token">
4444 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
from line 155
155155 ```
156156</CodeGroup>
157157 
158Admin API keys are not accepted on these endpoints; the Admin API page's `x-api-key` examples do not apply here.
158Admin API keys are not accepted on these endpoints; the Admin API page’s `x-api-key` examples do not apply here.
159159 
160160## Service accounts
161161 
from line 482
482482}
483483```
484484 
485To read or update a single service account, use `GET` and `POST` on `/v1/organizations/service_accounts/{service_account_id}`. A service account must be a member of a workspace before federated tokens can act in it. Every service account has an implicit membership in your organization's default workspace; add explicit memberships for other workspaces with `GET`, `POST`, and `DELETE` on `/v1/organizations/service_accounts/{service_account_id}/workspaces`, where `DELETE` targets `.../workspaces/{workspace_id}`.
485To read or update a single service account, use `GET` and `POST` on `/v1/organizations/service_accounts/{service_account_id}`. A service account must be a member of a workspace before federated tokens can act in it. Every service account has an implicit membership in your organization’s default workspace; add explicit memberships for other workspaces with `GET`, `POST`, and `DELETE` on `/v1/organizations/service_accounts/{service_account_id}/workspaces`, where `DELETE` targets `.../workspaces/{workspace_id}`.
486486 
487487For complete parameter details and response schemas, see the [Service accounts API reference](https://platform.claude.com/docs/en/api/organization/service_accounts).
488488 
489489## Federation issuers
490490 
491A [federation issuer](https://platform.claude.com/docs/en/manage-claude/workload-identity-federation#federation-issuers) (`fdis_...`) registers an OIDC identity provider with your organization. The `jwks` field is a discriminated union that controls how Anthropic fetches the provider's signing keys:
491A [federation issuer](https://platform.claude.com/docs/en/manage-claude/workload-identity-federation#federation-issuers) (`fdis_...`) registers an OIDC identity provider with your organization. The `jwks` field is a discriminated union that controls how Anthropic fetches the provider’s signing keys:
492492 
493493| `jwks` value | When to use |
494494| ---------------------------------------- | --------------------------------------------------------------------------------- |
from line 826
826826 
827827## Federation rules
828828 
829A [federation rule](https://platform.claude.com/docs/en/manage-claude/workload-identity-federation#federation-rules) (`fdrl_...`) binds an issuer to a service account: JWTs from the issuer that satisfy the rule's match conditions can mint tokens that act as the rule's target. The `workspace_id` in the create request enables the rule in that workspace at creation; add more workspaces later through the `/federation_rules/{rule_id}/workspaces` sub-resource. Either `workspace_id` or `applies_to_all_workspaces: true` is required on create.
829A [federation rule](https://platform.claude.com/docs/en/manage-claude/workload-identity-federation#federation-rules) (`fdrl_...`) binds an issuer to a service account: JWTs from the issuer that satisfy the rule’s match conditions can mint tokens that act as the rule’s target. The `workspace_id` in the create request enables the rule in that workspace at creation; add more workspaces later through the `/federation_rules/{rule_id}/workspaces` sub-resource. Either `workspace_id` or `applies_to_all_workspaces: true` is required on create.
830830 
831831Create a rule. This example lets GitHub Actions deploys from the main branch act as the service account:
832832 
from line 1265
12651265 
12661266## Pagination and archiving
12671267 
1268The service-account, federation-issuer, and federation-rule list endpoints accept `limit` (1 to 100, default 20) and a `page` cursor taken from the previous response. Pass the response's `next_page` value as the `page` query parameter on the next request. The rule-workspaces sub-resource list returns the full set without pagination. Archived resources are hidden from lists by default; pass `include_archived=true` to include them.
1268The service-account, federation-issuer, and federation-rule list endpoints accept `limit` (1 to 100, default 20) and a `page` cursor taken from the previous response. Pass the response’s `next_page` value as the `page` query parameter on the next request. The rule-workspaces sub-resource list returns the full set without pagination. Archived resources are hidden from lists by default; pass `include_archived=true` to include them.
12691269 
12701270Archiving is a soft delete and is idempotent: archiving an already-archived resource succeeds. Archiving an issuer or a service account returns `400` while a live federation rule still references it; archive the rule first.
12711271 

manage-claude/wif-providers/aws Changed · +16 / -16 lines

from line 17
1717 
1818## Use STS web identity tokens (recommended)
1919 
20The AWS STS `GetWebIdentityToken` API returns an OIDC token signed by AWS that asserts the caller's IAM identity. Because it uses the workload's ambient AWS credentials, the same integration covers Lambda, EC2, ECS, and EKS.
20The AWS STS `GetWebIdentityToken` API returns an OIDC token signed by AWS that asserts the caller’s IAM identity. Because it uses the workload’s ambient AWS credentials, the same integration covers Lambda, EC2, ECS, and EKS.
2121 
2222### Configure AWS
2323 
from line 32
3232 If this is not enabled, calls to `GetWebIdentityToken` fail with `OutboundWebIdentityFederationDisabledException`.
3333 </Step>
3434 
35 <Step title="Grant the workload's IAM role permission to call the API">
35 <Step title="Grant the workload’s IAM role permission to call the API">
3636 Attach this policy to the IAM role that your Lambda function, EC2 instance, or ECS task runs as:
3737 
3838 ```json
from line 49
4949 ```
5050 </Step>
5151 
52 <Step title="Find your account's STS issuer URL">
52 <Step title="Find your account’s STS issuer URL">
5353 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
from line 74
7474}
7575```
7676 
77**Federation rule:** Match the audience you pass to `GetWebIdentityToken` and the calling role's IAM role ARN in the `sub` claim. The `sub` value is the IAM role ARN of the workload that called the API, in the form `arn:aws:iam::<account>:role/<role-name>`. The token also carries an `https://sts.amazonaws.com/` claim with `aws_account`, `org_id`, `principal_id`, and any `request_tags` you passed; you can match on those with the rule's `claims` map or a CEL `condition` for finer control.
77**Federation rule:** Match the audience you pass to `GetWebIdentityToken` and the calling role’s IAM role ARN in the `sub` claim. The `sub` value is the IAM role ARN of the workload that called the API, in the form `arn:aws:iam::<account>:role/<role-name>`. The token also carries an `https://sts.amazonaws.com/` claim with `aws_account`, `org_id`, `principal_id`, and any `request_tags` you passed; you can match on those with the rule’s `claims` map or a CEL `condition` for finer control.
7878 
7979```json
8080{
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 calls STS again 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 491
491491### Configure your EKS cluster
492492 
493493<Steps>
494 <Step title="Find your cluster's OIDC issuer URL">
494 <Step title="Find your cluster’s OIDC issuer URL">
495495 Each EKS cluster has a unique OIDC issuer. Retrieve it with the AWS CLI:
496496 
497497 ```bash CLI
from line 501
501501 --output text
502502 ```
503503 
504 The output looks like `https://oidc.eks.us-west-2.amazonaws.com/id/6FA42E7BFDE8549CB...`. You'll register this URL as a federation issuer in the next section.
504 The output looks like `https://oidc.eks.us-west-2.amazonaws.com/id/6FA42E7BFDE8549CB...`. You’ll register this URL as a federation issuer in the next section.
505505 </Step>
506506 
507507 <Step title="Create the service account and project an Anthropic-audience token">
from line 554
554554 ```
555555 </Step>
556556 
557 <Step title="Note the token's claim shape">
558 The projected token is a JSON Web Token (JWT) signed by your cluster's OIDC issuer. Its `sub` claim follows the Kubernetes convention `system:serviceaccount:<namespace>:<service-account-name>`:
557 <Step title="Note the token’s claim shape">
558 The projected token is a JSON Web Token (JWT) signed by your cluster’s OIDC issuer. Its `sub` claim follows the Kubernetes convention `system:serviceaccount:<namespace>:<service-account-name>`:
559559 
560560 ```json
561561 {
from line 581
581581 
582582The 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):
583583 
584**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.
584**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.
585585 
586586```json
587587{
from line 591
591591}
592592```
593593 
594**Federation rule:** Match the Kubernetes `sub` claim and the Anthropic audience `https://api.anthropic.com`. (Project a dedicated service-account token with that audience; don't reuse the IRSA default `sts.amazonaws.com` token.)
594**Federation rule:** Match the Kubernetes `sub` claim and the Anthropic audience `https://api.anthropic.com`. (Project a dedicated service-account token with that audience; don’t reuse the IRSA default `sts.amazonaws.com` token.)
595595 
596596```json
597597{
from line 612
612612 
613613### Acquire and use the token
614614 
615Inside the pod, the projected token is at `/var/run/secrets/anthropic.com/token` (exposed as `ANTHROPIC_IDENTITY_TOKEN_FILE` in the Pod spec). Pass that file to the SDK's federation credentials and the SDK handles the exchange and refresh.
615Inside the pod, the projected token is at `/var/run/secrets/anthropic.com/token` (exposed as `ANTHROPIC_IDENTITY_TOKEN_FILE` in the Pod spec). Pass that file to the SDK’s federation credentials and the SDK handles the exchange and refresh.
616616 
617617<CodeGroup>
618618 ```bash cURL
from line 835
835835JSON
836836```
837837 
838A successful exchange returns an `access_token` starting 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 and see [Troubleshoot a failed exchange](https://platform.claude.com/docs/en/manage-claude/wif-reference#troubleshoot-a-failed-exchange). The most common EKS-side cause is the projected token's `aud` not matching the rule (project a token with `audience: https://api.anthropic.com`, not the IRSA default `sts.amazonaws.com`).
838A successful exchange returns an `access_token` starting 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 and see [Troubleshoot a failed exchange](https://platform.claude.com/docs/en/manage-claude/wif-reference#troubleshoot-a-failed-exchange). The most common EKS-side cause is the projected token’s `aud` not matching the rule (project a token with `audience: https://api.anthropic.com`, not the IRSA default `sts.amazonaws.com`).
839839 
840840## Scope your rule
841841 
from line 843
843843 A `subject_prefix` of `arn:aws:iam::123456789012:role/*` matches every IAM role in the account. Any principal that can assume any matching role can obtain a federated Anthropic token.
844844</Warning>
845845 
846Lock the rule's `match` block to the narrowest scope that fits your use case:
846Lock the rule’s `match` block to the narrowest scope that fits your use case:
847847 
848848* **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.
849* **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.
849* **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.
850850* **Pin namespace and service account on EKS:** Use the exact `system:serviceaccount:<namespace>:<name>` value with no `*` after the `system:serviceaccount:` prefix.
851851* **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.
852852 
from line 853
853853## Next steps
854854 
855855* Review the [WIF reference](https://platform.claude.com/docs/en/manage-claude/wif-reference) for the full credential precedence, profile configuration, and rule matching reference.
856* For self-managed Kubernetes clusters that aren't on EKS, see [Use WIF with Kubernetes](https://platform.claude.com/docs/en/manage-claude/wif-providers/kubernetes).
856* For self-managed Kubernetes clusters that aren’t on EKS, see [Use WIF with Kubernetes](https://platform.claude.com/docs/en/manage-claude/wif-providers/kubernetes).
857857 

manage-claude/wif-providers/azure Changed · +42 / -42 lines

from line 8
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.
10102. **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.
113. **Configure Anthropic:** Register your tenant's Entra issuer, create a service account, and write a federation rule that matches the token's claims.
113. **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, 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.
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 23
2323 
2424## Register the token audience
2525 
26Microsoft Entra ID only issues a token when the requested audience exists in your tenant as an app registration with a service principal. Create one app registration to represent the Claude API audience; every workload in the tenant can request tokens for it. Without this registration, token requests fail with a "resource not found in tenant" error (`AADSTS50001` from the managed identity endpoints, `AADSTS500011` from the Entra token endpoint).
26Microsoft Entra ID only issues a token when the requested audience exists in your tenant as an app registration with a service principal. Create one app registration to represent the Claude API audience; every workload in the tenant can request tokens for it. Without this registration, token requests fail with a “resource not found in tenant” error (`AADSTS50001` from the managed identity endpoints, `AADSTS500011` from the Entra token endpoint).
2727 
2828```bash
2929# Create the app registration that represents the Claude API audience.
from line 44
4444 
4545## Use a managed identity
4646 
47Use this path when your workload runs on a VM, a VM Scale Set, App Service, Functions, or Container Apps. The workload requests an Entra-issued JWT for its assigned managed identity from the platform's local token endpoint, then exchanges that JWT with Anthropic.
47Use this path when your workload runs on a VM, a VM Scale Set, App Service, Functions, or Container Apps. The workload requests an Entra-issued JWT for its assigned managed identity from the platform’s local token endpoint, then exchanges that JWT with Anthropic.
4848 
4949### Configure the managed identity
5050 
from line 52
5252 <Step title="Attach a managed identity">
5353 Enable a system-assigned or user-assigned managed identity on your Azure resource. In the Azure portal, open the resource, go to **Identity**, and turn on **System assigned** (or attach a user-assigned identity).
5454 
55 After the identity is created, note its **Object (principal) ID**. This GUID appears as both the `sub` and `oid` claims in the issued token, and your Anthropic federation rule will match on it. You can find it on the resource's **Identity** page; for a user-assigned identity, it is the **Object (principal) ID** on the managed identity resource's **Overview** page. (A managed identity has only a service principal in Microsoft Entra ID, not an app registration.)
55 After the identity is created, note its **Object (principal) ID**. This GUID appears as both the `sub` and `oid` claims in the issued token, and your Anthropic federation rule will match on it. You can find it on the resource’s **Identity** page; for a user-assigned identity, it is the **Object (principal) ID** on the managed identity resource’s **Overview** page. (A managed identity has only a service principal in Microsoft Entra ID, not an app registration.)
5656 </Step>
5757 
58 <Step title="Find the platform's token endpoint">
58 <Step title="Find the platform’s token endpoint">
5959 The platform exposes a local token endpoint once the identity is attached:
6060 
6161 * **VMs and VM Scale Sets:** IMDS at `http://169.254.169.254/metadata/identity/oauth2/token` with the header `Metadata: true` and `api-version=2018-02-01`.
6262 * **App Service, Functions, and Container Apps:** The URL in the `IDENTITY_ENDPOINT` environment variable with the header `X-IDENTITY-HEADER` set to the value of `IDENTITY_HEADER`, and `api-version=2019-08-01`. IMDS is not reachable on these platforms.
6363 
64 If the resource has more than one user-assigned managed identity, add `client_id=<IDENTITY_CLIENT_ID>` to the token request to select one. Azure recommends always specifying it. Without it, the outcome depends on whether the resource also has a system-assigned identity enabled: if it does, the request silently falls back to that identity and then fails your federation rule's `oid` match; if it does not, the request fails outright as soon as a second user-assigned identity is attached.
64 If the resource has more than one user-assigned managed identity, add `client_id=<IDENTITY_CLIENT_ID>` to the token request to select one. Azure recommends always specifying it. Without it, the outcome depends on whether the resource also has a system-assigned identity enabled: if it does, the request silently falls back to that identity and then fails your federation rule’s `oid` match; if it does not, the request fails outright as soon as a second user-assigned identity is attached.
6565 </Step>
6666 
6767 <Step title="Decode a sample token">
from line 82
8282 
8383 | Claim | Value | Match this when |
8484 | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
85 | `oid` | The managed identity's object ID, identical to `sub` | You want to authorize one specific managed identity. This is the default; the rule in [Configure Anthropic](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#configure-anthropic) matches it. |
86 | `azp` | The calling identity's client ID | You want to authorize every workload that shares one app registration. For a managed identity, `azp` is unique to that identity, so it is equivalent to `oid`. |
87 | `aud` | 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)) | Always. The rule's `audience` field must equal the token's `aud` value exactly. |
85 | `oid` | The managed identity’s object ID, identical to `sub` | You want to authorize one specific managed identity. This is the default; the rule in [Configure Anthropic](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#configure-anthropic) matches it. |
86 | `azp` | The calling identity’s client ID | You want to authorize every workload that shares one app registration. For a managed identity, `azp` is unique to that identity, so it is equivalent to `oid`. |
87 | `aud` | 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)) | Always. The rule’s `audience` field must equal the token’s `aud` value exactly. |
8888 | `tid` | Your tenant ID | You want defense in depth. The issuer URL already pins the tenant. |
8989 
90 If the decoded token's `ver` claim is `1.0`, the claim names and values differ. See [If your tokens are v1.0](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#if-your-tokens-are-v1-0) before continuing.
90 If the decoded token’s `ver` claim is `1.0`, the claim names and values differ. See [If your tokens are v1.0](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#if-your-tokens-are-v1-0) before continuing.
9191 </Step>
9292</Steps>
9393 
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:** 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.
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 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.
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 
115115A 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 
117**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.
117**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 
119119```json
120120{
from line 143
143143 
144144At runtime your workload fetches its Entra token, exchanges it at `POST /v1/oauth/token`, and uses the returned bearer token to call Claude. The Claude SDK handles the exchange and refresh loop when you supply `identity_token_provider` (typescript: `identityTokenProvider`; php: `identityProvider`; csharp: `IdentityTokenProvider`; go: `option.WithFederationTokenProvider()`; java: `IdentityTokenConfig`), as shown in the following examples. Except in Java (see the comment in that example), that option takes a token provider, and the SDK calls it again for each refresh. The cURL tab shows the raw flow.
145145 
146The samples fetch the managed identity token from the platform's token endpoint: IMDS on VMs and VM Scale Sets, or the `IDENTITY_ENDPOINT` service on App Service, Functions, and Container Apps. Replace `<APP_ID>` in the `api://<APP_ID>` resource value with the audience app registration's client ID from [Register the token audience](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#register-the-token-audience).
146The samples fetch the managed identity token from the platform’s token endpoint: IMDS on VMs and VM Scale Sets, or the `IDENTITY_ENDPOINT` service on App Service, Functions, and Container Apps. Replace `<APP_ID>` in the `api://<APP_ID>` resource value with the audience app registration’s client ID from [Register the token audience](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#register-the-token-audience).
147147 
148148<Tip>
149149 If your workload already uses the Azure Identity client library, get the Entra token from it (`DefaultAzureCredential` with the scope `api://<APP_ID>/.default`) instead of calling the token endpoints directly. Supply it to `identity_token_provider` (typescript: `identityTokenProvider`; php: `identityProvider`; csharp: `IdentityTokenProvider`; go: `option.WithFederationTokenProvider()`; java: `IdentityTokenConfig`) the way the examples do. The library selects the correct endpoint on every Azure platform, including AKS with Entra Workload Identity.
from line 616
616616 
617617From your Azure resource, 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) and confirm that `POST /v1/oauth/token` returns a `200` with an `access_token` starting 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 token (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:
618618 
619* **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).
620* **Token lifetime:** Managed identity tokens carry up to 24 hours between `iat` and `exp`. If the issuer still has the wizard's `7500` (or the 1-hour default), raise `max_jwt_lifetime_seconds` to `86400` as described in [Configure Anthropic](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#configure-anthropic).
621* **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.
619* **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).
620* **Token lifetime:** Managed identity tokens carry up to 24 hours between `iat` and `exp`. If the issuer still has the wizard’s `7500` (or the 1-hour default), raise `max_jwt_lifetime_seconds` to `86400` as described in [Configure Anthropic](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#configure-anthropic).
621* **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.
622622* **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).
623623 
624624## Use Entra Workload Identity on AKS
625625 
626Use this path when your workload runs in an AKS pod. Entra Workload Identity federates a Kubernetes service account with a user-assigned managed identity: Kubernetes projects a service account token (signed by the AKS cluster's OIDC issuer) into the pod at the path in `AZURE_FEDERATED_TOKEN_FILE`. That projected token is not an Entra-issued token, so to stay on the Entra-mediated path described on this page, the workload performs a two-hop exchange. First, it redeems the projected token at `https://login.microsoftonline.com/<TENANT_ID>/oauth2/v2.0/token` (federated `client_credentials` grant) for an Entra-issued access token. Then it passes that Entra token to the Anthropic SDK as the identity token.
626Use this path when your workload runs in an AKS pod. Entra Workload Identity federates a Kubernetes service account with a user-assigned managed identity: Kubernetes projects a service account token (signed by the AKS cluster’s OIDC issuer) into the pod at the path in `AZURE_FEDERATED_TOKEN_FILE`. That projected token is not an Entra-issued token, so to stay on the Entra-mediated path described on this page, the workload performs a two-hop exchange. First, it redeems the projected token at `https://login.microsoftonline.com/<TENANT_ID>/oauth2/v2.0/token` (federated `client_credentials` grant) for an Entra-issued access token. Then it passes that Entra token to the Anthropic SDK as the identity token.
627627 
628628<Tip>
629 AKS pods can alternatively skip the Entra exchange and present the Kubernetes-projected service account token to Anthropic directly. That path registers your AKS cluster's OIDC issuer with Anthropic instead of your Entra tenant. See [Use WIF with Kubernetes](https://platform.claude.com/docs/en/manage-claude/wif-providers/kubernetes) for that flow.
629 AKS pods can alternatively skip the Entra exchange and present the Kubernetes-projected service account token to Anthropic directly. That path registers your AKS cluster’s OIDC issuer with Anthropic instead of your Entra tenant. See [Use WIF with Kubernetes](https://platform.claude.com/docs/en/manage-claude/wif-providers/kubernetes) for that flow.
630630</Tip>
631631 
632632### Configure Entra Workload Identity
from line 633
633633 
634634<Steps>
635635 <Step title="Enable the OIDC issuer and workload identity on your cluster">
636 Enabling workload identity installs the `azure-workload-identity` mutating webhook for you; deploy it manually only on non-AKS clusters. Capture the cluster's OIDC issuer URL for the federated credential you create in a later step.
636 Enabling workload identity installs the `azure-workload-identity` mutating webhook for you; deploy it manually only on non-AKS clusters. Capture the cluster’s OIDC issuer URL for the federated credential you create in a later step.
637637 
638638 ```bash
639639 az aks update \
from line 687
687687 </Step>
688688 
689689 <Step title="Create the federated credential on the managed identity">
690 The federated credential trusts your cluster's OIDC issuer for that specific service account. The `--audience api://AzureADTokenExchange` value is Entra's fixed audience for incoming Kubernetes service account tokens; it is unrelated to the Claude API audience you registered earlier.
690 The federated credential trusts your cluster’s OIDC issuer for that specific service account. The `--audience api://AzureADTokenExchange` value is Entra’s fixed audience for incoming Kubernetes service account tokens; it is unrelated to the Claude API audience you registered earlier.
691691 
692692 ```bash
693693 az identity federated-credential create \
from line 701
701701 </Step>
702702 
703703 <Step title="Label the pod and set its service account">
704 The pod must carry the `azure.workload.identity/use: "true"` label and run as the annotated service account. The webhook then injects `AZURE_FEDERATED_TOKEN_FILE`, `AZURE_CLIENT_ID`, and `AZURE_TENANT_ID` into the pod. The file at `AZURE_FEDERATED_TOKEN_FILE` contains the Kubernetes-projected service account token, signed by the AKS cluster's OIDC issuer.
704 The pod must carry the `azure.workload.identity/use: "true"` label and run as the annotated service account. The webhook then injects `AZURE_FEDERATED_TOKEN_FILE`, `AZURE_CLIENT_ID`, and `AZURE_TENANT_ID` into the pod. The file at `AZURE_FEDERATED_TOKEN_FILE` contains the Kubernetes-projected service account token, signed by the AKS cluster’s OIDC issuer.
705705 
706706 ```yaml
707707 apiVersion: v1
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:** 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.
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 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.
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 
763763A 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 
765**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.
765**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 
767767```json
768768{
from line 789
789789 
790790### Acquire and use the token
791791 
792At runtime the pod performs the two-hop exchange: it sends the Kubernetes-projected token (the file at `AZURE_FEDERATED_TOKEN_FILE`) to Entra's token endpoint as a federated `client_credentials` assertion, then exchanges the resulting Entra access token at `POST /v1/oauth/token`. The Claude SDK handles the second exchange and the refresh loop when you supply `identity_token_provider` (typescript: `identityTokenProvider`; php: `identityProvider`; csharp: `IdentityTokenProvider`; go: `option.WithFederationTokenProvider()`; java: `IdentityTokenConfig`), as shown in the following examples. Except in Java (see the comment in that example), that option takes your Entra fetch as a token provider, and the SDK calls it again for each refresh. The cURL tab shows the raw flow.
792At runtime the pod performs the two-hop exchange: it sends the Kubernetes-projected token (the file at `AZURE_FEDERATED_TOKEN_FILE`) to Entra’s token endpoint as a federated `client_credentials` assertion, then exchanges the resulting Entra access token at `POST /v1/oauth/token`. The Claude SDK handles the second exchange and the refresh loop when you supply `identity_token_provider` (typescript: `identityTokenProvider`; php: `identityProvider`; csharp: `IdentityTokenProvider`; go: `option.WithFederationTokenProvider()`; java: `IdentityTokenConfig`), as shown in the following examples. Except in Java (see the comment in that example), that option takes your Entra fetch as a token provider, and the SDK calls it again for each refresh. The cURL tab shows the raw flow.
793793 
794Two different client IDs appear in the samples. `<APP_ID>` is the audience app registration's client ID from [Register the token audience](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#register-the-token-audience); the scope `api://<APP_ID>/.default` asks Entra for a token addressed to that audience. `$AZURE_CLIENT_ID` is the managed identity's client ID, injected by the webhook, and identifies the caller. Do not substitute one for the other.
794Two different client IDs appear in the samples. `<APP_ID>` is the audience app registration’s client ID from [Register the token audience](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#register-the-token-audience); the scope `api://<APP_ID>/.default` asks Entra for a token addressed to that audience. `$AZURE_CLIENT_ID` is the managed identity’s client ID, injected by the webhook, and identifies the caller. Do not substitute one for the other.
795795 
796796<Tip>
797797 If your workload already uses the Azure Identity client library, get the Entra token from it (`DefaultAzureCredential` with the scope `api://<APP_ID>/.default`) instead of performing the two-hop exchange yourself. Supply it to `identity_token_provider` (typescript: `identityTokenProvider`; php: `identityProvider`; csharp: `IdentityTokenProvider`; go: `option.WithFederationTokenProvider()`; java: `IdentityTokenConfig`) the way the examples do. The library reads the same `AZURE_FEDERATED_TOKEN_FILE`, `AZURE_CLIENT_ID`, and `AZURE_TENANT_ID` environment variables and handles the Entra exchange.
from line 1243
12431243 
12441244From inside a labeled pod, run the cURL exchange shown in [Acquire and use the token on AKS](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` starting 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 
1246* **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 7,500 seconds, raise the issuer's `max_jwt_lifetime_seconds` as described in [Configure Anthropic for Entra Workload Identity on AKS](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#configure-anthropic-2).
1248* **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.
1246* **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 7,500 seconds, raise the issuer’s `max_jwt_lifetime_seconds` as described in [Configure Anthropic for Entra Workload Identity on AKS](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#configure-anthropic-2).
1248* **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 
12511251## If your tokens are v1.0
from line 1252
12521252 
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 
1255* **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:** Select **v1 (sts.windows.net)** in the **Connect workload** wizard's **Token issuer** selector instead of **v2.0 (login.microsoftonline.com)**.
1257* **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.
1258* **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.
1255* **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:** Select **v1 (sts.windows.net)** in the **Connect workload** wizard’s **Token issuer** selector instead of **v2.0 (login.microsoftonline.com)**.
1257* **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.
1258* **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 
12601260The `oid`, `sub`, and `tid` claims carry the same values in both versions, so the rest of this guide applies unchanged.
12611261 
12621262## Scope your rule
12631263 
1264A federation rule can match the token's subject with `subject_prefix` in addition to (or instead of) the `claims` map; see [Rule matching semantics](https://platform.claude.com/docs/en/manage-claude/wif-reference#rule-matching-semantics) for how the fields combine. Entra `sub` values for these identities are fixed-length canonical GUIDs, so a `subject_prefix` containing the full 36-character object ID matches only that subject; this is a property of Entra's subject format, not of `subject_prefix` in general.
1264A federation rule can match the token’s subject with `subject_prefix` in addition to (or instead of) the `claims` map; see [Rule matching semantics](https://platform.claude.com/docs/en/manage-claude/wif-reference#rule-matching-semantics) for how the fields combine. Entra `sub` values for these identities are fixed-length canonical GUIDs, so a `subject_prefix` containing the full 36-character object ID matches only that subject; this is a property of Entra’s subject format, not of `subject_prefix` in general.
12651265 
12661266<Warning>
12671267 Every identity in your tenant can request a token for the registered audience, so `audience` and `tid` alone do not identify a specific workload. A rule that omits an `oid` (or `azp`/`appid`) match, or that uses a wildcard or partial-GUID `subject_prefix`, authorizes every managed identity and service principal in the tenant.
12681268</Warning>
12691269 
1270Lock the rule's `match` block to the narrowest scope that fits your use case:
1270Lock the rule’s `match` block to the narrowest scope that fits your use case:
12711271 
1272* **Match `oid` as an exact value:** Set `claims.oid` to the managed identity's full object ID. A `subject_prefix` set to that full object ID is equivalent (the Console wizard sets both); never use a wildcard or partial-GUID `subject_prefix`, which matches more identities than you intend.
1272* **Match `oid` as an exact value:** Set `claims.oid` to the managed identity’s full object ID. A `subject_prefix` set to that full object ID is equivalent (the Console wizard sets both); never use a wildcard or partial-GUID `subject_prefix`, which matches more identities than you intend.
12731273* **Pin `tid` as defense in depth:** The issuer URL already pins your tenant, but adding `claims.tid` guards against configuration drift if the issuer record is later edited.
12741274* **Pin the audience:** Set `audience` to the exact `aud` value from your decoded token so tokens minted for other applications are rejected.
1275* **Use a separate rule for each managed identity:** Create one rule for each identity rather than one rule that authorizes several, so you can revoke a single workload's access without affecting others.
1275* **Use a separate rule for each managed identity:** Create one rule for each identity rather than one rule that authorizes several, so you can revoke a single workload’s access without affecting others.
12761276 
12771277## Next steps
12781278 

manage-claude/wif-providers/gcp Changed · +9 / -9 lines

from line 4
44description: Federate Google Cloud workloads (Cloud Run, Cloud Functions, App Engine, GCE, GKE) to the Claude API using Google-signed identity tokens instead of static API keys.
55---
66 
7Any Google Cloud compute environment with access to the instance metadata server (Cloud Run, Cloud Functions, App Engine, Compute Engine (GCE), and GKE with Workload Identity) can request a Google-signed identity token for its attached service account. The token's issuer is `https://accounts.google.com`, and Anthropic can validate it directly through standard OIDC discovery, with no extra Google Cloud configuration required.
7Any Google Cloud compute environment with access to the instance metadata server (Cloud Run, Cloud Functions, App Engine, Compute Engine (GCE), and GKE with Workload Identity) can request a Google-signed identity token for its attached service account. The token’s issuer is `https://accounts.google.com`, and Anthropic can validate it directly through standard OIDC discovery, with no extra Google Cloud configuration required.
88 
99This guide shows how to register the Google issuer with Anthropic, bind a Google service account to an Anthropic service account, and have your workload exchange its identity token for a short-lived Claude API access token.
1010 
from line 59
5959 }
6060 ```
6161 
62 The `sub` claim is the Google service account's opaque numeric unique ID. The `email` claim is the human-readable service account address. Match on both `sub` and `email` in your federation rule.
62 The `sub` claim is the Google service account’s opaque numeric unique ID. The `email` claim is the human-readable service account address. Match on both `sub` and `email` in your federation rule.
6363 </Tab>
6464 
6565 <Tab title="GKE with Workload Identity">
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 such as `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>
83 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.
83 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.
8484 </Note>
8585 </Tab>
8686</Tabs>
from line 101
101101}
102102```
103103 
104**Federation rule:** Match on both the `sub` and `email` claims. `email` is the readable service-account address; `sub` is the service account's numeric unique ID, which Google never reuses, so pinning it protects the rule if the service account is deleted and a new one is later created with the same email. Find the unique ID with `gcloud iam service-accounts describe [email protected] --format='value(uniqueId)'`.
104**Federation rule:** Match on both the `sub` and `email` claims. `email` is the readable service-account address; `sub` is the service account’s numeric unique ID, which Google never reuses, so pinning it protects the rule if the service account is deleted and a new one is later created with the same email. Find the unique ID with `gcloud iam service-accounts describe [email protected] --format='value(uniqueId)'`.
105105 
106106```json
107107{
from line 453
453453 ```
454454</CodeGroup>
455455 
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.
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 470
470470## Scope your rule
471471 
472472<Warning>
473 The Google `sub` claim is the service account's opaque numeric unique ID and has no stable prefix. A `subject_prefix` with a trailing `*` matches arbitrary service accounts across every Google Cloud project, and any of them could obtain a federated Anthropic token.
473 The Google `sub` claim is the service account’s opaque numeric unique ID and has no stable prefix. A `subject_prefix` with a trailing `*` matches arbitrary service accounts across every Google Cloud project, and any of them could obtain a federated Anthropic token.
474474</Warning>
475475 
476Lock the rule's `match` block to the narrowest scope that fits your use case:
476Lock the rule’s `match` block to the narrowest scope that fits your use case:
477477 
478478* **Match `sub` exactly:** Set the full numeric unique ID in `claims.sub` and never use `subject_prefix` for Google tokens.
479479* **Pin the `email` claim:** Add `claims.email` alongside `sub` so both the stable ID and the readable address must match.
480480* **Pin the audience:** Set `audience` to the exact value you request from the metadata server so tokens minted for other consumers are rejected.
481* **Pin the project on GKE:** For `format=full` tokens, add a `condition` such as `claims.google.compute_engine.project_id == "my-project"` to restrict the rule to one project's nodes.
481* **Pin the project on GKE:** For `format=full` tokens, add a `condition` such as `claims.google.compute_engine.project_id == "my-project"` to restrict the rule to one project’s nodes.
482482 
483483## Next steps
484484 

manage-claude/wif-providers/github-actions Changed · +7 / -7 lines

from line 4
44description: Authenticate GitHub Actions workflows to the Claude API with short-lived identity tokens instead of long-lived API keys.
55---
66 
7Every 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.
7Every 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 determine 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 64
6464}
6565```
6666 
67See [GitHub's OIDC subject claim reference](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#example-subject-claims) for the full list of `sub` formats.
67See [GitHub’s OIDC subject claim reference](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#example-subject-claims) for the full list of `sub` formats.
6868 
6969## Configure Anthropic
7070 
from line 307
307307 ```
308308</CodeGroup>
309309 
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.
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 
314A successful exchange returns an `access_token` starting with `sk-ant-oat01-` and an `expires_in` value in seconds. A denied exchange returns an opaque `401` `authentication_error` with the fixed message `Authentication failed`, whichever check failed; in most cases the deny reason is recorded on the attempt's entry in the [authentication history page](https://platform.claude.com/settings/workload-identity-federation?tab=history) in the Claude Console, and [Troubleshoot a failed exchange](https://platform.claude.com/docs/en/manage-claude/wif-reference#troubleshoot-a-failed-exchange) walks the checks in order. The most common GitHub Actions-side cause is the `sub` claim format not matching (its trailing segment varies among `ref:...`, `environment:...`, and `pull_request` events); the history entry shows reason `match_subject_prefix`.
314A successful exchange returns an `access_token` starting with `sk-ant-oat01-` and an `expires_in` value in seconds. A denied exchange returns an opaque `401` `authentication_error` with the fixed message `Authentication failed`, whichever check failed; in most cases the deny reason is recorded on the attempt’s entry in the [authentication history page](https://platform.claude.com/settings/workload-identity-federation?tab=history) in the Claude Console, and [Troubleshoot a failed exchange](https://platform.claude.com/docs/en/manage-claude/wif-reference#troubleshoot-a-failed-exchange) walks the checks in order. The most common GitHub Actions-side cause is the `sub` claim format not matching (its trailing segment varies among `ref:...`, `environment:...`, and `pull_request` events); the history entry shows reason `match_subject_prefix`.
315315 
316316## Restrict which workflows can authenticate
317317 
from line 319
319319 A `subject_prefix` of `repo:your-org/*` alone matches every repository in your organization, and without a `ref` constraint it also matches `pull_request` runs triggered from forks. Anyone who can open a pull request against a matching repository could obtain a federated Anthropic token.
320320</Warning>
321321 
322Lock the rule's `match` block to the narrowest scope that fits your use case:
322Lock the rule’s `match` block to the narrowest scope that fits your use case:
323323 
324324* **Pin to a single repository:** Use `subject_prefix: "repo:your-org/your-repo:*"` so other repositories in the organization do not match.
325325* **Pin to a protected branch:** Add `"ref": "refs/heads/main"` (or your release branch) under `claims` so pull-request runs and feature branches do not match.

manage-claude/wif-providers/kubernetes Changed · +11 / -11 lines

from line 4
44description: Authenticate to the Claude API from self-managed Kubernetes clusters using projected service account tokens.
55---
66 
7Self-managed Kubernetes clusters (kubeadm, k3s, OpenShift, and on-premise distributions) sign OIDC JSON Web Tokens (JWTs) for every pod through [projected service account tokens](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#serviceaccount-token-volume-projection). The cluster's API server acts as the OIDC issuer, and each token's `sub` claim follows the form `system:serviceaccount:<namespace>:<service-account>`. You can find your cluster's issuer URL by reading its discovery document:
7Self-managed Kubernetes clusters (kubeadm, k3s, OpenShift, and on-premise distributions) sign OIDC JSON Web Tokens (JWTs) for every pod through [projected service account tokens](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#serviceaccount-token-volume-projection). The cluster’s API server acts as the OIDC issuer, and each token’s `sub` claim follows the form `system:serviceaccount:<namespace>:<service-account>`. You can find your cluster’s issuer URL by reading its discovery document:
88 
99```bash cURL
1010kubectl get --raw /.well-known/openid-configuration | jq -r .issuer
from line 18
1818 
1919* Familiarity with [WIF concepts](https://platform.claude.com/docs/en/manage-claude/workload-identity-federation#concepts): service accounts, federation issuers, and federation rules.
2020 
21* A Kubernetes cluster with the [`--service-account-issuer`](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/) flag configured on the API server. Most distributions set this by default; kubeadm clusters typically use `https://kubernetes.default.svc.cluster.local`. Your platform team can confirm the value if you don't have direct access to the API server configuration.
21* A Kubernetes cluster with the [`--service-account-issuer`](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/) flag configured on the API server. Most distributions set this by default; kubeadm clusters typically use `https://kubernetes.default.svc.cluster.local`. Your platform team can confirm the value if you don’t have direct access to the API server configuration.
2222 
2323* One of the following so Anthropic can validate token signatures:
2424 
25 * The issuer's JWKS endpoint is reachable from the public internet over HTTPS on port 443, or
25 * The issuer’s JWKS endpoint is reachable from the public internet over HTTPS on port 443, or
2626 * You can fetch the JWKS from inside the cluster and register it in `inline` mode (covered in [Configure Anthropic](https://platform.claude.com/docs/en/manage-claude/wif-providers/kubernetes#configure-anthropic)).
2727 
2828* Permission to create service accounts, federation issuers, and federation rules in the Claude Console for your Anthropic organization.
from line 75
7575 
7676The 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):
7777 
78**Federation issuer:** Many self-managed clusters use an issuer URL such as `https://kubernetes.default.svc.cluster.local` that is not reachable from the public internet. If that applies to your cluster, choose the **inline** JWKS source and paste the cluster's keys. Fetch them from inside the cluster:
78**Federation issuer:** Many self-managed clusters use an issuer URL such as `https://kubernetes.default.svc.cluster.local` that is not reachable from the public internet. If that applies to your cluster, choose the **inline** JWKS source and paste the cluster’s keys. Fetch them from inside the cluster:
7979 
8080```bash cURL
8181kubectl get --raw /openid/v1/jwks
from line 94
9494}
9595```
9696 
97In `inline` mode the `issuer_url` is only compared against the JWT's `iss` claim; Anthropic never attempts to reach it. If your issuer is publicly reachable, use `"jwks": {"type": "discovery"}` instead.
97In `inline` mode the `issuer_url` is only compared against the JWT’s `iss` claim; Anthropic never attempts to reach it. If your issuer is publicly reachable, use `"jwks": {"type": "discovery"}` instead.
9898 
9999<Warning>
100100 With `inline` keys you are responsible for updating the issuer when the cluster rotates its service account signing key. Rotation is rare (typically only during cluster upgrades), but token exchanges fail with a signature error until you push the new JWKS.
101101</Warning>
102102 
103**Federation rule:** Match the service account's `sub` claim and the audience you set on the projected token.
103**Federation rule:** Match the service account’s `sub` claim and the audience you set on the projected token.
104104 
105105```json
106106{
from line 120
120120}
121121```
122122 
123Be as specific as the workload allows. Loosen `subject_prefix` to `system:serviceaccount:inference:*` (the trailing `*` makes it a prefix match) only if every service account in the namespace should map to the same Anthropic service account. Add the rule's `fdrl_...` ID to your pod's `ANTHROPIC_FEDERATION_RULE_ID` environment variable.
123Be as specific as the workload allows. Loosen `subject_prefix` to `system:serviceaccount:inference:*` (the trailing `*` makes it a prefix match) only if every service account in the namespace should map to the same Anthropic service account. Add the rule’s `fdrl_...` ID to your pod’s `ANTHROPIC_FEDERATION_RULE_ID` environment variable.
124124 
125125## Acquire and use the token
126126 
from line 296
296296## Scope your rule
297297 
298298<Warning>
299 A `subject_prefix` of `system:serviceaccount:*` matches every service account in the cluster, so any pod can obtain a federated Anthropic token. Without an `audience` matcher, the rule also matches the cluster's default-audience tokens, which every pod already has projected.
299 A `subject_prefix` of `system:serviceaccount:*` matches every service account in the cluster, so any pod can obtain a federated Anthropic token. Without an `audience` matcher, the rule also matches the cluster’s default-audience tokens, which every pod already has projected.
300300</Warning>
301301 
302Lock the rule's `match` block to the narrowest scope that fits your use case:
302Lock the rule’s `match` block to the narrowest scope that fits your use case:
303303 
304304* **Pin namespace and service-account name:** Use the full `system:serviceaccount:<namespace>:<name>` value with no trailing `*`.
305* **Always set an audience:** Require `audience` on the rule and set the same value on the pod's `serviceAccountToken` projection so default-audience tokens are rejected.
305* **Always set an audience:** Require `audience` on the rule and set the same value on the pod’s `serviceAccountToken` projection so default-audience tokens are rejected.
306306* **Use a separate rule for each namespace:** Create a distinct rule and Anthropic service account for each namespace rather than widening one rule.
307* **Scope inline-JWKS issuers to one cluster:** When several clusters share an issuer URL, register each cluster's JWKS as its own federation issuer and bind rules to that issuer only.
307* **Scope inline-JWKS issuers to one cluster:** When several clusters share an issuer URL, register each cluster’s JWKS as its own federation issuer and bind rules to that issuer only.
308308 
309309## Next steps
310310 

manage-claude/wif-providers/okta Changed · +14 / -14 lines

from line 6
66 
77Okta can act as a workload identity provider by issuing OIDC access tokens to a **service application** through the OAuth 2.0 `client_credentials` grant. Your workload authenticates to Okta (typically with `private_key_jwt`, so no shared secret is stored), receives a signed JSON Web Token (JWT), and exchanges that JWT with Anthropic for a short-lived access token.
88 
9The Okta authorization server's issuer URL takes the form `https://<your-domain>.okta.com/oauth2/<auth-server-id>`. If you use the built-in default server, the path is `/oauth2/default`.
9The Okta authorization server’s issuer URL takes the form `https://<your-domain>.okta.com/oauth2/<auth-server-id>`. If you use the built-in default server, the path is `/oauth2/default`.
1010 
1111<Note>
1212 You must use an Okta **custom authorization server** (including the `default` one). Tokens issued directly by the Okta org authorization server (the `/oauth2/v1/token` endpoint with no authorization server ID in the path) cannot be validated by external parties because Okta does not publish signing keys for them.
1313</Note>
1414 
15There are many ways to configure and authenticate to Okta that are outside the scope of this documentation. Ensure that your configuration and authentication mechanisms follow your company's guidance and security practices.
15There are many ways to configure and authenticate to Okta that are outside the scope of this documentation. Ensure that your configuration and authentication mechanisms follow your company’s guidance and security practices.
1616 
1717## Prerequisites
1818 
from line 19
1919* Familiarity with [WIF concepts](https://platform.claude.com/docs/en/manage-claude/workload-identity-federation#concepts): service accounts, federation issuers, and federation rules.
2020* An Okta organization with API Access Management enabled (required for custom authorization servers).
2121* Permission to create service accounts, federation issuers, and federation rules in the Claude Console for your Anthropic organization.
22* A workload that can request a token from Okta's `/v1/token` endpoint and reach `api.anthropic.com`.
22* A workload that can request a token from Okta’s `/v1/token` endpoint and reach `api.anthropic.com`.
2323 
2424## Configure Okta
2525 
from line 31
3131The exact navigation depends on your Okta org configuration and admin console version. The following numbered steps walk through one common path:
3232 
33331. **Create a service app integration.** In the Okta Admin Console, create a new app integration of type **API Services** (OIDC, machine-to-machine). Note the generated **Client ID**.
342. **Configure client authentication.** For a keyless setup, select **Public key / Private key** (`private_key_jwt`) and register your workload's public JWK. Alternatively, use a client secret if your environment can store one securely. For the following example you might need to disable the DPoP requirement on the application; ensure that your production setup adheres to your organization's security requirements.
342. **Configure client authentication.** For a keyless setup, select **Public key / Private key** (`private_key_jwt`) and register your workload’s public JWK. Alternatively, use a client secret if your environment can store one securely. For the following example you might need to disable the DPoP requirement on the application; ensure that your production setup adheres to your organization’s security requirements.
35353. **Set the audience.** On your custom authorization server, set the audience to `https://api.anthropic.com` so issued access tokens carry that `aud` claim. Anthropic validates `aud` against this fixed value.
36364. **Grant a scope.** On your custom authorization server, ensure at least one scope exists that the service app is allowed to request (for example, `anthropic.access`). Okta rejects `client_credentials` requests that do not include a granted scope.
37375. **Create an access policy.** On your custom authorization server, create an access policy with at least one rule that allows your service app to request the scope you granted in step 4.
386. **(Optional) Add custom claims.** If you want to match on something other than the client ID, add a claim to the access token in your authorization server's **Claims** tab.
386. **(Optional) Add custom claims.** If you want to match on something other than the client ID, add a claim to the access token in your authorization server’s **Claims** tab.
3939 
40For a service app using `client_credentials`, Okta sets the `sub` claim of the issued access token to the application's **Client ID**, and `iss` to the authorization server's issuer URL.
40For a service app using `client_credentials`, Okta sets the `sub` claim of the issued access token to the application’s **Client ID**, and `iss` to the authorization server’s issuer URL.
4141 
4242## Configure Anthropic
4343 
from line 45
4545 
4646The 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):
4747 
48**Federation issuer:** Use your Okta custom authorization server URL and discovery mode. Anthropic reads Okta's `.well-known/openid-configuration` discovery document and fetches the JWKS from the `jwks_uri` it advertises.
48**Federation issuer:** Use your Okta custom authorization server URL and discovery mode. Anthropic reads Okta’s `.well-known/openid-configuration` discovery document and fetches the JWKS from the `jwks_uri` it advertises.
4949 
5050```json
5151{
from line 55
5555}
5656```
5757 
58**Federation rule:** Match on the Okta `sub` claim, which is the service app's Client ID. If you defined custom claims in Okta, you can match on those instead with the `claims` map or a CEL `condition`.
58**Federation rule:** Match on the Okta `sub` claim, which is the service app’s Client ID. If you defined custom claims in Okta, you can match on those instead with the `claims` map or a CEL `condition`.
5959 
6060```json
6161{
from line 74
7474 
7575## Acquire a token and call the Claude API
7676 
77Unlike platform-native providers (AWS, Google Cloud, Kubernetes), which make a token available inside the workload's runtime (through a projected file or local metadata endpoint), Okta does not. Your workload must call Okta's token endpoint to obtain a JWT, then pass that JWT to the Anthropic SDK as the identity token.
77Unlike platform-native providers (AWS, Google Cloud, Kubernetes), which make a token available inside the workload’s runtime (through a projected file or local metadata endpoint), Okta does not. Your workload must call Okta’s token endpoint to obtain a JWT, then pass that JWT to the Anthropic SDK as the identity token.
7878 
7979<CodeGroup>
8080 ```bash cURL
from line 504
504504 Multiple service apps under the same Okta authorization server share the same issuer. A rule that omits `subject_prefix` matches every service app on that server, so any team that can register one could obtain a federated Anthropic token.
505505</Warning>
506506 
507Lock the rule's `match` block to the narrowest scope that fits your use case:
507Lock the rule’s `match` block to the narrowest scope that fits your use case:
508508 
509* **Pin the exact Client ID:** Set `subject_prefix` to the service app's full Client ID with no trailing `*`.
509* **Pin the exact Client ID:** Set `subject_prefix` to the service app’s full Client ID with no trailing `*`.
510510* **Pin the audience:** Match the `audience` value you configured on the authorization server so tokens minted for a different audience are rejected.
511* **Match on custom claims:** For finer-grained scoping, add claims in the authorization server's **Claims** tab and match them with the rule's `claims` map or a CEL `condition`.
511* **Match on custom claims:** For finer-grained scoping, add claims in the authorization server’s **Claims** tab and match them with the rule’s `claims` map or a CEL `condition`.
512512* **Use one rule for each service app:** Create a separate federation rule for each service app rather than sharing one rule across apps.
513513 
514514## Next steps

manage-claude/wif-providers/spiffe Changed · +30 / -30 lines

from line 8
88 
99Federation works either through an OIDC discovery document at a public HTTPS URL (`discovery` mode, subject to the [URL constraints](https://platform.claude.com/docs/en/manage-claude/wif-reference#url-fields)) or by registering the JWKS directly (`inline` mode).
1010 
11The JWT-SVID spec defines `sub` as the workload's SPIFFE ID, and the SPIFFE Workload API requires the caller to supply `aud` at fetch time, so those claims are the same across implementations. Anthropic additionally requires `iss` and `iat`, neither of which the JWT-SVID spec mandates, so configure your implementation to populate both (in SPIRE, `iss` is the `jwt_issuer` server setting and `iat` is set automatically). With those in place, the [Configure Anthropic](https://platform.claude.com/docs/en/manage-claude/wif-providers/spiffe#configure-anthropic), [Acquire and use the token](https://platform.claude.com/docs/en/manage-claude/wif-providers/spiffe#acquire-and-use-the-token), and [Scope your rule](https://platform.claude.com/docs/en/manage-claude/wif-providers/spiffe#scope-your-rule) sections of this guide apply to any SPIFFE implementation.
11The JWT-SVID spec defines `sub` as the workload’s SPIFFE ID, and the SPIFFE Workload API requires the caller to supply `aud` at fetch time, so those claims are the same across implementations. Anthropic additionally requires `iss` and `iat`, neither of which the JWT-SVID spec mandates, so configure your implementation to populate both (in SPIRE, `iss` is the `jwt_issuer` server setting and `iat` is set automatically). With those in place, the [Configure Anthropic](https://platform.claude.com/docs/en/manage-claude/wif-providers/spiffe#configure-anthropic), [Acquire and use the token](https://platform.claude.com/docs/en/manage-claude/wif-providers/spiffe#acquire-and-use-the-token), and [Scope your rule](https://platform.claude.com/docs/en/manage-claude/wif-providers/spiffe#scope-your-rule) sections of this guide apply to any SPIFFE implementation.
1212 
13SPIFFE assigns every workload a stable identity URI of the form `spiffe://<trust-domain>/<path>`, and SPIRE issues that identity as a JWT-SVID on demand through the Workload API. A JWT-SVID is an ordinary signed JWT whose `sub` claim is the workload's SPIFFE ID and whose `aud` claim is supplied by the workload at fetch time.
13SPIFFE assigns every workload a stable identity URI of the form `spiffe://<trust-domain>/<path>`, and SPIRE issues that identity as a JWT-SVID on demand through the Workload API. A JWT-SVID is an ordinary signed JWT whose `sub` claim is the workload’s SPIFFE ID and whose `aud` claim is supplied by the workload at fetch time.
1414 
15The bridge from a SPIRE trust domain to standard OIDC is the [SPIRE OIDC Discovery Provider](https://github.com/spiffe/spire/blob/main/support/oidc-discovery-provider/README.md), a standalone helper that publishes `/.well-known/openid-configuration` and a JWKS endpoint for the trust domain's JWT signing keys. With the discovery provider running, a JWT-SVID validates like any other OIDC token: register the discovery URL as a federation issuer, write a federation rule that matches the workload's SPIFFE ID, and have the workload present its JWT-SVID to Anthropic's token-exchange endpoint.
15The bridge from a SPIRE trust domain to standard OIDC is the [SPIRE OIDC Discovery Provider](https://github.com/spiffe/spire/blob/main/support/oidc-discovery-provider/README.md), a standalone helper that publishes `/.well-known/openid-configuration` and a JWKS endpoint for the trust domain’s JWT signing keys. With the discovery provider running, a JWT-SVID validates like any other OIDC token: register the discovery URL as a federation issuer, write a federation rule that matches the workload’s SPIFFE ID, and have the workload present its JWT-SVID to Anthropic’s token-exchange endpoint.
1616 
17This page's examples use SPIRE and apply anywhere SPIRE Agent runs: Kubernetes pods, virtual machines, and bare-metal hosts.
17This page’s examples use SPIRE and apply anywhere SPIRE Agent runs: Kubernetes pods, virtual machines, and bare-metal hosts.
1818 
1919<Note>
20 If your Kubernetes cluster does not run SPIRE and you want to authenticate with the cluster's native projected service-account tokens instead, see [Use WIF with Kubernetes](https://platform.claude.com/docs/en/manage-claude/wif-providers/kubernetes).
20 If your Kubernetes cluster does not run SPIRE and you want to authenticate with the cluster’s native projected service-account tokens instead, see [Use WIF with Kubernetes](https://platform.claude.com/docs/en/manage-claude/wif-providers/kubernetes).
2121</Note>
2222 
2323## Prerequisites
from line 25
2525* Familiarity with [WIF concepts](https://platform.claude.com/docs/en/manage-claude/workload-identity-federation#concepts): service accounts, federation issuers, and federation rules.
2626* A SPIFFE deployment with workload identities issued (the examples on this page use SPIRE Server and Agent), and registration entries for the workloads that need to call the Claude API.
2727* An OIDC discovery endpoint for the trust domain (in SPIRE, the [OIDC Discovery Provider](https://github.com/spiffe/spire/blob/main/support/oidc-discovery-provider/README.md)) running with a publicly reachable HTTPS endpoint, or the JWKS exported for `inline` registration.
28* Your SPIFFE issuer configured to set the `iss` claim on JWT-SVIDs to the value you will register as the federation issuer's `issuer_url`. For `discovery` mode, this is the discovery endpoint's public URL (in SPIRE, the `jwt_issuer` server setting).
28* Your SPIFFE issuer configured to set the `iss` claim on JWT-SVIDs to the value you will register as the federation issuer’s `issuer_url`. For `discovery` mode, this is the discovery endpoint’s public URL (in SPIRE, the `jwt_issuer` server setting).
2929* JWT-SVIDs available to your workloads. WIF accepts JWT-SVIDs only, not X.509-SVIDs.
3030* Permission to create service accounts, federation issuers, and federation rules in the Claude Console for your Anthropic organization.
3131 
32The audience value to request when fetching a JWT-SVID is always `https://api.anthropic.com`. Use this value in spiffe-helper's `jwt_audience`, the Workload API `FetchJWTSVID` call, and the federation rule's `audience` matcher.
32The audience value to request when fetching a JWT-SVID is always `https://api.anthropic.com`. Use this value in spiffe-helper’s `jwt_audience`, the Workload API `FetchJWTSVID` call, and the federation rule’s `audience` matcher.
3333 
3434## Configure SPIRE
3535 
from line 49
4949 
5050### Verify the JWT issuer
5151 
52Anthropic validates a JWT-SVID by matching its `iss` claim against a registered federation issuer and fetching the JWKS from that issuer's discovery document. Two SPIRE settings must agree on the same URL: SPIRE Server's `jwt_issuer` (which becomes the `iss` claim in every minted JWT-SVID) and the OIDC Discovery Provider's `domains` list (which determines the host the discovery document and JWKS are served from). That shared URL is what you register with Anthropic.
52Anthropic validates a JWT-SVID by matching its `iss` claim against a registered federation issuer and fetching the JWKS from that issuer’s discovery document. Two SPIRE settings must agree on the same URL: SPIRE Server’s `jwt_issuer` (which becomes the `iss` claim in every minted JWT-SVID) and the OIDC Discovery Provider’s `domains` list (which determines the host the discovery document and JWKS are served from). That shared URL is what you register with Anthropic.
5353 
5454The trust domain and the issuer URL are independent. The trust domain (`spiffe://prod.example.com`) scopes the `sub` claim. The issuer URL (`https://oidc-discovery.prod.example.com`) is where Anthropic fetches signing keys. They do not need to share a hostname.
5555 
56Confirm `jwt_issuer` is set in SPIRE Server's configuration and points at the discovery provider's public URL. The following example also shows a default JWT-SVID lifetime. SPIRE's built-in default is 5 minutes, which is short enough that continuous rotation is required (see [Run spiffe-helper](https://platform.claude.com/docs/en/manage-claude/wif-providers/spiffe#run-spiffe-helper)). Anthropic's token-exchange endpoint rejects any identity token whose lifetime exceeds the federation issuer's configured maximum, which is 1 hour by default (see [Validation rules](https://platform.claude.com/docs/en/manage-claude/wif-reference#validation-rules)). This check applies to every SPIFFE implementation, not only SPIRE, so keep `default_jwt_svid_ttl` (or any per-entry override) at or below that maximum.
56Confirm `jwt_issuer` is set in SPIRE Server’s configuration and points at the discovery provider’s public URL. The following example also shows a default JWT-SVID lifetime. SPIRE’s built-in default is 5 minutes, which is short enough that continuous rotation is required (see [Run spiffe-helper](https://platform.claude.com/docs/en/manage-claude/wif-providers/spiffe#run-spiffe-helper)). Anthropic’s token-exchange endpoint rejects any identity token whose lifetime exceeds the federation issuer’s configured maximum, which is 1 hour by default (see [Validation rules](https://platform.claude.com/docs/en/manage-claude/wif-reference#validation-rules)). This check applies to every SPIFFE implementation, not only SPIRE, so keep `default_jwt_svid_ttl` (or any per-entry override) at or below that maximum.
5757 
5858```text server.conf
5959server {
from line 64
6464}
6565```
6666 
67In the OIDC Discovery Provider's configuration, the same hostname must appear under `domains`, and the provider must be able to reach SPIRE Server's API socket. The provider serves the discovery document and JWKS over HTTPS. Terminate TLS with its built-in ACME support, or front it with a load balancer that does.
67In the OIDC Discovery Provider’s configuration, the same hostname must appear under `domains`, and the provider must be able to reach SPIRE Server’s API socket. The provider serves the discovery document and JWKS over HTTPS. Terminate TLS with its built-in ACME support, or front it with a load balancer that does.
6868 
6969```text oidc-discovery-provider.conf
7070domains = ["oidc-discovery.prod.example.com"]
from line 80
8080```
8181 
8282<Note>
83 The example uses `server_api`, which connects the discovery provider to SPIRE Server's privileged API socket. The provider also accepts a `workload_api` block (with `socket_path` and `trust_domain`) that obtains the bundle through a SPIRE Agent's Workload API instead. Use it when the discovery provider should not have access to the Server API or runs on a node that cannot reach the Server.
83 The example uses `server_api`, which connects the discovery provider to SPIRE Server’s privileged API socket. The provider also accepts a `workload_api` block (with `socket_path` and `trust_domain`) that obtains the bundle through a SPIRE Agent’s Workload API instead. Use it when the discovery provider should not have access to the Server API or runs on a node that cannot reach the Server.
8484</Note>
8585 
8686### Register the workload
8787 
88Each workload that calls the Claude API needs a SPIRE registration entry that maps its runtime selectors to a SPIFFE ID. If the workload is already registered, note its SPIFFE ID, which you use in the federation rule's `subject_prefix`. If not, register it. For a Kubernetes pod, the selectors are typically the namespace and Kubernetes service account:
88Each workload that calls the Claude API needs a SPIRE registration entry that maps its runtime selectors to a SPIFFE ID. If the workload is already registered, note its SPIFFE ID, which you use in the federation rule’s `subject_prefix`. If not, register it. For a Kubernetes pod, the selectors are typically the namespace and Kubernetes service account:
8989 
9090```bash CLI
9191# Replace NODE_UID with the node's UID:
from line 98
9898```
9999 
100100<Note>
101 The `parentID` shown is a single node's auto-generated agent ID. For cluster-wide registration, parent the entry to a [node alias](https://spiffe.io/docs/latest/deploying/registering/#mapping-workloads-to-multiple-nodes) so it matches workloads on every node, as the [SPIRE Kubernetes quickstart](https://spiffe.io/docs/latest/try/getting-started-k8s/) does.
101 The `parentID` shown is a single node’s auto-generated agent ID. For cluster-wide registration, parent the entry to a [node alias](https://spiffe.io/docs/latest/deploying/registering/#mapping-workloads-to-multiple-nodes) so it matches workloads on every node, as the [SPIRE Kubernetes quickstart](https://spiffe.io/docs/latest/try/getting-started-k8s/) does.
102102</Note>
103103 
104Workloads outside Kubernetes use host-level selectors such as `unix:uid:1000` (`unix:path` is also available but requires `discover_workload_path = true` in the agent's unix workload attestor configuration). Clusters running [spire-controller-manager](https://github.com/spiffe/spire-controller-manager) can declare entries with the `ClusterSPIFFEID` custom resource instead of calling `spire-server entry create` directly.
104Workloads outside Kubernetes use host-level selectors such as `unix:uid:1000` (`unix:path` is also available but requires `discover_workload_path = true` in the agent’s unix workload attestor configuration). Clusters running [spire-controller-manager](https://github.com/spiffe/spire-controller-manager) can declare entries with the `ClusterSPIFFEID` custom resource instead of calling `spire-server entry create` directly.
105105 
106106### Run spiffe-helper
107107 
from line 119
119119}]
120120```
121121 
122In Kubernetes, run spiffe-helper as a sidecar container that shares a memory-backed `emptyDir` volume (`medium: Memory`) with your application container so the bearer SVID never lands on the node's disk. Mount the SPIRE Agent socket from the host into the sidecar, mount the shared volume at `/var/run/secrets/anthropic.com` in both containers, and set `ANTHROPIC_IDENTITY_TOKEN_FILE=/var/run/secrets/anthropic.com/token` on the application container. On VMs and bare metal, run spiffe-helper as a system service alongside the workload and point both at a shared directory.
122In Kubernetes, run spiffe-helper as a sidecar container that shares a memory-backed `emptyDir` volume (`medium: Memory`) with your application container so the bearer SVID never lands on the node’s disk. Mount the SPIRE Agent socket from the host into the sidecar, mount the shared volume at `/var/run/secrets/anthropic.com` in both containers, and set `ANTHROPIC_IDENTITY_TOKEN_FILE=/var/run/secrets/anthropic.com/token` on the application container. On VMs and bare metal, run spiffe-helper as a system service alongside the workload and point both at a shared directory.
123123 
124124## Configure Anthropic
125125 
from line 127
127127 
128128The 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):
129129 
130**Federation issuer:** Register the OIDC Discovery Provider's public URL in `discovery` mode. Anthropic fetches `/.well-known/openid-configuration` from this URL and follows the returned `jwks_uri` to retrieve the trust domain's signing keys.
130**Federation issuer:** Register the OIDC Discovery Provider’s public URL in `discovery` mode. Anthropic fetches `/.well-known/openid-configuration` from this URL and follows the returned `jwks_uri` to retrieve the trust domain’s signing keys.
131131 
132132```json
133133{
from line 137
137137}
138138```
139139 
140If the discovery provider is not reachable from the public internet, fetch the JWKS yourself (`curl https://oidc-discovery.prod.example.com/keys`) and register the issuer with `"jwks": {"type": "inline", "keys": [...]}` using the contents of the returned `keys` array. In `inline` mode the `issuer_url` is only compared against the JWT-SVID's `iss` claim. Anthropic never attempts to reach it.
140If the discovery provider is not reachable from the public internet, fetch the JWKS yourself (`curl https://oidc-discovery.prod.example.com/keys`) and register the issuer with `"jwks": {"type": "inline", "keys": [...]}` using the contents of the returned `keys` array. In `inline` mode the `issuer_url` is only compared against the JWT-SVID’s `iss` claim. Anthropic never attempts to reach it.
141141 
142142<Warning>
143143 SPIRE rotates JWT signing keys frequently, by default on the same cadence as the CA (`ca_ttl`, 24 hours). If you register the issuer with an inline JWKS instead of a discovery URL, you must update the JWKS every time SPIRE rotates: add the new key before workloads start presenting it, and **remove superseded keys** once tokens signed with them have expired. Stale keys left in an inline JWKS remain trusted indefinitely.
144144</Warning>
145145 
146To automate JWKS updates without exposing a public discovery endpoint, configure a SPIRE Server [BundlePublisher](https://spiffe.io/docs/latest/deploying/spire_server/#built-in-plugins) plugin (`aws_s3`, `gcp_cloudstorage`, or `k8s_configmap`) with `format = "jwks"` to push the JWT signing keys to external storage on every rotation, then update the issuer's inline keys through the [Admin API](https://platform.claude.com/docs/en/manage-claude/wif-admin-api#federation-issuers).
146To automate JWKS updates without exposing a public discovery endpoint, configure a SPIRE Server [BundlePublisher](https://spiffe.io/docs/latest/deploying/spire_server/#built-in-plugins) plugin (`aws_s3`, `gcp_cloudstorage`, or `k8s_configmap`) with `format = "jwks"` to push the JWT signing keys to external storage on every rotation, then update the issuer’s inline keys through the [Admin API](https://platform.claude.com/docs/en/manage-claude/wif-admin-api#federation-issuers).
147147 
148**Federation rule:** Match the JWT-SVID's `sub` (the SPIFFE ID) and the `aud` you configured spiffe-helper to request. SPIFFE IDs are URI strings and `subject_prefix` matches them as opaque text, so an exact value or a trailing-`*` prefix match both work against them. For more complex patterns, use a CEL `condition`.
148**Federation rule:** Match the JWT-SVID’s `sub` (the SPIFFE ID) and the `aud` you configured spiffe-helper to request. SPIFFE IDs are URI strings and `subject_prefix` matches them as opaque text, so an exact value or a trailing-`*` prefix match both work against them. For more complex patterns, use a CEL `condition`.
149149 
150150```json
151151{
from line 167
167167 
168168`token_lifetime_seconds` is the lifetime of the Anthropic access token the exchange returns, not of the JWT-SVID. The SDK refreshes the access token automatically.
169169 
170Be as specific as the workload allows. Loosen `subject_prefix` to `spiffe://prod.example.com/ns/inference/*` only if every workload registered under that path should map to the same Anthropic service account. Add the rule's `fdrl_...` ID to the workload's `ANTHROPIC_FEDERATION_RULE_ID` environment variable.
170Be as specific as the workload allows. Loosen `subject_prefix` to `spiffe://prod.example.com/ns/inference/*` only if every workload registered under that path should map to the same Anthropic service account. Add the rule’s `fdrl_...` ID to the workload’s `ANTHROPIC_FEDERATION_RULE_ID` environment variable.
171171 
172172## Acquire and use the token
173173 
174The Claude SDK can read the JWT-SVID from the file that spiffe-helper maintains or, except in Java, call the SPIFFE Workload API directly through a token provider. The file path is the simplest integration and works in every SDK language. The token-provider path removes the sidecar but requires a SPIFFE Workload API client in your application's language.
174The Claude SDK can read the JWT-SVID from the file that spiffe-helper maintains or, except in Java, call the SPIFFE Workload API directly through a token provider. The file path is the simplest integration and works in every SDK language. The token-provider path removes the sidecar but requires a SPIFFE Workload API client in your application’s language.
175175 
176176<Tabs>
177177 <Tab title="File-based with spiffe-helper">
from line 444
444444 </CodeGroup>
445445 
446446 <Note>
447 For other languages, fetch the JWT-SVID with your runtime's SPIFFE Workload API client (or shell out to `spire-agent api fetch jwt`), write it to a file, and set `ANTHROPIC_IDENTITY_TOKEN_FILE` to that path as in the file-based tab.
447 For other languages, fetch the JWT-SVID with your runtime’s SPIFFE Workload API client (or shell out to `spire-agent api fetch jwt`), write it to a file, and set `ANTHROPIC_IDENTITY_TOKEN_FILE` to that path as in the file-based tab.
448448 </Note>
449449 </Tab>
450450</Tabs>
from line 454
454454Before integrating the SDK, fetch a JWT-SVID directly from SPIRE Agent and confirm the claims match what your federation rule expects. If you use a different SPIFFE implementation, fetch a JWT-SVID with its CLI or Workload API client and decode the payload the same way.
455455 
456456<Note>
457 The Workload API attests the calling process. For a Kubernetes registration entry, run this command inside a pod that satisfies the entry's selectors and has the agent socket mounted (for example, by using `kubectl exec`). On VMs and bare metal, run it as the user or process that matches the entry's `unix:` selectors. Running from an unattested host shell returns `no identity issued`, which is the most common verify-step failure.
457 The Workload API attests the calling process. For a Kubernetes registration entry, run this command inside a pod that satisfies the entry’s selectors and has the agent socket mounted (for example, by using `kubectl exec`). On VMs and bare metal, run it as the user or process that matches the entry’s `unix:` selectors. Running from an unattested host shell returns `no identity issued`, which is the most common verify-step failure.
458458</Note>
459459 
460460```bash CLI
from line 466
466466 | jq -rR 'split(".")[1] | gsub("-";"+") | gsub("_";"/") | @base64d | fromjson'
467467```
468468 
469The `-output json` flag returns the SVID response and bundle response as a two-element JSON array, so `jq -r '.[0].svids[0].svid'` extracts the bare token. On older SPIRE versions without `-output`, the command prints a labeled block instead. In that case, pipe the default output through `awk '/^[[:space:]]*eyJ/{print $1; exit}'` to extract the token line. Check that `iss` is the OIDC Discovery Provider URL you registered, `sub` is the workload's SPIFFE ID, and `aud` contains `https://api.anthropic.com`. Then run the cURL example from [Acquire and use the token](https://platform.claude.com/docs/en/manage-claude/wif-providers/spiffe#acquire-and-use-the-token). A successful exchange returns an `access_token` starting with `sk-ant-oat01-`. 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 and see [Troubleshoot a failed exchange](https://platform.claude.com/docs/en/manage-claude/wif-reference#troubleshoot-a-failed-exchange). The most common SPIRE-side cause is a mismatch between SPIRE Server's `jwt_issuer` and the URL registered as the federation issuer.
469The `-output json` flag returns the SVID response and bundle response as a two-element JSON array, so `jq -r '.[0].svids[0].svid'` extracts the bare token. On older SPIRE versions without `-output`, the command prints a labeled block instead. In that case, pipe the default output through `awk '/^[[:space:]]*eyJ/{print $1; exit}'` to extract the token line. Check that `iss` is the OIDC Discovery Provider URL you registered, `sub` is the workload’s SPIFFE ID, and `aud` contains `https://api.anthropic.com`. Then run the cURL example from [Acquire and use the token](https://platform.claude.com/docs/en/manage-claude/wif-providers/spiffe#acquire-and-use-the-token). A successful exchange returns an `access_token` starting with `sk-ant-oat01-`. 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 and see [Troubleshoot a failed exchange](https://platform.claude.com/docs/en/manage-claude/wif-reference#troubleshoot-a-failed-exchange). The most common SPIRE-side cause is a mismatch between SPIRE Server’s `jwt_issuer` and the URL registered as the federation issuer.
470470 
471471## Scope your rule
472472 
473SPIFFE ID path conventions are operator-defined, so the federation rule's `subject_prefix` matcher should reflect the path scheme your registration entries use. Common schemes include `spiffe://<trust-domain>/ns/<namespace>/sa/<service-account>` (the default emitted by the `ClusterSPIFFEID` resource in spire-controller-manager) and `spiffe://<trust-domain>/host/<hostname>/<service>` for VM and bare-metal workloads.
473SPIFFE ID path conventions are operator-defined, so the federation rule’s `subject_prefix` matcher should reflect the path scheme your registration entries use. Common schemes include `spiffe://<trust-domain>/ns/<namespace>/sa/<service-account>` (the default emitted by the `ClusterSPIFFEID` resource in spire-controller-manager) and `spiffe://<trust-domain>/host/<hostname>/<service>` for VM and bare-metal workloads.
474474 
475475<Warning>
476476 A `subject_prefix` of `spiffe://prod.example.com/*` matches every workload in the trust domain. Without an `audience` matcher, the rule also accepts JWT-SVIDs minted for any audience, including ones the workload requested for unrelated relying parties.
477477</Warning>
478478 
479Lock the rule's `match` block to the narrowest scope that fits your use case:
479Lock the rule’s `match` block to the narrowest scope that fits your use case:
480480 
481481* **Pin to one workload:** Set `subject_prefix` to the full SPIFFE ID with no trailing `*`.
482482* **Always set an audience:** Require `audience` on the rule and configure spiffe-helper (or the Workload API call) with the same value so SVIDs minted for other relying parties are rejected.

manage-claude/wif-reference Changed · +22 / -22 lines

from line 17
1717| `federation_rule_id` | Yes | Tagged ID (`fdrl_...`) of the federation rule to evaluate. |
1818| `organization_id` | Yes | UUID of your Anthropic organization. |
1919| `service_account_id` | Yes | Tagged ID (`svac_...`) of the target service account. |
20| `workspace_id` | Conditional | Tagged ID (`wrkspc_...`) of the workspace to scope the minted token to. Required when the rule is enabled for more than one workspace. When omitted, the server selects the rule's sole enabled workspace. The literal `default` also works for the organization's Default Workspace but is deprecated; use that workspace's `wrkspc_...` ID. |
20| `workspace_id` | Conditional | Tagged ID (`wrkspc_...`) of the workspace to scope the minted token to. Required when the rule is enabled for more than one workspace. When omitted, the server selects the rule’s sole enabled workspace. The literal `default` also works for the organization’s Default Workspace but is deprecated; use that workspace’s `wrkspc_...` ID. |
2121 
2222## Token exchange response
2323 
from line 125
125125 
126126| Scope | Grants access to |
127127| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
128| `workspace:developer` | All non-administrative Claude API endpoints in the rule's workspace: [Messages](https://platform.claude.com/docs/en/api/messages) (including streaming and token counting), [Models](https://platform.claude.com/docs/en/api/models/list), [Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview) and their sessions, [Files](https://platform.claude.com/docs/en/build-with-claude/files), and [Skills](https://platform.claude.com/docs/en/build-with-claude/skills-guide). This matches the access a workspace API key in the same workspace has. |
129| `workspace:inference` | The inference endpoints in the rule's workspace: [Messages](https://platform.claude.com/docs/en/api/messages) (including streaming and token counting), [Models](https://platform.claude.com/docs/en/api/models/list), and the [OpenAI-compatible chat endpoint](https://platform.claude.com/docs/en/cli-sdks-libraries/libraries/openai-sdk). Use this for workloads that only need to call Claude and never need to manage Files, Skills, or other resources. |
130| `workspace:manage_tunnels` | The [MCP tunnels API](https://platform.claude.com/docs/en/agents-and-tools/mcp-tunnels/reference#tunnels-api): create, list, and get tunnels, register and archive CA certificates, reveal and rotate the tunnel token, and archive tunnels. The Console's create-tunnel modal window locks this scope when you create a rule from it. |
128| `workspace:developer` | All non-administrative Claude API endpoints in the rule’s workspace: [Messages](https://platform.claude.com/docs/en/api/messages) (including streaming and token counting), [Models](https://platform.claude.com/docs/en/api/models/list), [Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview) and their sessions, [Files](https://platform.claude.com/docs/en/build-with-claude/files), and [Skills](https://platform.claude.com/docs/en/build-with-claude/skills-guide). This matches the access a workspace API key in the same workspace has. |
129| `workspace:inference` | The inference endpoints in the rule’s workspace: [Messages](https://platform.claude.com/docs/en/api/messages) (including streaming and token counting), [Models](https://platform.claude.com/docs/en/api/models/list), and the [OpenAI-compatible chat endpoint](https://platform.claude.com/docs/en/cli-sdks-libraries/libraries/openai-sdk). Use this for workloads that only need to call Claude and never need to manage Files, Skills, or other resources. |
130| `workspace:manage_tunnels` | The [MCP tunnels API](https://platform.claude.com/docs/en/agents-and-tools/mcp-tunnels/reference#tunnels-api): create, list, and get tunnels, register and archive CA certificates, reveal and rotate the tunnel token, and archive tunnels. The Console’s create-tunnel modal window locks this scope when you create a rule from it. |
131131| `org:admin` | Full access to the [Admin API](https://platform.claude.com/docs/en/manage-claude/admin-api) (organization members, invites, workspaces, API keys, and the rest). An OAuth `org:admin` token can only create or modify rules scoped to `workspace:developer` or `workspace:inference`, and cannot update an issuer that backs a rule with any other scope; see [Permissions and constraints](https://platform.claude.com/docs/en/manage-claude/wif-admin-api#permissions-and-constraints). |
132132 
133A request to an endpoint outside the token's scope returns HTTP 403. Finer-grained scopes (by resource, or read versus write) are not available.
133A request to an endpoint outside the token’s scope returns HTTP 403. Finer-grained scopes (by resource, or read versus write) are not available.
134134 
135135### Permission boundaries
136136 
137A federation rule's `oauth_scope` is a ceiling: the minted token can never exceed it. The target service account's `organization_role` (`developer` or `admin`) determines which scopes are grantable, so a rule that grants `org:admin` must target a service account with `organization_role=admin`. Effective permissions are the intersection of the rule's scope and the service account's role.
137A federation rule’s `oauth_scope` is a ceiling: the minted token can never exceed it. The target service account’s `organization_role` (`developer` or `admin`) determines which scopes are grantable, so a rule that grants `org:admin` must target a service account with `organization_role=admin`. Effective permissions are the intersection of the rule’s scope and the service account’s role.
138138 
139139| Rule `oauth_scope` | Service account `organization_role` | Effective permissions |
140140| --------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
141| `workspace:developer` | `admin` | Claude API access in the rule's workspace only. The scope caps the token below the role. |
141| `workspace:developer` | `admin` | Claude API access in the rule’s workspace only. The scope caps the token below the role. |
142142| `org:admin` | `admin` | Full Admin API access (organization members, invites, workspaces, API keys, and the rest), minus the OAuth-caller exceptions; see [Permissions and constraints](https://platform.claude.com/docs/en/manage-claude/wif-admin-api#permissions-and-constraints). |
143143 
144144## Validation rules
from line 178
178178| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
179179| Maximum size | The `assertion` JWT must be at most 16 KiB. |
180180| Signing algorithm | Only asymmetric algorithms (RSA and ECDSA families: ES256, ES384, ES512, RS256, RS384, RS512, PS256, PS384, PS512) are accepted. HMAC (`HS256`, `HS384`, `HS512`) and `none` are rejected. |
181| Key ID | The JWT header must carry a `kid` that matches a key in the issuer's JWKS. Tokens without `kid` are rejected. |
181| Key ID | The JWT header must carry a `kid` that matches a key in the issuer’s JWKS. Tokens without `kid` are rejected. |
182182| Required claims | `sub` must be present. `iat` must be present and not in the future. `exp` must be present and in the future. |
183| Single use | An assertion that carries a `jti` claim can be exchanged only once per issuer: repeating an exchange with the same `jti` is rejected as a replay. The issuer's `check_jti` field (enabled by default) controls this check; assertions without a `jti` claim are not subject to it. See the [Federation issuers API reference](https://platform.claude.com/docs/en/api/organization/federation/issuers). |
184| Maximum lifetime | The token's lifetime (`exp` minus `iat`) must not exceed the issuer's configured maximum (1 hour by default, configurable for each issuer in the Claude Console). |
183| Single use | An assertion that carries a `jti` claim can be exchanged only once per issuer: repeating an exchange with the same `jti` is rejected as a replay. The issuer’s `check_jti` field (enabled by default) controls this check; assertions without a `jti` claim are not subject to it. See the [Federation issuers API reference](https://platform.claude.com/docs/en/api/organization/federation/issuers). |
184| Maximum lifetime | The token’s lifetime (`exp` minus `iat`) must not exceed the issuer’s configured maximum (1 hour by default, configurable for each issuer in the Claude Console). |
185185| Clock skew | A 30-second leeway is applied to `exp`, `nbf`, and `iat`. |
186186 
187187## Rule matching semantics
188188 
189A federation rule's `match` block determines whether an incoming JWT is accepted. All populated fields are evaluated with AND semantics: the JWT must satisfy every populated matcher. At least one of `subject_prefix`, `claims`, or `condition` must be set; a `match` block that contains only `audience` (or no matchers at all) is rejected. This guards against rules that would accept every token from an issuer.
189A federation rule’s `match` block determines whether an incoming JWT is accepted. All populated fields are evaluated with AND semantics: the JWT must satisfy every populated matcher. At least one of `subject_prefix`, `claims`, or `condition` must be set; a `match` block that contains only `audience` (or no matchers at all) is rejected. This guards against rules that would accept every token from an issuer.
190190 
191191| Matcher | Type | Semantics |
192192| ---------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
from line 217
217217 
218218### Token exchange errors
219219 
220`POST /v1/oauth/token` returns errors in the standard [API error shape](https://platform.claude.com/docs/en/api/errors). The SDK reports an exchange failure as `WorkloadIdentityError` (ruby: `Anthropic::Credentials::WorkloadIdentityError`; go: `error`; java: `UnexpectedStatusCodeException`; csharp: `WorkloadIdentityException`; php: `OAuthException`), which gives the HTTP status and the error details from the response body. The request ID is on the error in Python, TypeScript, and Ruby, and in the exception's response headers in Java. In Go it is only in the error's message, because the error's type is in an internal package; the C# and PHP errors do not carry it.
220`POST /v1/oauth/token` returns errors in the standard [API error shape](https://platform.claude.com/docs/en/api/errors). The SDK reports an exchange failure as `WorkloadIdentityError` (ruby: `Anthropic::Credentials::WorkloadIdentityError`; go: `error`; java: `UnexpectedStatusCodeException`; csharp: `WorkloadIdentityException`; php: `OAuthException`), which gives the HTTP status and the error details from the response body. The request ID is on the error in Python, TypeScript, and Ruby, and in the exception’s response headers in Java. In Go it is only in the error’s message, because the error’s type is in an internal package; the C# and PHP errors do not carry it.
221221 
222222| Status | Error | Cause | Resolution |
223223| ------ | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
from line 226
226226| 401 | `authentication_error` | The JWT `iss` claim does not equal the registered `issuer_url` exactly. | Compare byte-for-byte, including trailing slashes and scheme: `jq -rR 'split(".")[1] \| gsub("-";"+") \| gsub("_";"/") \| @base64d \| fromjson \| .iss' <<< "$JWT"`. |
227227| 401 | `authentication_error` | JWKS fetch failed, JWKS is stale, or the JWT was signed with a key not in the JWKS. | For `inline` mode, update the issuer with the rotated keys. For `discovery` and `explicit_url`, confirm the JWKS endpoint is reachable on port 443; if the issuer recently rotated its signing key, see [Key rotation and caching](https://platform.claude.com/docs/en/manage-claude/wif-reference#key-rotation-and-caching). |
228228| 401 | `authentication_error` | The JWT `exp` claim is in the past (beyond the 30-second skew window). | Confirm your identity provider is projecting a fresh token and the SDK is re-reading the token file. |
229| 401 | `authentication_error` | The JWT was verified but its claims do not satisfy the rule's `match` block. | Decode the JWT and compare each claim against the rule. `subject_prefix` is case-sensitive. `audience` requires an exact element match. |
229| 401 | `authentication_error` | The JWT was verified but its claims do not satisfy the rule’s `match` block. | Decode the JWT and compare each claim against the rule. `subject_prefix` is case-sensitive. `audience` requires an exact element match. |
230230| 401 | `authentication_error` | The `federation_rule_id` does not exist or is archived, or the JWT is not authorized for it (consolidated to prevent enumeration). | Confirm the rule ID in the Claude Console and that the rule has not been archived. |
231231| 401 | `authentication_error` | The federation rule is enabled for more than one workspace and the request omits `workspace_id`. The authentication history entry shows reason `workspace_id_required`. | Set `ANTHROPIC_WORKSPACE_ID` (or the `workspace_id` body field on a raw request) to the `wrkspc_...` ID you want the token scoped to. See [Token exchange request](https://platform.claude.com/docs/en/manage-claude/wif-reference#token-exchange-request). |
232232 
233Every assertion denial returns the same opaque `401` `authentication_error` with the fixed message `Authentication failed`, regardless of which check failed; a distinguishable error would let a caller probe rule configuration. The deny reason is recorded on the attempt's entry in the [authentication history](https://platform.claude.com/settings/workload-identity-federation?tab=history) in the Claude Console, for example `match_subject_prefix` when the `sub` claim fails the rule's `subject_prefix`, or `workspace_id_required` when the rule spans multiple workspaces and the request names none. Requests rejected before the rule's organization is corroborated (the `400 invalid_request_error` family in the previous table) leave no history entry. Their response messages name the problem directly. A `401` with no matching history entry usually means the `federation_rule_id` itself was not recognized.
233Every assertion denial returns the same opaque `401` `authentication_error` with the fixed message `Authentication failed`, regardless of which check failed; a distinguishable error would let a caller probe rule configuration. The deny reason is recorded on the attempt’s entry in the [authentication history](https://platform.claude.com/settings/workload-identity-federation?tab=history) in the Claude Console, for example `match_subject_prefix` when the `sub` claim fails the rule’s `subject_prefix`, or `workspace_id_required` when the rule spans multiple workspaces and the request names none. Requests rejected before the rule’s organization is corroborated (the `400 invalid_request_error` family in the previous table) leave no history entry. Their response messages name the problem directly. A `401` with no matching history entry usually means the `federation_rule_id` itself was not recognized.
234234 
235235### Common SDK-side failures
236236 
237237| Symptom | Cause | Resolution |
238238| ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
239| SDK reports "no credentials" instead of exchanging | One of `ANTHROPIC_FEDERATION_RULE_ID`, `ANTHROPIC_ORGANIZATION_ID`, `ANTHROPIC_SERVICE_ACCOUNT_ID`, or `ANTHROPIC_IDENTITY_TOKEN[_FILE]` is unset and no profile is active. | Set all four variables, or configure a profile. |
239| SDK reports “no credentials” instead of exchanging | One of `ANTHROPIC_FEDERATION_RULE_ID`, `ANTHROPIC_ORGANIZATION_ID`, `ANTHROPIC_SERVICE_ACCOUNT_ID`, or `ANTHROPIC_IDENTITY_TOKEN[_FILE]` is unset and no profile is active. | Set all four variables, or configure a profile. |
240240| SDK authenticates with an API key instead of federating | `ANTHROPIC_API_KEY` or `ANTHROPIC_AUTH_TOKEN` is set and wins precedence. | Unset the key or token variable. |
241241| `FileNotFoundError` on first request | The path in `ANTHROPIC_IDENTITY_TOKEN_FILE` does not exist. The SDK opens the file lazily at exchange time. | Confirm the projected-token volume is mounted and the path matches. |
242| Token exchange succeeds but a Claude API request returns 403 | The minted token's scope does not grant access to that endpoint. | Check the rule's `oauth_scope` against [OAuth scopes](https://platform.claude.com/docs/en/manage-claude/wif-reference#oauth-scopes). |
242| Token exchange succeeds but a Claude API request returns 403 | The minted token’s scope does not grant access to that endpoint. | Check the rule’s `oauth_scope` against [OAuth scopes](https://platform.claude.com/docs/en/manage-claude/wif-reference#oauth-scopes). |
243243| Authentication fails with empty credential | A credential environment variable is exported but set to an empty string. Empty values can still win their precedence slot. | Unset the variable with `unset VAR` rather than `VAR=""`. |
244244 
245245## Troubleshoot a failed exchange
from line 268
268268 </Step>
269269 
270270 <Step title="Check aud matches the rule">
271 The decoded `aud` claim must contain the rule's `audience` value as an exact match. When `aud` is an array, one element must match exactly.
271 The decoded `aud` claim must contain the rule’s `audience` value as an exact match. When `aud` is an array, one element must match exactly.
272272 </Step>
273273 
274274 <Step title="Check sub and each claims entry">
275 Compare `sub` against the rule's `subject_prefix` (case-sensitive; a trailing `*` is a prefix match, and anything else is exact). Compare every key in the rule's `claims` map against the same-named top-level claim.
275 Compare `sub` against the rule’s `subject_prefix` (case-sensitive; a trailing `*` is a prefix match, and anything else is exact). Compare every key in the rule’s `claims` map against the same-named top-level claim.
276276 </Step>
277277 
278278 <Step title="Check exp, nbf, and iat">
279 `exp` must be in the future and `nbf`/`iat` must be in the past, within the 30-second skew window. If the workload host's clock has drifted, an otherwise valid token is rejected.
279 `exp` must be in the future and `nbf`/`iat` must be in the past, within the 30-second skew window. If the workload host’s clock has drifted, an otherwise valid token is rejected.
280280 </Step>
281281 
282282 <Step title="Check JWKS reachability">
283 For `discovery` mode, fetch `<jwks.discovery_base or issuer_url>/.well-known/openid-configuration` over public HTTPS on port 443 and confirm `jwks_uri` resolves. For `explicit_url`, fetch the JWKS URL directly. For `inline`, confirm the issuer's signing key has not rotated since you registered the keys.
283 For `discovery` mode, fetch `<jwks.discovery_base or issuer_url>/.well-known/openid-configuration` over public HTTPS on port 443 and confirm `jwks_uri` resolves. For `explicit_url`, fetch the JWKS URL directly. For `inline`, confirm the issuer’s signing key has not rotated since you registered the keys.
284284 
285 If the issuer rotated its signing key and immediately started signing with it, exchanges can fail for up to a minute while Anthropic's JWKS cache refreshes. See [Key rotation and caching](https://platform.claude.com/docs/en/manage-claude/wif-reference#key-rotation-and-caching).
285 If the issuer rotated its signing key and immediately started signing with it, exchanges can fail for up to a minute while Anthropic’s JWKS cache refreshes. See [Key rotation and caching](https://platform.claude.com/docs/en/manage-claude/wif-reference#key-rotation-and-caching).
286286 </Step>
287287</Steps>
288288 

manage-claude/workload-identity-federation Changed · +15 / -15 lines

from line 12
1212 
1313## Concepts
1414 
15You configure three resources in the Claude Console before any workload can federate. Together they express "tokens signed by issuer X, with claims that look like Y, may act as service account Z."
15You configure three resources in the Claude Console before any workload can federate. Together they express “tokens signed by issuer X, with claims that look like Y, may act as service account Z.”
1616 
1717### Service accounts
1818 
19A **service account** (`svac_...`) is a named, non-human identity inside your Anthropic organization. It is the principal that a [service account key](https://platform.claude.com/docs/en/manage-claude/authentication#key-types) or a federated token acts as. Service accounts live at the organization level and become active in a workspace when you add them as members of that workspace. At exchange time, Anthropic checks that the federation rule's workspace matches one of the service account's workspace memberships; the minted token then follows that workspace's rate limits and usage attribution, the same as an API key. Unlike a human user, a service account has no email, no password, and no Console login. Every service account is implicitly a member of your organization's default workspace; add explicit memberships for any other workspace it should act in. To let an all-workspaces service account key act in a workspace, add the service account to that workspace.
19A **service account** (`svac_...`) is a named, non-human identity inside your Anthropic organization. It is the principal that a [service account key](https://platform.claude.com/docs/en/manage-claude/authentication#key-types) or a federated token acts as. Service accounts live at the organization level and become active in a workspace when you add them as members of that workspace. At exchange time, Anthropic checks that the federation rule’s workspace matches one of the service account’s workspace memberships; the minted token then follows that workspace’s rate limits and usage attribution, the same as an API key. Unlike a human user, a service account has no email, no password, and no Console login. Every service account is implicitly a member of your organization’s default workspace; add explicit memberships for any other workspace it should act in. To let an all-workspaces service account key act in a workspace, add the service account to that workspace.
2020 
2121The key distinction versus a workspace API key: a workspace API key *is* a credential, whereas a service account *has* credentials. You can more easily audit which workloads acted as which service account.
2222 
2323### Federation issuers
2424 
25A **federation issuer** (`fdis_...`) registers an OIDC identity provider with your organization. Registering an issuer tells Anthropic "JWTs signed by this provider may assert workload identity for my org."
25A **federation issuer** (`fdis_...`) registers an OIDC identity provider with your organization. Registering an issuer tells Anthropic “JWTs signed by this provider may assert workload identity for my org.”
2626 
2727An issuer has two pieces of configuration:
2828 
29* **Issuer URL:** The exact `iss` claim value that appears in the provider's JWTs, for example `https://token.actions.githubusercontent.com` or `https://oidc.eks.us-west-2.amazonaws.com/id/EXAMPLE`.
29* **Issuer URL:** The exact `iss` claim value that appears in the provider’s JWTs, for example `https://token.actions.githubusercontent.com` or `https://oidc.eks.us-west-2.amazonaws.com/id/EXAMPLE`.
3030* **JWKS source:** How Anthropic fetches the public keys to verify JWT signatures. Use `discovery` (the default) for any provider that serves `/.well-known/openid-configuration` at its issuer URL. Use `explicit_url` to point at a JWKS endpoint directly, or `inline` to upload the key set for issuers that are not reachable from the public internet (for example, a private Kubernetes cluster).
3131 
3232Issuer and JWKS URLs must be `https`, on port 443, and use a public DNS hostname that resolves to public IP addresses; IP literals are not accepted. These constraints apply only to URLs Anthropic fetches; in `explicit_url` and `inline` modes the `issuer_url` is compared as a string and may reference an internal hostname.
from line 35
3535 
3636### Federation rules
3737 
38A **federation rule** (`fdrl_...`) is the bridge between an issuer and a service account: "when a JWT from issuer X has claims that look like Y, mint a token for service account Z with scope S."
38A **federation rule** (`fdrl_...`) is the bridge between an issuer and a service account: “when a JWT from issuer X has claims that look like Y, mint a token for service account Z with scope S.”
3939 
4040A rule defines match conditions, a target, and the authorization scope and token lifetime that apply when the rule matches:
4141 
from line 43
4343* **Target:** The service account the matched JWT maps to.
4444* **Authorization:** The OAuth `scope` granted on the minted token. The default is `workspace:developer`, which grants the same access as a workspace API key. Some products lock the scope when you create a rule from their flow; for example, the [MCP tunnels](https://platform.claude.com/docs/en/agents-and-tools/mcp-tunnels/overview) create-tunnel modal creates rules scoped to `workspace:manage_tunnels`. See [OAuth scopes](https://platform.claude.com/docs/en/manage-claude/wif-reference#oauth-scopes). The rule also sets `token_lifetime_seconds` (60 to 86400, default 3600).
4545 
46A single issuer can have many rules: one for each team, namespace, or permission level. Rules are evaluated by ID: the client specifies which rule to use in the exchange request, and Anthropic verifies the JWT satisfies that rule's match criteria. There is no implicit rule search.
46A single issuer can have many rules: one for each team, namespace, or permission level. Rules are evaluated by ID: the client specifies which rule to use in the exchange request, and Anthropic verifies the JWT satisfies that rule’s match criteria. There is no implicit rule search.
4747 
4848## How it works
4949 
501. **Your IdP issues a JWT to the workload.** On most platforms this is ambient: a Kubernetes projected service-account token, the Google Cloud metadata server, Azure IMDS, or the GitHub Actions OIDC endpoint. The JWT's `iss` claim identifies the provider, and its `sub` and other claims identify the specific workload.
512. **The SDK exchanges the JWT for an Anthropic access token.** The SDK posts the JWT to `POST /v1/oauth/token` using the [RFC 7523](https://www.rfc-editor.org/rfc/rfc7523) `jwt-bearer` grant. Anthropic verifies the JWT against the issuer's JWKS and the federation rule's match conditions, then returns a short-lived `sk-ant-oat01-...` token that acts on behalf of the rule's target service account.
501. **Your IdP issues a JWT to the workload.** On most platforms this is ambient: a Kubernetes projected service-account token, the Google Cloud metadata server, Azure IMDS, or the GitHub Actions OIDC endpoint. The JWT’s `iss` claim identifies the provider, and its `sub` and other claims identify the specific workload.
512. **The SDK exchanges the JWT for an Anthropic access token.** The SDK posts the JWT to `POST /v1/oauth/token` using the [RFC 7523](https://www.rfc-editor.org/rfc/rfc7523) `jwt-bearer` grant. Anthropic verifies the JWT against the issuer’s JWKS and the federation rule’s match conditions, then returns a short-lived `sk-ant-oat01-...` token that acts on behalf of the rule’s target service account.
52523. **The SDK sends the token on every request and refreshes it before it expires.** Your application code constructs the client with no API key and calls the API as usual. The SDK re-runs the exchange before the token expires.
5353 
5454## Set up federation
from line 63
6363 </Step>
6464 
6565 <Step title="Choose your provider">
66 Select the tile for your identity provider: GitHub Actions, AWS, Google Cloud, Microsoft Entra ID, or Kubernetes. Each tile prefills the issuer URL pattern and the match fields that provider's JWTs support. For any other standards-compliant provider (such as SPIFFE or Okta), select **Custom OIDC**.
66 Select the tile for your identity provider: GitHub Actions, AWS, Google Cloud, Microsoft Entra ID, or Kubernetes. Each tile prefills the issuer URL pattern and the match fields that provider’s JWTs support. For any other standards-compliant provider (such as SPIFFE or Okta), select **Custom OIDC**.
6767 </Step>
6868 
6969 <Step title="Fill in the guided fields">
from line 75
7575 </Step>
7676 
7777 <Step title="Test the connection">
78 The wizard creates the issuer, service account, and federation rule, then listens for a successful token exchange for 15 minutes. Trigger an exchange from your workload within that window (see [Authenticate from your workload](https://platform.claude.com/docs/en/manage-claude/workload-identity-federation#authenticate-from-your-workload)) to confirm the setup works. If the window elapses, the resources persist; you can re-run the test from the federation rule's detail page. Note the rule's ID (`fdrl_...`) and the service account ID (`svac_...`) the wizard creates: your workload passes both, along with your organization ID (and your workspace ID when the rule covers more than one workspace), in every token-exchange request.
78 The wizard creates the issuer, service account, and federation rule, then listens for a successful token exchange for 15 minutes. Trigger an exchange from your workload within that window (see [Authenticate from your workload](https://platform.claude.com/docs/en/manage-claude/workload-identity-federation#authenticate-from-your-workload)) to confirm the setup works. If the window elapses, the resources persist; you can re-run the test from the federation rule’s detail page. Note the rule’s ID (`fdrl_...`) and the service account ID (`svac_...`) the wizard creates: your workload passes both, along with your organization ID (and your workspace ID when the rule covers more than one workspace), in every token-exchange request.
7979 </Step>
8080</Steps>
8181 
from line 346
346346Every SDK resolves credentials in the same five-tier order: constructor arguments, then `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN`, then an explicit `ANTHROPIC_PROFILE`, then the federation environment variables, then the implicit active profile. The first source that yields a credential wins.
347347 
348348<Warning>
349 `ANTHROPIC_API_KEY` sits above the federation tiers, so a leftover key in the environment silently shadows federation. When migrating a workload from API keys to Workload Identity Federation, confirm `ANTHROPIC_API_KEY` is unset everywhere that workload runs (container env, CI secrets, shell profiles). The CLI's [`ant auth status`](https://platform.claude.com/docs/en/cli-sdks-libraries/cli/authentication#check-authentication-status) command reports which source won.
349 `ANTHROPIC_API_KEY` sits above the federation tiers, so a leftover key in the environment silently shadows federation. When migrating a workload from API keys to Workload Identity Federation, confirm `ANTHROPIC_API_KEY` is unset everywhere that workload runs (container env, CI secrets, shell profiles). The CLI’s [`ant auth status`](https://platform.claude.com/docs/en/cli-sdks-libraries/cli/authentication#check-authentication-status) command reports which source won.
350350</Warning>
351351 
352352For the full precedence table, the per-tier semantics, and the profile file schema, see [Credential precedence in the WIF reference](https://platform.claude.com/docs/en/manage-claude/wif-reference#credential-precedence).
from line 355
355355 
356356To switch an existing workload from a static API key to federation without downtime:
357357 
3581. **Configure federation in parallel.** Complete the [setup walkthrough](https://platform.claude.com/docs/en/manage-claude/workload-identity-federation#set-up-federation) and confirm the federation rule matches your workload's token. Leave the existing `ANTHROPIC_API_KEY` in place for now.
3581. **Configure federation in parallel.** Complete the [setup walkthrough](https://platform.claude.com/docs/en/manage-claude/workload-identity-federation#set-up-federation) and confirm the federation rule matches your workload’s token. Leave the existing `ANTHROPIC_API_KEY` in place for now.
3593592. **Smoke-test which credential wins.** Run `ant auth status` from inside the workload (or inspect SDK debug logs). Because `ANTHROPIC_API_KEY` sits above the federation tiers in the precedence chain, the API key still wins at this stage.
3603603. **Unset `ANTHROPIC_API_KEY` everywhere it is injected.** Remove it from CI secrets, container environment, and shell profiles (see the preceding warning). Re-run `ant auth status` and confirm the federation source now wins.
3613614. **Delete the API key.** Once the workload is running on the federated token, delete the key on the [API keys](https://platform.claude.com/settings/keys) page (`platform.claude.com/settings/keys`) in the Claude Console.
from line 362
362362 
363363## Token lifetime and refresh
364364 
365The minted Anthropic token's lifetime is the lesser of (a) the rule's `token_lifetime_seconds` (default 3,600 seconds) and (b) twice the remaining lifetime of the IdP JWT you presented. The result is never less than 60 seconds. The second bound prevents an Anthropic token from outliving the upstream identity it was derived from by more than a small margin.
365The minted Anthropic token’s lifetime is the lesser of (a) the rule’s `token_lifetime_seconds` (default 3,600 seconds) and (b) twice the remaining lifetime of the IdP JWT you presented. The result is never less than 60 seconds. The second bound prevents an Anthropic token from outliving the upstream identity it was derived from by more than a small margin.
366366 
367367The SDK caches the token and refreshes it on a two-tier schedule modeled on `botocore`:
368368 
from line 371
371371 
372372Because the SDK re-reads `ANTHROPIC_IDENTITY_TOKEN_FILE` on every exchange, it transparently picks up rotated projected tokens (Kubernetes service-account tokens, for example, rotate well before their `exp`).
373373 
374By default, identity tokens that carry a `jti` claim are single-use: each exchange must present a JWT that has not been exchanged before, and re-presenting one fails with the reason `jti_reused` on the [authentication history page](https://platform.claude.com/settings/workload-identity-federation?tab=history) in the Claude Console. If your workload fetches its own tokens from your identity provider, mint a fresh JWT for each exchange instead of reusing a cached one (retry loops are the usual cause of reuse). The same applies to a token read from `ANTHROPIC_IDENTITY_TOKEN_FILE`: the SDK re-reads the file on every exchange, so the file must hold a new token before each refresh. A refresh that re-reads an unrotated token, or a restarted process that re-presents a token it already exchanged, is rejected the same way. Rotating the token well within the minted token's lifetime keeps the file ahead of the refresh schedule. If your token source cannot rotate that often, you can disable `check_jti` for that issuer as a last resort. This removes replay protection for every rule on the issuer. See [JWT verification](https://platform.claude.com/docs/en/manage-claude/wif-reference#jwt-verification) for details.
374By default, identity tokens that carry a `jti` claim are single-use: each exchange must present a JWT that has not been exchanged before, and re-presenting one fails with the reason `jti_reused` on the [authentication history page](https://platform.claude.com/settings/workload-identity-federation?tab=history) in the Claude Console. If your workload fetches its own tokens from your identity provider, mint a fresh JWT for each exchange instead of reusing a cached one (retry loops are the usual cause of reuse). The same applies to a token read from `ANTHROPIC_IDENTITY_TOKEN_FILE`: the SDK re-reads the file on every exchange, so the file must hold a new token before each refresh. A refresh that re-reads an unrotated token, or a restarted process that re-presents a token it already exchanged, is rejected the same way. Rotating the token well within the minted token’s lifetime keeps the file ahead of the refresh schedule. If your token source cannot rotate that often, you can disable `check_jti` for that issuer as a last resort. This removes replay protection for every rule on the issuer. See [JWT verification](https://platform.claude.com/docs/en/manage-claude/wif-reference#jwt-verification) for details.
375375 
376376## Identity providers
377377 
from line 411
411411 
412412* [Manage WIF with the Admin API](https://platform.claude.com/docs/en/manage-claude/wif-admin-api): create issuers, service accounts, and rules from infrastructure as code
413413* [WIF reference](https://platform.claude.com/docs/en/manage-claude/wif-reference): environment variables, profile file schema, validation rules, and error codes
414* [Authentication](https://platform.claude.com/docs/en/manage-claude/authentication): all the SDK's authentication options
414* [Authentication](https://platform.claude.com/docs/en/manage-claude/authentication): all the SDK’s authentication options
415415* [Admin API reference](https://platform.claude.com/docs/en/api/beta/organization): generated request and response schemas for every Admin API endpoint
416416 

manage-claude/workspaces Changed · +39 / -39 lines

from line 13
1313Key characteristics:
1414 
1515* **Workspace identifiers** use the `wrkspc_` prefix (for example, `wrkspc_01JwQvzr7rXLA5AGx3HKfFUJ`).
16* **Maximum 100 workspaces** for each organization by default (archived workspaces don't count). Contact your account team if you need more.
17* **Default Workspace** has a `wrkspc_` ID like any other workspace (returned in the [`anthropic-workspace-id` response header](https://platform.claude.com/docs/en/manage-claude/workspaces#identify-the-workspace-behind-an-api-response) and accepted by [Get Workspace](https://platform.claude.com/docs/en/api/organization/workspaces/retrieve)), but it appears in [List Workspaces](https://platform.claude.com/docs/en/api/organization/workspaces/list) results only when you pass `include_default=true`, and usage reports and cost reports show `null` for its `workspace_id` (an API key's `scope` field carries the real ID for a key bound to the Default Workspace).
18* **API keys** can be scoped to a single workspace. In this case, they can only access resources within that workspace. Other API keys aren't scoped to a workspace. With one of these keys, send a [workspace ID header](https://platform.claude.com/docs/en/manage-claude/authentication#select-a-workspace) on each request to choose the workspace. The key's user or service account must have access to that workspace.
16* **Maximum 100 workspaces** for each organization by default (archived workspaces don’t count). Contact your account team if you need more.
17* **Default Workspace** has a `wrkspc_` ID like any other workspace (returned in the [`anthropic-workspace-id` response header](https://platform.claude.com/docs/en/manage-claude/workspaces#identify-the-workspace-behind-an-api-response) and accepted by [Get Workspace](https://platform.claude.com/docs/en/api/organization/workspaces/retrieve)), but it appears in [List Workspaces](https://platform.claude.com/docs/en/api/organization/workspaces/list) results only when you pass `include_default=true`, and usage reports and cost reports show `null` for its `workspace_id` (an API key’s `scope` field carries the real ID for a key bound to the Default Workspace).
18* **API keys** can be scoped to a single workspace. In this case, they can only access resources within that workspace. Other API keys aren’t scoped to a workspace. With one of these keys, send a [workspace ID header](https://platform.claude.com/docs/en/manage-claude/authentication#select-a-workspace) on each request to choose the workspace. The key’s user or service account must have access to that workspace.
1919 
2020### Claude Code workspace
2121 
from line 25
2525 
2626* Claude Code mints a per-user API key in this workspace at sign-in. You cannot create keys in it manually from the Console.
2727* A Claude Code key stops working if its owner is removed from the workspace or organization, unlike a workspace key.
28* Claude Code usage is rate-limited separately, and admins can cap its share of the organization's limits under [Rate limits in the Claude Console](https://platform.claude.com/usage/limits).
28* Claude Code usage is rate-limited separately, and admins can cap its share of the organization’s limits under [Rate limits in the Claude Console](https://platform.claude.com/usage/limits).
2929* It is the only workspace that supports per-user monthly spend limits.
3030 
3131<Warning>
from line 36
3636 
3737Members can have different roles in each workspace, allowing fine-grained access control.
3838 
39| Role | Permissions |
40| --------------------------- | ----------------------------------------------------------------------------------------------- |
41| Workspace User | Use playground only |
42| Workspace Limited Developer | Create and manage API keys, use the API. Cannot access session tracing views or download files. |
43| Workspace Developer | Create and manage API keys, use the API |
44| Workspace Admin | Full control over workspace settings and members |
45| Workspace Billing | View workspace billing information (inherited from organization billing role) |
39| Role | Permissions |
40| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
41| Workspace User | Use playground only |
42| Workspace Limited Developer | Create and manage API keys, use the API<br />For [Claude Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview) in the Claude Console, members with this role cannot open or start sessions, open memory stores, or manage deployments. They also cannot download or delete files on the Console’s [Files](https://platform.claude.com/files) page. These limits apply only to the Console’s interface, not to the API. |
43| Workspace Developer | Create and manage API keys, use the API |
44| Workspace Admin | Full control over workspace settings and members |
45| Workspace Billing | View workspace billing information (inherited from organization billing role) |
4646 
4747### Role inheritance
4848 
from line 49
4949* **Organization admins** automatically receive Workspace Admin access to all workspaces.
5050* **Organization billing members** automatically receive Workspace Billing access to all workspaces.
5151* **Organization users and developers** must be explicitly added to each workspace.
52* **Service accounts** are added to workspaces on the [Service accounts](https://platform.claude.com/settings/service-accounts) page (`platform.claude.com/settings/service-accounts`) in the Claude Console: select **Add to workspace** in the account's menu or on its own page. To see the accounts in one workspace, filter the list by workspace.
52* **Service accounts** are added to workspaces on the [Service accounts](https://platform.claude.com/settings/service-accounts) page (`platform.claude.com/settings/service-accounts`) in the Claude Console: select **Add to workspace** in the account’s menu or on its own page. To see the accounts in one workspace, filter the list by workspace.
5353 
5454<Note>
55 The Workspace Billing role cannot be manually assigned. It's inherited from having the organization billing role.
55 The Workspace Billing role cannot be manually assigned. It’s inherited from having the organization billing role.
5656</Note>
5757 
5858## Managing workspaces
from line 91
9191 
9292#### Edit workspace details
9393 
94To modify a workspace's name or color:
94To modify a workspace’s name or color:
9595 
96961. Select the workspace from the list.
97972. Open the **More actions** menu (three dots) and select **Edit details**.
from line 111
111111#### Remove members from a workspace
112112 
1131131. On the [Members](https://platform.claude.com/settings/members) page in the Claude Console, select the workspace from the **Workspace** dropdown menu. If it shows **All**, **Remove member** removes the member from your organization instead.
1142. At the end of the member's row, open the **More actions** menu (three dots) and select **Remove member**.
1142. At the end of the member’s row, open the **More actions** menu (three dots) and select **Remove member**.
1151153. Click **Remove** to confirm.
116116 
117117<Note>
from line 120
120120 
121121#### Set workspace limits
122122 
123You set a workspace's rate limits and spend limits in different places:
123You set a workspace’s rate limits and spend limits in different places:
124124 
125125* **Rate limits:** Under [Rate limits in the Claude Console](https://platform.claude.com/usage/limits), select the workspace from the **Workspace** dropdown menu and click **Edit** next to a model to set limits for requests per minute, input tokens, or output tokens.
126126* **Spend limits:** On the [Workspaces](https://platform.claude.com/settings/workspaces) page in the Claude Console, select the workspace. In the panel that opens, click **Spend limits** to cap monthly spending and configure alerts when spending reaches certain thresholds.
from line 134
134134* Cannot be undone
135135 
136136<Warning>
137 Archiving a workspace archives every API key created for that workspace within seconds (they remain listed in the Admin API as archived), and keys that aren't scoped to a workspace can no longer act in it. This action cannot be undone. If you archive the [Claude Code workspace](https://platform.claude.com/docs/en/manage-claude/workspaces#claude-code-workspace), members of your organization can no longer sign in to Claude Code through Console billing.
137 Archiving a workspace archives every API key created for that workspace within seconds (they remain listed in the Admin API as archived), and keys that aren’t scoped to a workspace can no longer act in it. This action cannot be undone. If you archive the [Claude Code workspace](https://platform.claude.com/docs/en/manage-claude/workspaces#claude-code-workspace), members of your organization can no longer sign in to Claude Code through Console billing.
138138</Warning>
139139 
140140### Using the Admin API
from line 142
142142Programmatically manage workspaces using the [Admin API](https://platform.claude.com/docs/en/manage-claude/admin-api).
143143 
144144<Note>
145 Admin API endpoints accept an [Admin API key](https://platform.claude.com/docs/en/manage-claude/admin-api-keys), an `org:admin` OAuth token, or a personal or service account key that isn't scoped to a specific workspace. Workspace keys don't work there. See [Authentication](https://platform.claude.com/docs/en/manage-claude/admin-api#authentication).
145 Admin API endpoints accept an [Admin API key](https://platform.claude.com/docs/en/manage-claude/admin-api-keys), an `org:admin` OAuth token, or a personal or service account key that isn’t scoped to a specific workspace. Workspace keys don’t work there. See [Authentication](https://platform.claude.com/docs/en/manage-claude/admin-api#authentication).
146146</Note>
147147 
148148The following SDK and CLI examples construct the default client, which reads the Admin API key from the `ANTHROPIC_API_KEY` environment variable; the SDK exposes these endpoints under `client.organization.workspaces` (csharp, go: `client.Organization.Workspaces`; java: `client.organization().workspaces()`; php: `$client->organization->workspaces`). SDK list methods fetch further pages on demand, so `limit` sets the page size; the PHP, Ruby, and curl examples return one page.
from line 581
581581 ```
582582</CodeGroup>
583583 
584Update a member's role:
584Update a member’s role:
585585 
586586<CodeGroup>
587587 ```bash cURL
from line 826
826826Every request runs in exactly one workspace and can only access resources within that workspace. Which workspace depends on the [key type](https://platform.claude.com/docs/en/manage-claude/authentication#key-types):
827827 
828828* A **workspace key** (a legacy key without an owner) belongs to the workspace it was created in and always runs there.
829* A **personal key** or **service account key** acts as its user or service account. A single-workspace key always runs in the workspace chosen when it was created. A key that isn't scoped to a workspace runs in the workspace named by each request's `anthropic-workspace-id` header. Accounts must have access to the workspace to use it.
829* A **personal key** or **service account key** acts as its user or service account. A single-workspace key always runs in the workspace chosen when it was created. A key that isn’t scoped to a workspace runs in the workspace named by each request’s `anthropic-workspace-id` header. Accounts must have access to the workspace to use it.
830830 
831831Resources scoped to workspaces include:
832832 
from line 837
837837Some resources are managed differently:
838838 
839839* **[MCP tunnels](https://platform.claude.com/docs/en/agents-and-tools/mcp-tunnels/overview)** are managed with a `workspace:manage_tunnels` OAuth token obtained through [Workload Identity Federation](https://platform.claude.com/docs/en/manage-claude/workload-identity-federation), not an API key. Tunnels are created in a workspace, and the Console **MCP tunnels** list and the Managed Agent server picker show tunnels in the current workspace only; the cap of 10 active tunnels applies organization-wide. Tunnel management requires a role with tunnel management permissions; organization developers can view but not change them.
840* **Workspaces** themselves and **organization members** are managed at the organization level through the [Admin API](https://platform.claude.com/docs/en/manage-claude/admin-api), using an Admin API key, an `org:admin` OAuth token, or a personal or service account key that isn't scoped to a specific workspace.
840* **Workspaces** themselves and **organization members** are managed at the organization level through the [Admin API](https://platform.claude.com/docs/en/manage-claude/admin-api), using an Admin API key, an `org:admin` OAuth token, or a personal or service account key that isn’t scoped to a specific workspace.
841841 
842To look up your organization's workspace IDs, call the [List Workspaces](https://platform.claude.com/docs/en/api/organization/workspaces/list) endpoint (pass `include_default=true` to include the Default Workspace) or find them on the [Workspaces](https://platform.claude.com/settings/workspaces) page in the Claude Console.
842To look up your organization’s workspace IDs, call the [List Workspaces](https://platform.claude.com/docs/en/api/organization/workspaces/list) endpoint (pass `include_default=true` to include the Default Workspace) or find them on the [Workspaces](https://platform.claude.com/settings/workspaces) page in the Claude Console.
843843 
844844<Note>
845845 [Prompt caches](https://platform.claude.com/docs/en/build-with-claude/prompt-caching) are also isolated by workspace on the Claude API, [Claude Platform on AWS](https://platform.claude.com/docs/en/build-with-claude/claude-platform-on-aws), and [Microsoft Foundry](https://platform.claude.com/docs/en/build-with-claude/claude-in-microsoft-foundry). On Amazon Bedrock and Claude Models on Google Cloud (Google managed), prompt caches are isolated by organization.
from line 847
847847 
848848## Identify the workspace behind an API response
849849 
850Claude API responses include an `anthropic-workspace-id` header alongside the `request-id` and `anthropic-organization-id` [response headers](https://platform.claude.com/docs/en/api/overview#response-headers). Its value is the `wrkspc_`-prefixed ID of the workspace that the request's API key or access token resolved to, including when that workspace is the Default Workspace. For example, a successful response includes headers like these:
850Claude API responses include an `anthropic-workspace-id` header alongside the `request-id` and `anthropic-organization-id` [response headers](https://platform.claude.com/docs/en/api/overview#response-headers). Its value is the `wrkspc_`-prefixed ID of the workspace that the request’s API key or access token resolved to, including when that workspace is the Default Workspace. For example, a successful response includes headers like these:
851851 
852852```http
853853HTTP/1.1 200 OK
from line 856
856856anthropic-workspace-id: wrkspc_01JwQvzr7rXLA5AGx3HKfFUJ
857857```
858858 
859The header is absent when the credential doesn't resolve to a workspace (for example, on Admin API requests) or when the request fails before authentication completes, such as a 401 error.
859The header is absent when the credential doesn’t resolve to a workspace (for example, on Admin API requests) or when the request fails before authentication completes, such as a 401 error.
860860 
861861The following examples send a Messages API request and read the workspace ID from the response headers:
862862 
from line 1009
10091009 
10101010With the workspace ID from a response, you can:
10111011 
1012* Confirm which workspace's usage, cost, and [rate limits](https://platform.claude.com/docs/en/api/rate-limits) the request counted toward
1012* Confirm which workspace’s usage, cost, and [rate limits](https://platform.claude.com/docs/en/api/rate-limits) the request counted toward
10131013* Match it against the `workspace_id` field in [Usage and Cost API](https://platform.claude.com/docs/en/manage-claude/usage-cost-api) reports, which is `null` for the Default Workspace. Also match it against the `scope` field of API keys in the [Admin API](https://platform.claude.com/docs/en/manage-claude/admin-api), which carries the real ID of the workspace a key is bound to, including the Default Workspace
1014* Check whether it's your Default Workspace's ID by passing it to [Get Workspace](https://platform.claude.com/docs/en/api/organization/workspaces/retrieve) with an [Admin API key](https://platform.claude.com/docs/en/manage-claude/admin-api-keys): the Default Workspace comes back with `"name": "Default"`, even though [List Workspaces](https://platform.claude.com/docs/en/api/organization/workspaces/list) omits it unless you pass `include_default=true`
1015* Open that workspace in the [Console](https://platform.claude.com/settings/workspaces) to find the request's resources, such as sessions, files, message batches, and skills
1014* Check whether it’s your Default Workspace’s ID by passing it to [Get Workspace](https://platform.claude.com/docs/en/api/organization/workspaces/retrieve) with an [Admin API key](https://platform.claude.com/docs/en/manage-claude/admin-api-keys): the Default Workspace comes back with `"name": "Default"`, even though [List Workspaces](https://platform.claude.com/docs/en/api/organization/workspaces/list) omits it unless you pass `include_default=true`
1015* Open that workspace in the [Console](https://platform.claude.com/settings/workspaces) to find the request’s resources, such as sessions, files, message batches, and skills
10161016 
10171017## Workspace limits
10181018 
from line 1020
10201020 
10211021### Setting workspace limits
10221022 
1023You can set workspace limits lower than (but not higher than) your organization's limits:
1023You can set workspace limits lower than (but not higher than) your organization’s limits:
10241024 
10251025* **Spend limits:** Cap monthly spending for a workspace. To set these, go to the [Workspaces](https://platform.claude.com/settings/workspaces) page (`platform.claude.com/settings/workspaces`) in the Claude Console and select the workspace. In the panel that opens, click **Spend limits**.
10261026* **Rate limits:** Limit requests per minute, input tokens per minute, or output tokens per minute. To set these, go to [Rate limits in the Claude Console](https://platform.claude.com/usage/limits), select the workspace from the **Workspace** dropdown menu, and click **Edit** next to a model.
from line 1027
10271027 
10281028<Note>
10291029 - You cannot set spend limits on the Default Workspace.
1030 - If not set, workspace limits match the organization's limits.
1030 - If not set, workspace limits match the organization’s limits.
10311031 - Organization-wide limits always apply, even if workspace limits add up to more.
10321032</Note>
10331033 
from line 1078
10781078 
10791079<Steps>
10801080 <Step title="Plan your workspace structure">
1081 Consider how you'll organize workspaces before creating them. Think about billing, access control, and usage tracking needs.
1081 Consider how you’ll organize workspaces before creating them. Think about billing, access control, and usage tracking needs.
10821082 </Step>
10831083 
10841084 <Step title="Use meaningful names">
1085 Name workspaces clearly to indicate their purpose (for example, "Production - Customer Chatbot" or "Dev - Internal Tools").
1085 Name workspaces clearly to indicate their purpose (for example, “Production - Customer Chatbot” or “Dev - Internal Tools”).
10861086 </Step>
10871087 
10881088 <Step title="Set appropriate limits">
from line 1101
11011101## FAQ
11021102 
11031103<AccordionGroup>
1104 <Accordion title="What's the Default Workspace?">
1105 Every organization has a "Default Workspace" that cannot be renamed, archived, or deleted. Like every workspace, it has a `wrkspc_` ID: the API returns it in the [`anthropic-workspace-id` response header](https://platform.claude.com/docs/en/manage-claude/workspaces#identify-the-workspace-behind-an-api-response), and you can pass it to [Get Workspace](https://platform.claude.com/docs/en/api/organization/workspaces/retrieve) and [Update Workspace](https://platform.claude.com/docs/en/api/organization/workspaces/update). It has no member list of its own, because access to it follows each member's organization role. It appears in [List Workspaces](https://platform.claude.com/docs/en/api/organization/workspaces/list) results only when you pass `include_default=true`, and usage reports and cost reports that belong to it show `null` for `workspace_id`. For an API key that belongs to it, the key's `scope` field carries the workspace's real ID.
1104 <Accordion title="What’s the Default Workspace?">
1105 Every organization has a “Default Workspace” that cannot be renamed, archived, or deleted. Like every workspace, it has a `wrkspc_` ID: the API returns it in the [`anthropic-workspace-id` response header](https://platform.claude.com/docs/en/manage-claude/workspaces#identify-the-workspace-behind-an-api-response), and you can pass it to [Get Workspace](https://platform.claude.com/docs/en/api/organization/workspaces/retrieve) and [Update Workspace](https://platform.claude.com/docs/en/api/organization/workspaces/update). It has no member list of its own, because access to it follows each member’s organization role. It appears in [List Workspaces](https://platform.claude.com/docs/en/api/organization/workspaces/list) results only when you pass `include_default=true`, and usage reports and cost reports that belong to it show `null` for `workspace_id`. For an API key that belongs to it, the key’s `scope` field carries the workspace’s real ID.
11061106 </Accordion>
11071107 
1108 <Accordion title="What's the Claude Code workspace?">
1109 Anthropic creates the Claude Code workspace automatically the first time a member of your organization signs in to Claude Code with their Console account. It isolates Claude Code's API keys, usage, and rate limits from your other workloads. See [Claude Code workspace](https://platform.claude.com/docs/en/manage-claude/workspaces#claude-code-workspace) for details.
1108 <Accordion title="What’s the Claude Code workspace?">
1109 Anthropic creates the Claude Code workspace automatically the first time a member of your organization signs in to Claude Code with their Console account. It isolates Claude Code’s API keys, usage, and rate limits from your other workloads. See [Claude Code workspace](https://platform.claude.com/docs/en/manage-claude/workspaces#claude-code-workspace) for details.
11101110 </Accordion>
11111111 
11121112 <Accordion title="Are there limits on workspaces?">
1113 Yes. Each organization can have up to 100 workspaces by default, and archived workspaces don't count toward this limit. If you need more, contact your account team.
1113 Yes. Each organization can have up to 100 workspaces by default, and archived workspaces don’t count toward this limit. If you need more, contact your account team.
11141114 </Accordion>
11151115 
11161116 <Accordion title="How do organization roles affect workspace access?">
from line 1118
11181118 </Accordion>
11191119 
11201120 <Accordion title="Which roles can be assigned in workspaces?">
1121 Organization users and developers can be assigned Workspace Admin, Workspace Developer, Workspace Limited Developer, or Workspace User roles. The Workspace Billing role cannot be manually assigned; it's inherited from having the organization `billing` role.
1121 Organization users and developers can be assigned Workspace Admin, Workspace Developer, Workspace Limited Developer, or Workspace User roles. The Workspace Billing role cannot be manually assigned; it’s inherited from having the organization `billing` role.
11221122 </Accordion>
11231123 
1124 <Accordion title="Can organization admin or billing members' workspace roles be changed?">
1124 <Accordion title="Can organization admin or billing members’ workspace roles be changed?">
11251125 Organization admins and billing members cannot have their workspace roles changed or be removed from workspaces while they hold those organization roles (with one exception: billing members can be upgraded to a Workspace Admin role). For everyone else covered by this constraint, change their organization role first to change their workspace access.
11261126 </Accordion>
11271127 

managed-agents/agent-setup Changed · +15 / -15 lines

from line 20
2020| ------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2121| `name` (csharp, go: `Name`) | Required. A human-readable name for the agent. |
2222| `model` (csharp, go: `Model`) | Required. The Claude [model](https://platform.claude.com/docs/en/models/overview) that powers the agent. Accepts a model ID string or an object, for example `{"id": "claude-opus-5-5"}`. Claude 4.5 and later models are supported. The object form also accepts `speed` (csharp, go: `Speed`), `effort` (csharp, go: `Effort`), and `inference_geo` (java, php: `inferenceGeo`; csharp, go: `InferenceGeo`) fields; see the tips under [Create an agent](https://platform.claude.com/docs/en/managed-agents/agent-setup#create-an-agent), [Effort levels](https://platform.claude.com/docs/en/build-with-claude/effort#effort-levels), and [Pin the inference geo](https://platform.claude.com/docs/en/managed-agents/agent-setup#pin-the-inference-geo). |
23| `system` (csharp, go: `System`; ruby: `system_`) | A [system prompt](https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/claude-prompting-best-practices#give-claude-a-role) that defines the agent's behavior and persona. The system prompt is distinct from [user messages](https://platform.claude.com/docs/en/managed-agents/reference#event-types), which should describe the work to be done. |
23| `system` (csharp, go: `System`; ruby: `system_`) | A [system prompt](https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/claude-prompting-best-practices#give-claude-a-role) that defines the agent’s behavior and persona. The system prompt is distinct from [user messages](https://platform.claude.com/docs/en/managed-agents/reference#event-types), which should describe the work to be done. |
2424| `tools` (csharp, go: `Tools`) | The tools available to the agent. Combines [pre-built agent tools](https://platform.claude.com/docs/en/managed-agents/tools), [MCP tools](https://platform.claude.com/docs/en/managed-agents/mcp-connector), and [custom tools](https://platform.claude.com/docs/en/managed-agents/tools#custom-tools). |
2525| `mcp_servers` (java, php: `mcpServers`; csharp: `McpServers`; go: `MCPServers`) | [MCP servers](https://platform.claude.com/docs/en/managed-agents/mcp-connector) that provide standardized third-party capabilities. |
2626| `skills` (csharp, go: `Skills`) | [Skills](https://platform.claude.com/docs/en/managed-agents/skills) that supply domain-specific context with progressive disclosure. |
from line 28
2828| `description` (csharp, go: `Description`) | A description of what the agent does. |
2929| `metadata` (csharp, go: `Metadata`) | Arbitrary key-value pairs for your own tracking. |
3030 
31You can also override `model` (csharp, go: `Model`), `system` (csharp, go: `System`; ruby: `system_`), `tools` (csharp, go: `Tools`), `mcp_servers` (java, php: `mcpServers`; csharp: `McpServers`; go: `MCPServers`), and `skills` (csharp, go: `Skills`) for a single session without changing the agent. A `model` (csharp, go: `Model`) override replaces the agent's `model` (csharp, go: `Model`) object in full, so the agent's own `effort` (csharp, go: `Effort`) isn't carried over. To run the session at a specific effort level, set `effort` (csharp, go: `Effort`) inside the override's `model` (csharp, go: `Model`) object. See [Override agent configuration for a session](https://platform.claude.com/docs/en/managed-agents/sessions#override-agent-configuration-for-a-session).
31You can also override `model` (csharp, go: `Model`), `system` (csharp, go: `System`; ruby: `system_`), `tools` (csharp, go: `Tools`), `mcp_servers` (java, php: `mcpServers`; csharp: `McpServers`; go: `MCPServers`), and `skills` (csharp, go: `Skills`) for a single session without changing the agent. A `model` (csharp, go: `Model`) override replaces the agent’s `model` (csharp, go: `Model`) object in full, so the agent’s own `effort` (csharp, go: `Effort`) isn’t carried over. To run the session at a specific effort level, set `effort` (csharp, go: `Effort`) inside the override’s `model` (csharp, go: `Model`) object. See [Override agent configuration for a session](https://platform.claude.com/docs/en/managed-agents/sessions#override-agent-configuration-for-a-session).
3232 
3333## Create an agent
3434 
3535The following example defines a coding agent that uses the newest Claude Opus model with access to the pre-built agent toolset. The toolset lets the agent write code, read files, search the web, and more. See the [agent tools reference](https://platform.claude.com/docs/en/managed-agents/tools) for the full list of supported tools.
3636 
37The examples use curl, the `ant` CLI, or the SDK. If you haven't set one up, the [quickstart](https://platform.claude.com/docs/en/managed-agents/quickstart#install-the-cli) covers installation and client setup.
37The examples use curl, the `ant` CLI, or the SDK. If you haven’t set one up, the [quickstart](https://platform.claude.com/docs/en/managed-agents/quickstart#install-the-cli) covers installation and client setup.
3838 
3939<CodeGroup defaultLanguage="CLI">
4040 ```bash cURL
from line 186
186186The `default_config` on the toolset shows its default [permission policy](https://platform.claude.com/docs/en/managed-agents/permission-policies), `always_allow`, which applies unless you configure one.
187187 
188188<Tip>
189 To use Claude Opus 5.5, Claude Opus 5, or Claude Opus 4.8 with [fast mode](https://platform.claude.com/docs/en/build-with-claude/fast-mode), pass `model` as an object, for example: `{"id": "claude-opus-5-5", "speed": "fast"}`. See the fast mode page's [supported models](https://platform.claude.com/docs/en/build-with-claude/fast-mode#supported-models).
189 To use Claude Opus 5.5, Claude Opus 5, or Claude Opus 4.8 with [fast mode](https://platform.claude.com/docs/en/build-with-claude/fast-mode), pass `model` as an object, for example: `{"id": "claude-opus-5-5", "speed": "fast"}`. See the fast mode page’s [supported models](https://platform.claude.com/docs/en/build-with-claude/fast-mode#supported-models).
190190</Tip>
191191 
192192<Tip>
193 To set the model's effort level, pass `model` as an object, for example: `{"id": "claude-opus-5-5", "effort": "high"}`. The `effort` field accepts a level string (`low`, `medium`, `high`, `xhigh`, or `max`) or an object such as `{"type": "high"}`. See [Effort levels](https://platform.claude.com/docs/en/build-with-claude/effort#effort-levels) for what each level does.
193 To set the model’s effort level, pass `model` as an object, for example: `{"id": "claude-opus-5-5", "effort": "high"}`. The `effort` field accepts a level string (`low`, `medium`, `high`, `xhigh`, or `max`) or an object such as `{"type": "high"}`. See [Effort levels](https://platform.claude.com/docs/en/build-with-claude/effort#effort-levels) for what each level does.
194194</Tip>
195195 
196196### Pin the inference geo
197197 
198Like `speed` (csharp, go: `Speed`) and `effort` (csharp, go: `Effort`), `inference_geo` (java, php: `inferenceGeo`; csharp, go: `InferenceGeo`) is set through the object form of `model` (csharp, go: `Model`): pass `model` (csharp, go: `Model`) as an object and set `inference_geo` (java, php: `inferenceGeo`; csharp, go: `InferenceGeo`) alongside `id` (csharp, go: `ID`). The field accepts `"us"` or `"global"`. When it's unset, each model request follows the workspace's default inference geo at the time it's served. See [Data residency](https://platform.claude.com/docs/en/manage-claude/data-residency) for the workspace-level geo controls and pricing.
198Like `speed` (csharp, go: `Speed`) and `effort` (csharp, go: `Effort`), `inference_geo` (java, php: `inferenceGeo`; csharp, go: `InferenceGeo`) is set through the object form of `model` (csharp, go: `Model`): pass `model` (csharp, go: `Model`) as an object and set `inference_geo` (java, php: `inferenceGeo`; csharp, go: `InferenceGeo`) alongside `id` (csharp, go: `ID`). The field accepts `"us"` or `"global"`. When it’s unset, each model request follows the workspace’s default inference geo at the time it’s served. See [Data residency](https://platform.claude.com/docs/en/manage-claude/data-residency) for the workspace-level geo controls and pricing.
199199 
200The following example pins an agent to US inference. The response carries the `inference_geo` (java, php: `inferenceGeo`; csharp, go: `InferenceGeo`) value in the agent's `model` (csharp, go: `Model`) object:
200The following example pins an agent to US inference. The response carries the `inference_geo` (java, php: `inferenceGeo`; csharp, go: `InferenceGeo`) value in the agent’s `model` (csharp, go: `Model`) object:
201201 
202202<CodeGroup defaultLanguage="CLI">
203203 ```bash cURL
from line 327
327327 ```
328328</CodeGroup>
329329 
330An `inference_geo` (java, php: `inferenceGeo`; csharp, go: `InferenceGeo`) pin is validated against the workspace's [`allowed_inference_geos`](https://platform.claude.com/docs/en/manage-claude/data-residency#workspace-level-restrictions) when the agent is saved, when a session is created from it, and on every turn the session serves. If the workspace allowlist narrows so a pin is no longer allowed, new sessions can't be created from the agent and running sessions refuse further turns; pins are never exempted, because workspaces rely on them for compliance and data residency.
330An `inference_geo` (java, php: `inferenceGeo`; csharp, go: `InferenceGeo`) pin is validated against the workspace’s [`allowed_inference_geos`](https://platform.claude.com/docs/en/manage-claude/data-residency#workspace-level-restrictions) when the agent is saved, when a session is created from it, and on every turn the session serves. If the workspace allowlist narrows so a pin is no longer allowed, new sessions can’t be created from the agent and running sessions refuse further turns; pins are never exempted, because workspaces rely on them for compliance and data residency.
331331 
332Setting `inference_geo` (java, php: `inferenceGeo`; csharp, go: `InferenceGeo`) on a model that doesn't support geographic inference pinning returns a 400 error; see [Model availability](https://platform.claude.com/docs/en/manage-claude/data-residency#model-availability) for the models that do. In a `multiagent` (csharp, go: `Multiagent`) configuration, the agent's pin and the pin of every agent listed in the `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) list of `subagents` (csharp, go: `Subagents`) or `workflows` (csharp, go: `Workflows`) must all be set to the same value or all be unset; see [Multiagent orchestration](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration). To change or clear the pin later, update the agent's `model` (csharp, go: `Model`) object; supplying `model` (csharp, go: `Model`) without `inference_geo` (java, php: `inferenceGeo`; csharp, go: `InferenceGeo`) clears it, as described under [Update semantics](https://platform.claude.com/docs/en/managed-agents/agent-setup#update-semantics).
332Setting `inference_geo` (java, php: `inferenceGeo`; csharp, go: `InferenceGeo`) on a model that doesn’t support geographic inference pinning returns a 400 error; see [Model availability](https://platform.claude.com/docs/en/manage-claude/data-residency#model-availability) for the models that do. In a `multiagent` (csharp, go: `Multiagent`) configuration, the agent’s pin and the pin of every agent listed in the `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) list of `subagents` (csharp, go: `Subagents`) or `workflows` (csharp, go: `Workflows`) must all be set to the same value or all be unset; see [Multiagent orchestration](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration). To change or clear the pin later, update the agent’s `model` (csharp, go: `Model`) object; supplying `model` (csharp, go: `Model`) without `inference_geo` (java, php: `inferenceGeo`; csharp, go: `InferenceGeo`) clears it, as described under [Update semantics](https://platform.claude.com/docs/en/managed-agents/agent-setup#update-semantics).
333333 
334334## Update an agent
335335 
336336Updating an agent generates a new version when the configuration changes. The `version` (csharp, go: `Version`) field is optional: supply it for optimistic concurrency (a mismatch returns a 409), or omit it to apply the update unconditionally (last write wins). Updates to archived agents are rejected.
337337 
338With the CLI, edit the agent's file and run `ant apply` again; apply supplies `version` for you.
338With the CLI, edit the agent’s file and run `ant apply` again; apply supplies `version` for you.
339339 
340340<CodeGroup defaultLanguage="CLI">
341341 ```bash cURL
from line 532
532532 
533533### Update semantics
534534 
535* **`version` (csharp, go: `Version`)** is optional and must be at least 1 when supplied. When supplied, the request returns a 409 if it doesn't match the agent's current version, even when the fields you send already match the stored values; re-read the agent and retry. When omitted, the update applies unconditionally and the most recent update silently replaces any concurrent one, with no error to either caller. Supplying `version` (csharp, go: `Version`) is the recommended default for interactive callers, and omitting it fits declarative apply loops, such as a CI job that syncs checked-in agent definitions, where the loop owns the agent.
535* **`version` (csharp, go: `Version`)** is optional and must be at least 1 when supplied. When supplied, the request returns a 409 if it doesn’t match the agent’s current version, even when the fields you send already match the stored values; re-read the agent and retry. When omitted, the update applies unconditionally and the most recent update silently replaces any concurrent one, with no error to either caller. Supplying `version` (csharp, go: `Version`) is the recommended default for interactive callers, and omitting it fits declarative apply loops, such as a CI job that syncs checked-in agent definitions, where the loop owns the agent.
536536 
537537* **Omitted fields are preserved.** You only need to include the fields you want to change.
538538 
539* **Scalar fields** (`model` (csharp, go: `Model`), `system` (csharp, go: `System`; ruby: `system_`), `name` (csharp, go: `Name`), `description` (csharp, go: `Description`)) are replaced with the new value. `system` (csharp, go: `System`; ruby: `system_`) and `description` (csharp, go: `Description`) can be cleared by passing `null` (python: `None`; ruby: `nil`; java: `Optional.empty()`; go: `param.Null[string]()`; php: `requestOptions: ['extraBodyParams' => ['system' => null, 'description' => null]]`). `model` (csharp, go: `Model`) and `name` (csharp, go: `Name`) are mandatory and cannot be cleared. Within a `model` (csharp, go: `Model`) object you supply, `effort` (csharp, go: `Effort`) is the sole exception: if the model `id` (csharp, go: `ID`) is unchanged, omitting `effort` (csharp, go: `Effort`) leaves the stored effort level unchanged. If you change the model `id` (csharp, go: `ID`), an omitted `effort` (csharp, go: `Effort`) resets to the new model's default. Other `model` (csharp, go: `Model`) fields are replaced along with the object: supplying `model` (csharp, go: `Model`) without `inference_geo` (java, php: `inferenceGeo`; csharp, go: `InferenceGeo`) clears the agent's inference geo pin.
539* **Scalar fields** (`model` (csharp, go: `Model`), `system` (csharp, go: `System`; ruby: `system_`), `name` (csharp, go: `Name`), `description` (csharp, go: `Description`)) are replaced with the new value. `system` (csharp, go: `System`; ruby: `system_`) and `description` (csharp, go: `Description`) can be cleared by passing `null` (python: `None`; ruby: `nil`; java: `Optional.empty()`; go: `param.Null[string]()`; php: `requestOptions: ['extraBodyParams' => ['system' => null, 'description' => null]]`). `model` (csharp, go: `Model`) and `name` (csharp, go: `Name`) are mandatory and cannot be cleared. Within a `model` (csharp, go: `Model`) object you supply, `effort` (csharp, go: `Effort`) is the sole exception: if the model `id` (csharp, go: `ID`) is unchanged, omitting `effort` (csharp, go: `Effort`) leaves the stored effort level unchanged. If you change the model `id` (csharp, go: `ID`), an omitted `effort` (csharp, go: `Effort`) resets to the new model’s default. Other `model` (csharp, go: `Model`) fields are replaced along with the object: supplying `model` (csharp, go: `Model`) without `inference_geo` (java, php: `inferenceGeo`; csharp, go: `InferenceGeo`) clears the agent’s inference geo pin.
540540 
541541* **Array fields** (`tools` (csharp, go: `Tools`), `mcp_servers` (java, php: `mcpServers`; csharp: `McpServers`; go: `MCPServers`), `skills` (csharp, go: `Skills`)) are fully replaced by the new array. To clear an array field entirely, pass `null` (python: `None`; ruby: `nil`; java: `Optional.empty()`; go: `param.NullSlice[[]anthropic.BetaAgentUpdateParamsToolUnion]()`; php: `requestOptions: ['extraBodyParams' => ['mcp_servers' => null]]`) or an empty array.
542542 
from line 546
546546 
547547* **No-op detection.** If the update produces no change relative to the current version, no new version is created and the existing version is returned.
548548 
549* **Other agents' `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) lists are not updated.** Agents that list this agent in the `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) list of `subagents` (csharp, go: `Subagents`) or `workflows` (csharp, go: `Workflows`) keep the version that was pinned when they were created or when an update last sent that list, even if the reference omits `version` (csharp, go: `Version`). To delegate to the new version, [update the agent that lists it](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#list-the-subagents) so that the list references the new version.
549* **Other agents’ `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) lists are not updated.** Agents that list this agent in the `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) list of `subagents` (csharp, go: `Subagents`) or `workflows` (csharp, go: `Workflows`) keep the version that was pinned when they were created or when an update last sent that list, even if the reference omits `version` (csharp, go: `Version`). To delegate to the new version, [update the agent that lists it](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#list-the-subagents) so that the list references the new version.
550550 
551551The following update clears the description, deletes the `team` key from `metadata` (csharp, go: `Metadata`), and sets `workflows` (csharp, go: `Workflows`) to `null`, which turns dynamic workflows on:
552552 

managed-agents/budgets Changed · +24 / -24 lines

from line 10
1010 betaHeader: managed-agents-2026-04-01
1111---
1212 
13A session budget is an optional hard spend ceiling you set when you [create a session](https://platform.claude.com/docs/en/managed-agents/sessions). The platform continuously prices everything the session consumes at public list rates (the session's **list cost**) and stops issuing new model requests once that cost reaches the budget. The request in flight when the cap is crossed still completes, so the final list cost can land [a fraction past the budget](https://platform.claude.com/docs/en/managed-agents/budgets#when-a-session-reaches-its-budget). A session at its budget pauses and goes [idle](https://platform.claude.com/docs/en/managed-agents/session-operations#session-statuses) rather than terminating; changing or removing the budget resumes the work that the budget paused. Deployments accept the same budget and apply it to each session they start; see [Budgets on deployments](https://platform.claude.com/docs/en/managed-agents/budgets#budgets-on-deployments).
13A session budget is an optional hard spend ceiling you set when you [create a session](https://platform.claude.com/docs/en/managed-agents/sessions). The platform continuously prices everything the session consumes at public list rates (the session’s **list cost**) and stops issuing new model requests once that cost reaches the budget. The request in flight when the cap is crossed still completes, so the final list cost can land [a fraction past the budget](https://platform.claude.com/docs/en/managed-agents/budgets#when-a-session-reaches-its-budget). A session at its budget pauses and goes [idle](https://platform.claude.com/docs/en/managed-agents/session-operations#session-statuses) rather than terminating; changing or removing the budget resumes the work that the budget paused. Deployments accept the same budget and apply it to each session they start; see [Budgets on deployments](https://platform.claude.com/docs/en/managed-agents/budgets#budgets-on-deployments).
1414 
1515## Set a budget at session creation
1616 
from line 141
141141* `type` (csharp, go: `Type`) is always `"limit"`.
142142* `max_list_cost` (java, php: `maxListCost`; csharp, go: `MaxListCost`) is the cap itself: `amount` (csharp, go: `Amount`) is a whole number of US cents written as a string with no leading zeros (`"125"` is $1.25 USD and `"50"` is 50 cents) and must be greater than zero. Decimal forms such as `"25.00"` are rejected. The amount is a string rather than a number so no float rounding is ever applied to it. `currency` (csharp, go: `Currency`) is an uppercase ISO-4217 currency code; `USD` is the only supported currency.
143143 
144A budget can only be attached when the session is created. Adding a budget to an existing session that doesn't have one is rejected with a 400 error. A budgeted session's cap can be [changed](https://platform.claude.com/docs/en/managed-agents/budgets#change-the-budget) or [removed](https://platform.claude.com/docs/en/managed-agents/budgets#remove-the-budget) at any time.
144A budget can only be attached when the session is created. Adding a budget to an existing session that doesn’t have one is rejected with a 400 error. A budgeted session’s cap can be [changed](https://platform.claude.com/docs/en/managed-agents/budgets#change-the-budget) or [removed](https://platform.claude.com/docs/en/managed-agents/budgets#remove-the-budget) at any time.
145145 
146146## How list cost is measured
147147 
148148The platform prices what the session consumes, continuously, at public list rates:
149149 
150* **Model tokens**, at each served model's list price
150* **Model tokens**, at each served model’s list price
151151* **Web searches**, at $10 USD per 1,000 searches
152152* **Session running time**, at $0.08 USD per hour
153153 
154This running dollar total is the session's **list cost**, and it is what the budget compares against. List cost is not your contracted price: if your organization has negotiated discounts, the session reaches its cap when the list-price total does, and your billed spend might be lower than the cap.
154This running dollar total is the session’s **list cost**, and it is what the budget compares against. List cost is not your contracted price: if your organization has negotiated discounts, the session reaches its cap when the list-price total does, and your billed spend might be lower than the cap.
155155 
156156Enforcement uses the exact, unrounded list cost. The `list_cost` (java, php: `listCost`; csharp, go: `ListCost`) figures reported on the session and its events are whole cents, rounded to the nearest cent, so a reported figure can read up to half a cent either side of the exact amount enforcement uses.
157157 
158158## When a session reaches its budget
159159 
160The cap is enforced between model requests, not mid-request. Before each model request, the platform checks the session's consumed list cost, and once that total reaches the cap every thread pauses before its next request. The request that carried the total past the cap was admitted while the session was still under it and runs to completion. As a result, a paused session's recorded `list_cost` (java, php: `listCost`; csharp, go: `ListCost`) reads at or a fraction past `max_list_cost` (java, php: `maxListCost`; csharp, go: `MaxListCost`). For example, a session capped at `"50"` (50 cents) can pause with a `list_cost` (java, php: `listCost`; csharp, go: `ListCost`) of `"53"`. This is expected, not a billing error. Model spend past the cap is at most the cost of one model request for each thread. The tool calls in that request's response still run, and their web searches and running time also count toward list cost. Treat the budget as a bound on new work rather than an exact stopping point, and size the cap with that margin in mind.
160The cap is enforced between model requests, not mid-request. Before each model request, the platform checks the session’s consumed list cost, and once that total reaches the cap every thread pauses before its next request. The request that carried the total past the cap was admitted while the session was still under it and runs to completion. As a result, a paused session’s recorded `list_cost` (java, php: `listCost`; csharp, go: `ListCost`) reads at or a fraction past `max_list_cost` (java, php: `maxListCost`; csharp, go: `MaxListCost`). For example, a session capped at `"50"` (50 cents) can pause with a `list_cost` (java, php: `listCost`; csharp, go: `ListCost`) of `"53"`. This is expected, not a billing error. Model spend past the cap is at most the cost of one model request for each thread. The tool calls in that request’s response still run, and their web searches and running time also count toward list cost. Treat the budget as a bound on new work rather than an exact stopping point, and size the cap with that margin in mind.
161161 
162A session that reaches its budget goes idle with a `stop_reason` (java, php: `stopReason`; csharp, go: `StopReason`) of `budget_reached`; it is not terminated, and its history and sandbox are preserved like any other idle session's. On the [event stream](https://platform.claude.com/docs/en/managed-agents/events-and-streaming) you'll see, in order:
162A session that reaches its budget goes idle with a `stop_reason` (java, php: `stopReason`; csharp, go: `StopReason`) of `budget_reached`; it is not terminated, and its history and sandbox are preserved like any other idle session’s. On the [event stream](https://platform.claude.com/docs/en/managed-agents/events-and-streaming) you’ll see, in order:
163163 
1641641. A `session.thread_status_idle` event with a `stop_reason` (java, php: `stopReason`; csharp, go: `StopReason`) of `budget_reached` as each thread pauses.
1652. A [`session.usage`](https://platform.claude.com/docs/en/managed-agents/budgets#monitor-spend) event with the session's cumulative usage and list cost.
1652. A [`session.usage`](https://platform.claude.com/docs/en/managed-agents/budgets#monitor-spend) event with the session’s cumulative usage and list cost.
1661663. A `session.status_idle` event with a `stop_reason` (java, php: `stopReason`; csharp, go: `StopReason`) of `budget_reached`. The usage event always immediately precedes this idle event.
167167 
168If [workflow runs](https://platform.claude.com/docs/en/managed-agents/workflow-runs#budgets-and-limits) are open, each one that isn't already idle also gets a `workflow_run.status_idle` event.
168If [workflow runs](https://platform.claude.com/docs/en/managed-agents/workflow-runs#budgets-and-limits) are open, each one that isn’t already idle also gets a `workflow_run.status_idle` event.
169169 
170170A thread whose final request both crosses the cap and completes its turn reports `end_turn` on its own `session.thread_status_idle` event while the session still reports `budget_reached`; treat the session-level `stop_reason` (java, php: `stopReason`; csharp, go: `StopReason`) as the signal that the session paused at its budget.
171171 
from line 182
182182 
183183A `user.interrupt` sent while the session is paused at its budget (all threads paused at the cap) is accepted, and it does not appear in the event list. It stops no thread, because every thread is already paused. Change or remove the budget to continue.
184184 
185If the session has open workflow runs, the interrupt can pause them, as an interrupt at any other time can. A run that an interrupt paused doesn't resume when you change or remove the budget. To continue it, change or remove the budget, then send a `user.message` asking the agent to continue its runs. See [Interrupt a session with runs open](https://platform.claude.com/docs/en/managed-agents/workflow-runs#interrupt-a-session-with-runs-open).
185If the session has open workflow runs, the interrupt can pause them, as an interrupt at any other time can. A run that an interrupt paused doesn’t resume when you change or remove the budget. To continue it, change or remove the budget, then send a `user.message` asking the agent to continue its runs. See [Interrupt a session with runs open](https://platform.claude.com/docs/en/managed-agents/workflow-runs#interrupt-a-session-with-runs-open).
186186 
187187## Resume a session at its budget
188188 
from line 192
192192 
193193### Change the budget
194194 
195Update the session with a new `max_list_cost` (java, php: `maxListCost`; csharp, go: `MaxListCost`). The new value can be higher or lower than the current cap, but it must be strictly greater than the session's consumed list cost; otherwise the update is rejected with a 400 error: `budget.max_list_cost must be greater than the session's consumed list cost`. Because the consumed cost usually sits [a fraction past the old cap](https://platform.claude.com/docs/en/managed-agents/budgets#when-a-session-reaches-its-budget) when the session pauses, base the new value on the session's reported `usage.list_cost` (java, php: `usage.listCost`; csharp, go: `Usage.ListCost`), not on the old `max_list_cost` (java, php: `maxListCost`; csharp, go: `MaxListCost`). Set it a cent or more above that figure: the reported value is rounded and can sit a fraction below the exact consumed cost the check uses.
195Update the session with a new `max_list_cost` (java, php: `maxListCost`; csharp, go: `MaxListCost`). The new value can be higher or lower than the current cap, but it must be strictly greater than the session’s consumed list cost; otherwise the update is rejected with a 400 error: `budget.max_list_cost must be greater than the session's consumed list cost`. Because the consumed cost usually sits [a fraction past the old cap](https://platform.claude.com/docs/en/managed-agents/budgets#when-a-session-reaches-its-budget) when the session pauses, base the new value on the session’s reported `usage.list_cost` (java, php: `usage.listCost`; csharp, go: `Usage.ListCost`), not on the old `max_list_cost` (java, php: `maxListCost`; csharp, go: `MaxListCost`). Set it a cent or more above that figure: the reported value is rounded and can sit a fraction below the exact consumed cost the check uses.
196196 
197197<CodeGroup>
198198 ```bash cURL
from line 367
367367</CodeGroup>
368368 
369369<Warning>
370 Removing a session's budget is one-way: a session whose budget has been removed cannot be given a new one. To keep a cap on the session, change the budget instead.
370 Removing a session’s budget is one-way: a session whose budget has been removed cannot be given a new one. To keep a cap on the session, change the budget instead.
371371</Warning>
372372 
373373## Monitor spend
374374 
375The session object carries its `budget` (csharp, go: `Budget`) and a `usage` (csharp, go: `Usage`) object with the tracked spend: `usage.list_cost` (java, php: `usage.listCost`; csharp, go: `Usage.ListCost`) is the session's consumed list cost, and `usage.active_seconds` (java, php: `usage.activeSeconds`; csharp, go: `Usage.ActiveSeconds`) is the running time its runtime cost is priced on. On a session paused at `budget_reached`, expect `usage.list_cost` (java, php: `usage.listCost`; csharp, go: `Usage.ListCost`) to read at or a fraction past `max_list_cost` (java, php: `maxListCost`; csharp, go: `MaxListCost`): the [request that crossed the cap](https://platform.claude.com/docs/en/managed-agents/budgets#when-a-session-reaches-its-budget) completed before the pause. Session-level `active_seconds` (java, php: `activeSeconds`; csharp, go: `ActiveSeconds`) counts overlapping activity from concurrent threads once. Thread retrieval responses carry the same two fields on the thread's own `usage` (csharp, go: `Usage`), priced for each thread. Per-thread figures are rounded independently and exclude the session's running-time cost, so they don't sum exactly to the session's `list_cost` (java, php: `listCost`; csharp, go: `ListCost`); the session figure is the one the budget is enforced against.
375The session object carries its `budget` (csharp, go: `Budget`) and a `usage` (csharp, go: `Usage`) object with the tracked spend: `usage.list_cost` (java, php: `usage.listCost`; csharp, go: `Usage.ListCost`) is the session’s consumed list cost, and `usage.active_seconds` (java, php: `usage.activeSeconds`; csharp, go: `Usage.ActiveSeconds`) is the running time its runtime cost is priced on. On a session paused at `budget_reached`, expect `usage.list_cost` (java, php: `usage.listCost`; csharp, go: `Usage.ListCost`) to read at or a fraction past `max_list_cost` (java, php: `maxListCost`; csharp, go: `MaxListCost`): the [request that crossed the cap](https://platform.claude.com/docs/en/managed-agents/budgets#when-a-session-reaches-its-budget) completed before the pause. Session-level `active_seconds` (java, php: `activeSeconds`; csharp, go: `ActiveSeconds`) counts overlapping activity from concurrent threads once. Thread retrieval responses carry the same two fields on the thread’s own `usage` (csharp, go: `Usage`), priced for each thread. Per-thread figures are rounded independently and exclude the session’s running-time cost, so they don’t sum exactly to the session’s `list_cost` (java, php: `listCost`; csharp, go: `ListCost`); the session figure is the one the budget is enforced against.
376376 
377The `session.usage` event is a snapshot of the session's cumulative usage and tracked list cost. It carries the session's token totals, `list_cost` (java, php: `listCost`; csharp, go: `ListCost`), `active_seconds` (java, php: `activeSeconds`; csharp, go: `ActiveSeconds`), `server_tool_use` (java, php: `serverToolUse`; csharp, go: `ServerToolUse`) request counts (`web_search_requests` (java, php: `webSearchRequests`; csharp, go: `WebSearchRequests`), priced into list cost per request, and `web_fetch_requests` (java, php: `webFetchRequests`; csharp, go: `WebFetchRequests`), which reads `0` because web fetch requests carry no per-request charge and aren't metered), and the session's `budget` (csharp, go: `Budget`) if the session has one. It appears in the events list and the session stream. The session emits one immediately before it goes idle, whatever the stop reason, so a session that reaches its budget always emits one immediately before the budget-reached idle event.
377The `session.usage` event is a snapshot of the session’s cumulative usage and tracked list cost. It carries the session’s token totals, `list_cost` (java, php: `listCost`; csharp, go: `ListCost`), `active_seconds` (java, php: `activeSeconds`; csharp, go: `ActiveSeconds`), `server_tool_use` (java, php: `serverToolUse`; csharp, go: `ServerToolUse`) request counts (`web_search_requests` (java, php: `webSearchRequests`; csharp, go: `WebSearchRequests`), priced into list cost per request, and `web_fetch_requests` (java, php: `webFetchRequests`; csharp, go: `WebFetchRequests`), which reads `0` because web fetch requests carry no per-request charge and aren’t metered), and the session’s `budget` (csharp, go: `Budget`) if the session has one. It appears in the events list and the session stream. The session emits one immediately before it goes idle, whatever the stop reason, so a session that reaches its budget always emits one immediately before the budget-reached idle event.
378378 
379379To read usage from the stream and the session object, see [Track usage](https://platform.claude.com/docs/en/managed-agents/session-observability#track-usage).
380380 
381381## Budgets in multiagent sessions
382382 
383A [multiagent](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration) session has a single budget shared across all of its threads; there are no per-thread caps. Each thread's consumption is priced at its own served model, and threads pause independently as the shared cap is reached. [Advisor](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#give-the-session-an-advisor) consultations count against the same budget, priced at the advisor model's rates. One thread can pause at `budget_reached` while another completes its in-flight request.
383A [multiagent](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration) session has a single budget shared across all of its threads; there are no per-thread caps. Each thread’s consumption is priced at its own served model, and threads pause independently as the shared cap is reached. [Advisor](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#give-the-session-an-advisor) consultations count against the same budget, priced at the advisor model’s rates. One thread can pause at `budget_reached` while another completes its in-flight request.
384384 
385A pending ask outranks the cap: a session with one thread waiting on `requires_action` and another paused at `budget_reached` reports `requires_action` at the session level. The pending request still needs an answer, and answering it is a [settle event](https://platform.claude.com/docs/en/managed-agents/budgets#events-accepted-at-the-cap) the budget doesn't block.
385A pending ask outranks the cap: a session with one thread waiting on `requires_action` and another paused at `budget_reached` reports `requires_action` at the session level. The pending request still needs an answer, and answering it is a [settle event](https://platform.claude.com/docs/en/managed-agents/budgets#events-accepted-at-the-cap) the budget doesn’t block.
386386 
387387## Budgets on deployments
388388 
from line 397
397397}
398398```
399399 
400The cap is copied onto each session the deployment starts, so it bounds each run separately rather than the deployment's cumulative spend. Changing the deployment's budget applies to sessions the deployment starts afterward, not to sessions already running. Unlike a session, a deployment's budget can be cleared with `"budget": null` (python: `budget=None`; typescript: `budget: null`; ruby: `budget: nil`; go: `Budget: param.NullStruct[anthropic.BetaManagedAgentsBudgetLimitParam]()`; java: `.budget(Optional.empty())`; csharp: `Budget = null`; php: `requestOptions: ['extraBodyParams' => ['budget' => null]]`; cli: `--budget null`) and set again later. See [Set a budget on each run](https://platform.claude.com/docs/en/managed-agents/scheduled-deployments#set-a-budget-on-each-run).
400The cap is copied onto each session the deployment starts, so it bounds each run separately rather than the deployment’s cumulative spend. Changing the deployment’s budget applies to sessions the deployment starts afterward, not to sessions already running. Unlike a session, a deployment’s budget can be cleared with `"budget": null` (python: `budget=None`; typescript: `budget: null`; ruby: `budget: nil`; go: `Budget: param.NullStruct[anthropic.BetaManagedAgentsBudgetLimitParam]()`; java: `.budget(Optional.empty())`; csharp: `Budget = null`; php: `requestOptions: ['extraBodyParams' => ['budget' => null]]`; cli: `--budget null`) and set again later. See [Set a budget on each run](https://platform.claude.com/docs/en/managed-agents/scheduled-deployments#set-a-budget-on-each-run).
401401 
402402## Models without a list price
403403 
404404A budget can only track consumption the platform can price. Creating a budgeted session is rejected with a 400 error if any of the following uses a model with no public list price:
405405 
406* The session's agent
406* The session’s agent
407407* Any agent listed in the `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) list of `subagents` (csharp, go: `Subagents`) or `workflows` (csharp, go: `Workflows`)
408* The advisor set in the agent's [multiagent configuration](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration)
408* The advisor set in the agent’s [multiagent configuration](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration)
409409 
410410The error states that no list price is available for the model.
411411 
412If a budgeted session's usage comes to include a model with no list price, the budget can no longer measure the session's spend: the session can pause with a `stop_reason` (java, php: `stopReason`; csharp, go: `StopReason`) of `budget_reached`, and changing the budget is rejected. Remove the budget to resume the session.
412If a budgeted session’s usage comes to include a model with no list price, the budget can no longer measure the session’s spend: the session can pause with a `stop_reason` (java, php: `stopReason`; csharp, go: `StopReason`) of `budget_reached`, and changing the budget is rejected. Remove the budget to resume the session.
413413 
414414## Error reference
415415 
from line 418
418418| Condition | Status |
419419| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ |
420420| A work-starting event (for example, `user.message`) is sent while the session is at or over its budget; the error names the [accepted settle events](https://platform.claude.com/docs/en/managed-agents/budgets#events-accepted-at-the-cap) | 400 |
421| The budget is set to a value at or below the session's consumed list cost | 400 |
421| The budget is set to a value at or below the session’s consumed list cost | 400 |
422422| A budget is added to a session created without one, or re-added after removal | 400 |
423423| `amount` (csharp, go: `Amount`) is not a whole number of cents (for example, `"25.00"`), is zero or negative, or `currency` (csharp, go: `Currency`) is not `USD` | 400 |
424424| A budgeted create references a model with [no public list price](https://platform.claude.com/docs/en/managed-agents/budgets#models-without-a-list-price) | 400 |
425425 
426426<Note>
427 Session budgets are hard caps in US dollars (written in cents) on a single session, enforced by the platform. They are distinct from the Messages API's [task budgets](https://platform.claude.com/docs/en/build-with-claude/task-budgets), which are advisory, token-denominated budgets the model uses to self-regulate within one agentic loop.
427 Session budgets are hard caps in US dollars (written in cents) on a single session, enforced by the platform. They are distinct from the Messages API’s [task budgets](https://platform.claude.com/docs/en/build-with-claude/task-budgets), which are advisory, token-denominated budgets the model uses to self-regulate within one agentic loop.
428428</Note>
429429 

managed-agents/choosing-an-environment Changed · +14 / -14 lines

## What a self-hosted sandbox doesn’t change ## What a self-hosted sandbox doesn't change

from line 10
1010 betaHeader: managed-agents-2026-04-01
1111---
1212 
13An environment sets where your agent's tools and code run. They run in a sandbox. A sandbox is an isolated workspace, such as a container, where the agent runs commands and works on files. You have two options:
13An environment sets where your agent’s tools and code run. They run in a sandbox. A sandbox is an isolated workspace, such as a container, where the agent runs commands and works on files. You have two options:
1414 
1515* **Cloud environment:** Anthropic runs the sandbox for you. Each session gets its own fresh Linux container on Anthropic-managed infrastructure, with common languages, databases, and utilities already installed. You choose its network rules and any extra packages to install.
16* **Self-hosted sandbox:** You run the sandbox on your own infrastructure. Claude still runs on Anthropic's side and determines which tool to call next. A program that you run, called a worker, carries out the agent's bash and file tool calls on your machines and sends each result back. It can also serve custom tools.
16* **Self-hosted sandbox:** You run the sandbox on your own infrastructure. Claude still runs on Anthropic’s side and determines which tool to call next. A program that you run, called a worker, carries out the agent’s bash and file tool calls on your machines and sends each result back. It can also serve custom tools.
1717 
1818The agent definition is the same whichever you choose. You set the model, prompt, and tools on the [agent](https://platform.claude.com/docs/en/managed-agents/agent-setup), not on the environment.
1919 
2020## Start with a cloud environment
2121 
22If you're not sure which to choose, or you're new to Claude Managed Agents, start with a [cloud environment](https://platform.claude.com/docs/en/managed-agents/environments), as the [quickstart](https://platform.claude.com/docs/en/managed-agents/quickstart) does. Anthropic runs an isolated container for each session, with common languages and tools already installed, so there is nothing for you to host. Choose a self-hosted sandbox only if tools must run on infrastructure you control.
22If you’re not sure which to choose, or you’re new to Claude Managed Agents, start with a [cloud environment](https://platform.claude.com/docs/en/managed-agents/environments), as the [quickstart](https://platform.claude.com/docs/en/managed-agents/quickstart) does. Anthropic runs an isolated container for each session, with common languages and tools already installed, so there is nothing for you to host. Choose a self-hosted sandbox only if tools must run on infrastructure you control.
2323 
2424## Compare the options
2525 
from line 28
2828| | [Cloud environment](https://platform.claude.com/docs/en/managed-agents/environments) (default) | [Self-hosted sandbox](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes) |
2929| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- |
3030| Where tools and code run | An Anthropic-managed sandbox | Your worker, on your infrastructure |
31| What reaches Anthropic | The [conversation history](https://platform.claude.com/docs/en/managed-agents/events-and-streaming#list-past-events), [tool results](https://platform.claude.com/docs/en/managed-agents/tools#available-tools), the sandbox's files and state, skills, and memory stores | The conversation history, tool results, skills, and memory stores. The sandbox's files and processes stay on your worker. |
31| What reaches Anthropic | The [conversation history](https://platform.claude.com/docs/en/managed-agents/events-and-streaming#list-past-events), [tool results](https://platform.claude.com/docs/en/managed-agents/tools#available-tools), the sandbox’s files and state, skills, and memory stores | The conversation history, tool results, skills, and memory stores. The sandbox’s files and processes stay on your worker. |
3232| What you operate | Nothing | The worker hosts, their hardening, their network limits, and the environment key your worker authenticates with |
3333| Network reach | Network rules you set on the environment | Your own network policy |
3434| File and GitHub repository mounting | Managed by Anthropic | Managed by you |
3535| Memory stores | Mounted by Anthropic at `/mnt/memory/` | Downloaded to `/mnt/memory/` and synced by the worker |
3636| Sandbox lifecycle | Managed by Anthropic | Managed by you |
37| Choose it when | The default. You don't need tools to run on your own infrastructure. | Tools must run on infrastructure you control. |
37| Choose it when | The default. You don’t need tools to run on your own infrastructure. | Tools must run on infrastructure you control. |
3838 
3939## When to choose a self-hosted sandbox
4040 
4141A self-hosted sandbox fits when the agent needs to do one of the following:
4242 
43* **Work on data that can't leave your network:** The agent's files and processes stay on your worker. What a tool returns to the model still reaches the Managed Agents service.
44* **Reach internal services that aren't publicly routable:** The agent's bash and file tools, and any custom tools your worker serves, run inside your network. Anthropic, not your worker, connects to the agent's MCP servers. A private one might be in front of your internal ticket system or company wiki. To reach it, you can use an [MCP tunnel](https://platform.claude.com/docs/en/agents-and-tools/mcp-tunnels/overview) with either option. MCP tunnels are in research preview and need an access request. See [Self-hosted sandboxes and MCP tunnels](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes#self-hosted-sandboxes-and-mcp-tunnels).
45* **Run under your organization's own compliance and audit controls:** Tools run on hosts that you configure, log, and audit.
43* **Work on data that can’t leave your network:** The agent’s files and processes stay on your worker. What a tool returns to the model still reaches the Managed Agents service.
44* **Reach internal services that aren’t publicly routable:** The agent’s bash and file tools, and any custom tools your worker serves, run inside your network. Anthropic, not your worker, connects to the agent’s MCP servers. A private one might be in front of your internal ticket system or company wiki. To reach it, you can use an [MCP tunnel](https://platform.claude.com/docs/en/agents-and-tools/mcp-tunnels/overview) with either option. MCP tunnels are in research preview and need an access request. See [Self-hosted sandboxes and MCP tunnels](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes#self-hosted-sandboxes-and-mcp-tunnels).
45* **Run under your organization’s own compliance and audit controls:** Tools run on hosts that you configure, log, and audit.
4646 
47## What a self-hosted sandbox doesn't change
47## What a self-hosted sandbox doesn’t change
4848 
49With either option, the conversation history and tool results go to the Managed Agents service, which Anthropic operates. That is how the model receives the result of each tool call. The web search and web fetch tools also run on Anthropic's side, not in the sandbox. See [Restrict web search and web fetch domains](https://platform.claude.com/docs/en/managed-agents/tools-web-restrictions). Neither option changes the compliance coverage of Managed Agents or how long it keeps conversation history. See [API and data retention](https://platform.claude.com/docs/en/manage-claude/api-and-data-retention#feature-eligibility).
49With either option, the conversation history and tool results go to the Managed Agents service, which Anthropic operates. That is how the model receives the result of each tool call. The web search and web fetch tools also run on Anthropic’s side, not in the sandbox. See [Restrict web search and web fetch domains](https://platform.claude.com/docs/en/managed-agents/tools-web-restrictions). Neither option changes the compliance coverage of Managed Agents or how long it keeps conversation history. See [API and data retention](https://platform.claude.com/docs/en/manage-claude/api-and-data-retention#feature-eligibility).
5050 
5151## What you take on and give up with a self-hosted sandbox
5252 
from line 61
6161 
6262## Differences on Claude Platform on AWS
6363 
64If you use Claude Managed Agents through Claude Platform on AWS, Anthropic still operates the service. There, a self-hosted sandbox can't use memory stores, workers don't use an environment key, and MCP tunnels aren't available. See [Claude Managed Agents on Claude Platform on AWS](https://platform.claude.com/docs/en/build-with-claude/claude-platform-on-aws#claude-managed-agents) and [Features not supported](https://platform.claude.com/docs/en/build-with-claude/claude-platform-on-aws#features-not-supported).
64If you use Claude Managed Agents through Claude Platform on AWS, Anthropic still operates the service. There, a self-hosted sandbox can’t use memory stores, workers don’t use an environment key, and MCP tunnels aren’t available. See [Claude Managed Agents on Claude Platform on AWS](https://platform.claude.com/docs/en/build-with-claude/claude-platform-on-aws#claude-managed-agents) and [Features not supported](https://platform.claude.com/docs/en/build-with-claude/claude-platform-on-aws#features-not-supported).
6565 
6666## Differences on Claude Models on Google Cloud (Anthropic managed)
6767 
68If you use Claude Managed Agents through Claude Models on Google Cloud (Anthropic managed), Anthropic still operates the service. There, memory stores aren't supported in a self-hosted sandbox, and MCP tunnels aren't available. Workers authenticate with Google Cloud credentials, not an environment key, and you write your own, because the pre-built workers in the `ant` CLI and the Python, TypeScript, and Go SDKs don't work there. See [Feature support](https://platform.claude.com/docs/en/build-with-claude/claude-models-on-google-cloud/anthropic-managed#feature-support) in the Claude Models on Google Cloud (Anthropic managed) guide.
68If you use Claude Managed Agents through Claude Models on Google Cloud (Anthropic managed), Anthropic still operates the service. There, memory stores aren’t supported in a self-hosted sandbox, and MCP tunnels aren’t available. Workers authenticate with Google Cloud credentials, not an environment key, and you write your own, because the pre-built workers in the `ant` CLI and the Python, TypeScript, and Go SDKs don’t work there. See [Feature support](https://platform.claude.com/docs/en/build-with-claude/claude-models-on-google-cloud/anthropic-managed#feature-support) in the Claude Models on Google Cloud (Anthropic managed) guide.
6969 
7070## Switch from a cloud environment to a self-hosted sandbox
7171 
from line 75
7575* **What is new:** A `self_hosted` environment, an environment key that you generate in the Claude Console, and a worker that you run. The [self-hosted sandboxes quickstart](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes#quickstart) covers all three.
7676* **What stops working:** The features listed in [What you take on and give up with a self-hosted sandbox](https://platform.claude.com/docs/en/managed-agents/choosing-an-environment#what-you-take-on-and-give-up-with-a-self-hosted-sandbox).
7777 
78On Claude Platform on AWS, memory stores don't carry over to a self-hosted sandbox, and the worker doesn't use an environment key. See [Differences on Claude Platform on AWS](https://platform.claude.com/docs/en/managed-agents/choosing-an-environment#differences-on-claude-platform-on-aws).
78On Claude Platform on AWS, memory stores don’t carry over to a self-hosted sandbox, and the worker doesn’t use an environment key. See [Differences on Claude Platform on AWS](https://platform.claude.com/docs/en/managed-agents/choosing-an-environment#differences-on-claude-platform-on-aws).
7979 
80On Claude Models on Google Cloud (Anthropic managed), memory stores aren't supported in a self-hosted sandbox, and you write your own worker, which doesn't use an environment key. See [Differences on Claude Models on Google Cloud (Anthropic managed)](https://platform.claude.com/docs/en/managed-agents/choosing-an-environment#differences-on-claude-models-on-google-cloud-anthropic-managed).
80On Claude Models on Google Cloud (Anthropic managed), memory stores aren’t supported in a self-hosted sandbox, and you write your own worker, which doesn’t use an environment key. See [Differences on Claude Models on Google Cloud (Anthropic managed)](https://platform.claude.com/docs/en/managed-agents/choosing-an-environment#differences-on-claude-models-on-google-cloud-anthropic-managed).
8181 

managed-agents/define-outcomes Changed · +8 / -8 lines

from line 12
1212 
1313An outcome tells the session what the end result should look like and how to measure its quality. The agent works toward that target, self-evaluating and iterating until the outcome is met.
1414 
15When you define an outcome, the harness automatically provisions a *grader* to evaluate the artifact against a rubric. The grader uses a separate context window to avoid being influenced by the main agent's implementation choices.
15When you define an outcome, the harness automatically provisions a *grader* to evaluate the artifact against a rubric. The grader uses a separate context window to avoid being influenced by the main agent’s implementation choices.
1616 
1717The grader returns an explanation summarizing which criteria passed or failed, or confirming that the artifact satisfies the rubric. That feedback is handed back to the agent for the next iteration.
1818 
from line 21
2121A rubric is a markdown document describing per-criterion scoring. The rubric is required.
2222 
2323<Accordion title="Tips for writing effective rubrics">
24 Structure the rubric as explicit, gradeable criteria, such as "The CSV contains a price column with numeric values" rather than "The data looks good." The grader scores each criterion independently, so vague criteria produce noisy evaluations.
24 Structure the rubric as explicit, gradeable criteria, such as “The CSV contains a price column with numeric values” rather than “The data looks good.” The grader scores each criterion independently, so vague criteria produce noisy evaluations.
2525 
26 If you don't have a rubric, try giving Claude an example of a known-good artifact and asking it to analyze what makes that content good, then turn that analysis into a rubric. This middle-ground approach often produces better results than writing criteria with no example to start from.
26 If you don’t have a rubric, try giving Claude an example of a known-good artifact and asking it to analyze what makes that content good, then turn that analysis into a rubric. This middle-ground approach often produces better results than writing criteria with no example to start from.
2727</Accordion>
2828 
2929Example rubric:
from line 522
522522Progress on an outcome-oriented session is surfaced on the events [stream](https://platform.claude.com/docs/en/managed-agents/events-and-streaming).
523523 
524524* `agent.*` events (such as messages and tool use) show progress toward the outcome.
525* `span.outcome_evaluation_*` events are only emitted for outcome-oriented sessions and show the number of iteration loops and the grader's feedback process.
526* You can also send `user.message` [events](https://platform.claude.com/docs/en/managed-agents/reference#event-types) to an outcome-oriented session to direct the agent's work as it progresses, but it isn't required: the agent works toward the outcome on its own, iterating until it succeeds or runs out of iterations.
525* `span.outcome_evaluation_*` events are only emitted for outcome-oriented sessions and show the number of iteration loops and the grader’s feedback process.
526* You can also send `user.message` [events](https://platform.claude.com/docs/en/managed-agents/reference#event-types) to an outcome-oriented session to direct the agent’s work as it progresses, but it isn’t required: the agent works toward the outcome on its own, iterating until it succeeds or runs out of iterations.
527527* A `user.interrupt` event pauses work on the current outcome and marks the `result` (csharp, go: `Result`) of the `span.outcome_evaluation_end` event as `interrupted`, allowing you to start a new outcome.
528528* After the final outcome evaluation, the session can be continued as a conversational session, or a new outcome can be started. The session retains history of the prior outcome.
529529 
from line 560
560560 
561561### Outcome evaluation ongoing
562562 
563Heartbeat emitted while the grader runs. The grader's internal reasoning is opaque: the event shows only that the grader is working.
563Heartbeat emitted while the grader runs. The grader’s internal reasoning is opaque: the event shows only that the grader is working.
564564 
565565```json
566566{
from line 582
582582| `needs_revision` | Agent starts a new iteration cycle. |
583583| `max_iterations_reached` | One final acknowledgment turn follows before the session transitions to `idle`. No further evaluation runs. |
584584| `failed` | Session transitions to `idle`. Returned when the rubric does not apply to the deliverables, for example if the description and rubric contradict each other. |
585| `interrupted` | Emitted when the session is interrupted while an outcome is active, even if evaluation hadn't started yet. If no `outcome_evaluation_start` fired before the interrupt, `outcome_evaluation_start_id` is an empty string. |
585| `interrupted` | Emitted when the session is interrupted while an outcome is active, even if evaluation hadn’t started yet. If no `outcome_evaluation_start` fired before the interrupt, `outcome_evaluation_start_id` is an empty string. |
586586 
587587```json
588588{

managed-agents/dreams Changed · +10 / -10 lines

from line 12
1212 
1313**Dreams** let Claude clean that up. A dream reads an existing memory store alongside past session transcripts, then produces a new, reorganized memory store: duplicates merged, stale or contradicted entries replaced with the latest value, and new insights surfaced.
1414 
15The input store is never modified, so you can review the output and discard it if you don't like the result.
15The input store is never modified, so you can review the output and discard it if you don’t like the result.
1616 
1717<Note>
18 Dream endpoints are gated by the `dreaming-2026-04-21` beta header; the `managed-agents-2026-04-01` header on its own doesn't grant access to dreams. The dream-endpoint examples on this page send both headers; session and memory-store calls need only `managed-agents-2026-04-01`. The SDK sets these automatically.
18 Dream endpoints are gated by the `dreaming-2026-04-21` beta header; the `managed-agents-2026-04-01` header on its own doesn’t grant access to dreams. The dream-endpoint examples on this page send both headers; session and memory-store calls need only `managed-agents-2026-04-01`. The SDK sets these automatically.
1919</Note>
2020 
2121## How it works
from line 25
2525* a pre-existing **memory store:** the store Claude verifies, deduplicates, and reorganizes, and
2626* 1 to 100 **sessions:** past transcripts Claude mines for patterns and insights to fold into the output.
2727 
28The dream produces another **output memory store**, separate from the input. The output store ID appears in the dream's `outputs` (csharp, go: `Outputs`) array shortly after the dream starts `running`, once the workflow has cloned the input store; a `running` dream can briefly report an empty `outputs` (csharp, go: `Outputs`) array.
28The dream produces another **output memory store**, separate from the input. The output store ID appears in the dream’s `outputs` (csharp, go: `Outputs`) array shortly after the dream starts `running`, once the workflow has cloned the input store; a `running` dream can briefly report an empty `outputs` (csharp, go: `Outputs`) array.
2929 
3030## Create a dream
3131 
from line 188
188188 
189189The optional `instructions` (csharp, go: `Instructions`) field steers what the dreaming pipeline synthesizes. It is applied throughout the pipeline: what to read closely, what to merge or drop, and how to structure the output store.
190190 
191Use `instructions` (csharp, go: `Instructions`) for high-level synthesis guidance such as focus areas ("focus on coding-style preferences"), content to preserve unchanged, or output conventions you want applied across the store. The pipeline is a synthesis pass over the inputs, not an editor applied to the text of the store, so imperative directives that target specific lines ("change sentence X to Y," "fix the count in section Z") generally produce no change. To make targeted edits to individual memories, use the [Memory Stores API](https://platform.claude.com/docs/en/managed-agents/memory#view-and-edit-memories) on the output store directly.
191Use `instructions` (csharp, go: `Instructions`) for high-level synthesis guidance such as focus areas (“focus on coding-style preferences”), content to preserve unchanged, or output conventions you want applied across the store. The pipeline is a synthesis pass over the inputs, not an editor applied to the text of the store, so imperative directives that target specific lines (“change sentence X to Y,” “fix the count in section Z”) generally produce no change. To make targeted edits to individual memories, use the [Memory Stores API](https://platform.claude.com/docs/en/managed-agents/memory#view-and-edit-memories) on the output store directly.
192192 
193193## Track progress
194194 
from line 279
279279 
280280### Watch the pipeline run
281281 
282Once a dream is `running`, its `session_id` (java: `sessionId`; php: `sessionID`; csharp, go: `SessionID`) field points at the underlying [session](https://platform.claude.com/docs/en/managed-agents/sessions) running the pipeline. You can stream that session's [events](https://platform.claude.com/docs/en/managed-agents/events-and-streaming) to observe what the dream is reading and writing in real time. The session is archived (not deleted) when the dream reaches a terminal state, so the transcript remains available afterward.
282Once a dream is `running`, its `session_id` (java: `sessionId`; php: `sessionID`; csharp, go: `SessionID`) field points at the underlying [session](https://platform.claude.com/docs/en/managed-agents/sessions) running the pipeline. You can stream that session’s [events](https://platform.claude.com/docs/en/managed-agents/events-and-streaming) to observe what the dream is reading and writing in real time. The session is archived (not deleted) when the dream reaches a terminal state, so the transcript remains available afterward.
283283 
284284## Use the output
285285 
286When `status` (csharp, go: `Status`) reaches `completed`, the `memory_store` entry in `outputs` (csharp, go: `Outputs`) references a fully populated store. It's an ordinary memory store in your workspace. Review it with the [Memory Stores API](https://platform.claude.com/docs/en/managed-agents/memory#view-and-edit-memories) or in the Claude Console, then either:
286When `status` (csharp, go: `Status`) reaches `completed`, the `memory_store` entry in `outputs` (csharp, go: `Outputs`) references a fully populated store. It’s an ordinary memory store in your workspace. Review it with the [Memory Stores API](https://platform.claude.com/docs/en/managed-agents/memory#view-and-edit-memories) or in the Claude Console, then either:
287287 
288288* **Use it:** attach it to future sessions as a `memory_store` resource in place of (or alongside) the input memory store, or
289289* **Discard it:** [delete the memory store](https://platform.claude.com/docs/en/api/beta/memory_stores/delete) or [archive the memory store](https://platform.claude.com/docs/en/api/beta/memory_stores/archive).
from line 430
430430 ```
431431</CodeGroup>
432432 
433The dream itself never deletes or modifies its inputs. On `failed` or `canceled` the output store persists with partial contents so you can inspect what was produced before stopping; clean it up through the Memory Stores API if you don't need it.
433The dream itself never deletes or modifies its inputs. On `failed` or `canceled` the output store persists with partial contents so you can inspect what was produced before stopping; clean it up through the Memory Stores API if you don’t need it.
434434 
435435<Warning>
436436 While a dream is `pending` or `running`, the 400 guard applies to archiving the dream itself, not its stores. Archiving or deleting an *input* memory store mid-run (or deleting an input session) will cause the dream to fail with `input_memory_store_unavailable` or `input_session_unavailable`.
from line 441
441441Cancel moves a `pending` or `running` dream to `canceled` immediately. Canceling an already-`canceled` dream is an idempotent no-op; canceling a `completed` or `failed` dream returns 400.
442442 
443443<Note>
444 After cancellation, the dream's `usage` (csharp, go: `Usage`) fields might continue to update for a few seconds while in-flight work winds down. Poll the dream until `usage` (csharp, go: `Usage`) stabilizes if you need the final count.
444 After cancellation, the dream’s `usage` (csharp, go: `Usage`) fields might continue to update for a few seconds while in-flight work winds down. Poll the dream until `usage` (csharp, go: `Usage`) stabilizes if you need the final count.
445445</Note>
446446 
447447<CodeGroup>
from line 618
618618| `timeout` | The pipeline exceeded its runtime budget. |
619619| `internal_error` | Unclassified pipeline failure. |
620620| `memory_store_org_limit_exceeded` | Your organization hit its memory-store cap while the pipeline was provisioning working storage. |
621| `input_memory_store_too_large` | The input memory store exceeds the pipeline's size limit. |
621| `input_memory_store_too_large` | The input memory store exceeds the pipeline’s size limit. |
622622| `input_memory_store_unavailable` | The input memory store was archived or deleted after the dream was created. |
623623| `input_session_unavailable` | An input session was deleted after the dream was created. |
624624 
625625## Billing
626626 
627Dreams are billed at standard API token rates for the model you select; `usage` (csharp, go: `Usage`) on the resource reports the exact totals. Cost scales roughly linearly with the number and length of input sessions. Start with a small batch of sessions and scale up once you're satisfied with the curation quality.
627Dreams are billed at standard API token rates for the model you select; `usage` (csharp, go: `Usage`) on the resource reports the exact totals. Cost scales roughly linearly with the number and length of input sessions. Start with a small batch of sessions and scale up once you’re satisfied with the curation quality.
628628 
629629## Limits
630630 

managed-agents/environments Changed · +10 / -10 lines

from line 409
409409 
410410### Networking
411411 
412The `networking` (csharp, go: `Networking`) field controls the sandbox's outbound network access.
412The `networking` (csharp, go: `Networking`) field controls the sandbox’s outbound network access.
413413 
414With `limited` networking, `allowed_hosts` (java, php: `allowedHosts`; csharp, go: `AllowedHosts`) also applies to the `web_search` and `web_fetch` tools, which run on Anthropic's servers. A `web_fetch` call for a URL on a host that `allowed_hosts` (java, php: `allowedHosts`; csharp, go: `AllowedHosts`) does not match returns an error result to the agent. `web_search` omits results from hosts that `allowed_hosts` (java, php: `allowedHosts`; csharp, go: `AllowedHosts`) does not match. `allow_package_managers` (java, php: `allowPackageManagers`; csharp, go: `AllowPackageManagers`) and `allow_mcp_servers` (java: `allowMcpServers`; php: `allowMCPServers`; csharp: `AllowMcpServers`; go: `AllowMCPServers`) add no hosts for these tools. When `allowed_hosts` (java, php: `allowedHosts`; csharp, go: `AllowedHosts`) lists no hosts, no `web_fetch` or `web_search` call returns a page or a search result. A host that you add to `allowed_hosts` (java, php: `allowedHosts`; csharp, go: `AllowedHosts`) for these tools is also open to the sandbox. `unrestricted` networking and self-hosted environments do not limit these tools. To restrict them further, set `allowed_domains` (java, php: `allowedDomains`; csharp, go: `AllowedDomains`) or `blocked_domains` (java, php: `blockedDomains`; csharp, go: `BlockedDomains`) on the tool's entry in the agent toolset. See [Restrict web search and web fetch domains](https://platform.claude.com/docs/en/managed-agents/tools-web-restrictions).
414With `limited` networking, `allowed_hosts` (java, php: `allowedHosts`; csharp, go: `AllowedHosts`) also applies to the `web_search` and `web_fetch` tools, which run on Anthropic’s servers. A `web_fetch` call for a URL on a host that `allowed_hosts` (java, php: `allowedHosts`; csharp, go: `AllowedHosts`) does not match returns an error result to the agent. `web_search` omits results from hosts that `allowed_hosts` (java, php: `allowedHosts`; csharp, go: `AllowedHosts`) does not match. `allow_package_managers` (java, php: `allowPackageManagers`; csharp, go: `AllowPackageManagers`) and `allow_mcp_servers` (java: `allowMcpServers`; php: `allowMCPServers`; csharp: `AllowMcpServers`; go: `AllowMCPServers`) add no hosts for these tools. When `allowed_hosts` (java, php: `allowedHosts`; csharp, go: `AllowedHosts`) lists no hosts, no `web_fetch` or `web_search` call returns a page or a search result. A host that you add to `allowed_hosts` (java, php: `allowedHosts`; csharp, go: `AllowedHosts`) for these tools is also open to the sandbox. `unrestricted` networking and self-hosted environments do not limit these tools. To restrict them further, set `allowed_domains` (java, php: `allowedDomains`; csharp, go: `AllowedDomains`) or `blocked_domains` (java, php: `blockedDomains`; csharp, go: `BlockedDomains`) on the tool’s entry in the agent toolset. See [Restrict web search and web fetch domains](https://platform.claude.com/docs/en/managed-agents/tools-web-restrictions).
415415 
416416| Mode | Description |
417417| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
from line 419
419419| `unrestricted` | Full outbound network access, except for a general safety blocklist. Before you use it, read [Risks of unrestricted networking](https://platform.claude.com/docs/en/managed-agents/environments#risks-of-unrestricted-networking). |
420420 
421421<Note>
422 Set `networking` (csharp, go: `Networking`) explicitly in API requests; a create request that omits it gets `unrestricted`. The Claude Console's form for creating an environment starts with **Limited** selected and nothing else allowed.
422 Set `networking` (csharp, go: `Networking`) explicitly in API requests; a create request that omits it gets `unrestricted`. The Claude Console’s form for creating an environment starts with **Limited** selected and nothing else allowed.
423423</Note>
424424 
425425The following example creates an environment with `limited` networking:
from line 611
611611| Containers | `registry-1.docker.io`, `auth.docker.io`, `production.cloudflare.docker.com`, `download.docker.com`, `ghcr.io` |
612612 
613613<Warning>
614 Network access is granted by host, not by operation. The sandbox can send any request to an allowed host, including uploads such as `git push` and package publishing, with any credential the command supplies. If the agent processes untrusted input (repository files, fetched web content, or third-party tool output), a successful prompt injection could use an allowed host to copy files out of the sandbox. To reduce this risk, set the `bash` tool's [permission policy](https://platform.claude.com/docs/en/managed-agents/permission-policies) to `always_ask` or `auto`. If the environment does not specify `packages` (csharp, go: `Packages`), you can instead leave `allow_package_managers` (java, php: `allowPackageManagers`; csharp, go: `AllowPackageManagers`) set to `false` and list only the hosts your agent needs in `allowed_hosts` (java, php: `allowedHosts`; csharp, go: `AllowedHosts`).
614 Network access is granted by host, not by operation. The sandbox can send any request to an allowed host, including uploads such as `git push` and package publishing, with any credential the command supplies. If the agent processes untrusted input (repository files, fetched web content, or third-party tool output), a successful prompt injection could use an allowed host to copy files out of the sandbox. To reduce this risk, set the `bash` tool’s [permission policy](https://platform.claude.com/docs/en/managed-agents/permission-policies) to `always_ask` or `auto`. If the environment does not specify `packages` (csharp, go: `Packages`), you can instead leave `allow_package_managers` (java, php: `allowPackageManagers`; csharp, go: `AllowPackageManagers`) set to `false` and list only the hosts your agent needs in `allowed_hosts` (java, php: `allowedHosts`; csharp, go: `AllowedHosts`).
615615</Warning>
616616 
617617#### Risks of unrestricted networking
from line 619
619619With `unrestricted` networking, code in the sandbox can send requests to any host on the internet, except for hosts on a general safety blocklist. Before you choose this mode, consider what the agent can do with that access:
620620 
621621* **The agent can change things on external sites, not only read them:** The `bash` tool can send any request. The agent can post data, submit forms, call APIs, and run scripts that change data on external sites. Even a request that only fetches a URL can change data on some sites.
622* **Nothing pauses these requests by default:** The agent toolset's default [permission policy](https://platform.claude.com/docs/en/managed-agents/permission-policies) is `always_allow`, so `bash` commands run without approval.
622* **Nothing pauses these requests by default:** The agent toolset’s default [permission policy](https://platform.claude.com/docs/en/managed-agents/permission-policies) is `always_allow`, so `bash` commands run without approval.
623623* **Anything in the sandbox can leave it:** This includes files, tool outputs, and any credentials or secrets you put in the sandbox.
624624* **Fetched content can steer the agent:** Webpages, API responses, and other content the agent reads can contain instructions (prompt injection) that change what it does next.
625* **The agent acts on your behalf:** Its actions can violate a site's terms of service, or create accounts and records there.
625* **The agent acts on your behalf:** Its actions can violate a site’s terms of service, or create accounts and records there.
626626* **Model behavior is not a security control:** The agent can act on external sites in ways you did not ask for, including retrying in a different way after a site blocks a request. Use network settings and permission policies to limit what it can do.
627627* **The safety blocklist is not an allowlist:** It does not limit which other sites the agent reaches, or what the agent does on them.
628628 
from line 636
636636}
637637```
638638 
639An agent that only uses the `web_search` and `web_fetch` tools does not need `unrestricted` networking if you can list the sites it needs. With `limited` networking, `allowed_hosts` (java, php: `allowedHosts`; csharp, go: `AllowedHosts`) also applies to those tools (see [Networking](https://platform.claude.com/docs/en/managed-agents/environments#networking)), so list those sites in `allowed_hosts` (java, php: `allowedHosts`; csharp, go: `AllowedHosts`). Listing them in `web_search`'s `allowed_domains` (java, php: `allowedDomains`; csharp, go: `AllowedDomains`) too makes it search those sites. A host that you add to `allowed_hosts` (java, php: `allowedHosts`; csharp, go: `AllowedHosts`) is also open to the sandbox. To restrict the tools further, see [Restrict web search and web fetch domains](https://platform.claude.com/docs/en/managed-agents/tools-web-restrictions).
639An agent that only uses the `web_search` and `web_fetch` tools does not need `unrestricted` networking if you can list the sites it needs. With `limited` networking, `allowed_hosts` (java, php: `allowedHosts`; csharp, go: `AllowedHosts`) also applies to those tools (see [Networking](https://platform.claude.com/docs/en/managed-agents/environments#networking)), so list those sites in `allowed_hosts` (java, php: `allowedHosts`; csharp, go: `AllowedHosts`). Listing them in `web_search`’s `allowed_domains` (java, php: `allowedDomains`; csharp, go: `AllowedDomains`) too makes it search those sites. A host that you add to `allowed_hosts` (java, php: `allowedHosts`; csharp, go: `AllowedHosts`) is also open to the sandbox. To restrict the tools further, see [Restrict web search and web fetch domains](https://platform.claude.com/docs/en/managed-agents/tools-web-restrictions).
640640 
641Use `unrestricted` only when the agent must reach sites you cannot list in advance. In that case, keep secrets and sensitive files out of the sandbox, and give the agent only the credentials the task needs. Consider setting the `bash` tool's permission policy to `always_ask` or `auto`, and [watch the session's events](https://platform.claude.com/docs/en/managed-agents/events-and-streaming).
641Use `unrestricted` only when the agent must reach sites you cannot list in advance. In that case, keep secrets and sensitive files out of the sandbox, and give the agent only the credentials the task needs. Consider setting the `bash` tool’s permission policy to `always_ask` or `auto`, and [watch the session’s events](https://platform.claude.com/docs/en/managed-agents/events-and-streaming).
642642 
643643## Environment lifecycle
644644 

managed-agents/event-deltas Changed · +16 / -16 lines

from line 10
1010 betaHeader: managed-agents-2026-04-01
1111---
1212 
13By default, the agent's response text reaches the [session event stream](https://platform.claude.com/docs/en/managed-agents/events-and-streaming) as buffered `agent.message` events. Each one is emitted only after the model request that produced it completes. Event deltas let you render that text incrementally, as a live preview, while the model is still generating it.
13By default, the agent’s response text reaches the [session event stream](https://platform.claude.com/docs/en/managed-agents/events-and-streaming) as buffered `agent.message` events. Each one is emitted only after the model request that produced it completes. Event deltas let you render that text incrementally, as a live preview, while the model is still generating it.
1414 
1515Previews are a best-effort display aid, and the buffered `agent.message` is always the authoritative record. A client that ignores previews still receives a complete, correct stream.
1616 
1717## Opt in to previews
1818 
19Previews are opt-in for each stream connection. Add the `event_deltas[]` query parameter to the stream you're reading, and repeat it once for each event type you want previewed. The accepted values are `agent.message` and `agent.thinking`. Any other value returns a 400 error, as does a request with more than 100 values.
19Previews are opt-in for each stream connection. Add the `event_deltas[]` query parameter to the stream you’re reading, and repeat it once for each event type you want previewed. The accepted values are `agent.message` and `agent.thinking`. Any other value returns a 400 error, as does a request with more than 100 values.
2020 
2121Both stream endpoints accept the parameter:
2222 
from line 23
2323* **Session-level stream:** `GET /v1/sessions/{session_id}/events/stream`
2424* **Session thread stream:** `GET /v1/sessions/{session_id}/threads/{thread_id}/stream`
2525 
26A subagent's previews appear on [that subagent's own thread stream](https://platform.claude.com/docs/en/managed-agents/event-deltas#preview-session-thread-events).
26A subagent’s previews appear on [that subagent’s own thread stream](https://platform.claude.com/docs/en/managed-agents/event-deltas#preview-session-thread-events).
2727 
2828`[]` is a shell glob pattern, so quote the URL whenever you build the request in a shell. The examples percent-encode the brackets as `%5B%5D`, which also works.
2929 
3030### Preview events
3131 
32When a previewed event starts, the stream emits an `event_start` carrying the upcoming event's type and `id`:
32When a previewed event starts, the stream emits an `event_start` carrying the upcoming event’s type and `id`:
3333 
3434```json
3535{
from line 80
80804. The buffered `agent.message`
81815. [`span.model_request_end`](https://platform.claude.com/docs/en/managed-agents/reference#event-types) (in the **Span events** tab)
8282 
83On the wire, this is the previewed portion of that sequence, interleaved with the connection's other buffered events:
83On the wire, this is the previewed portion of that sequence, interleaved with the connection’s other buffered events:
8484 
8585```text wrap
8686event_start {"event": {"type": "agent.message", "id": "sevt_01abc..."}}
from line 91
9191 
9292The `event_delta` line repeats once for each text fragment. Process each event as it arrives:
9393 
941. On `event_start`, note the announced `id`. The identifiers always line up: `event_start.event.id`, every `event_delta.event_id`, and the buffered `agent.message`'s `id` are the same value.
941. On `event_start`, note the announced `id`. The identifiers always line up: `event_start.event.id`, every `event_delta.event_id`, and the buffered `agent.message`’s `id` are the same value.
95952. On each `event_delta`, append `delta.content.text` to the entry at `(event_id, delta.index)` and render the running text. The first delta for an `index` creates that entry.
963. When the buffered `agent.message` arrives, match it by `id`, discard the accumulated preview, and render the message's content instead.
963. When the buffered `agent.message` arrives, match it by `id`, discard the accumulated preview, and render the message’s content instead.
97974. On `span.model_request_end`, close any preview that has not been reconciled by its buffered event. No more deltas are coming for it. If the turn errors or is interrupted, the buffered event might never arrive, but `span.model_request_end` still does.
9898 
9999The pattern relies on two guarantees:
100100 
101* Concatenating a preview's deltas in arrival order, keyed by `(event_id, index)`, gives a prefix of `content[index].text` in the buffered event. It is not necessarily the whole text, because deltas might be [shed under load](https://platform.claude.com/docs/en/managed-agents/event-deltas#limitations).
101* Concatenating a preview’s deltas in arrival order, keyed by `(event_id, index)`, gives a prefix of `content[index].text` in the buffered event. It is not necessarily the whole text, because deltas might be [shed under load](https://platform.claude.com/docs/en/managed-agents/event-deltas#limitations).
102102* A connection emits at most one `event_start` for each `event_id`, and the buffered event is the last thing that connection delivers for that `id`.
103103 
104104### SDK accumulator helpers
105105 
106Each SDK's helper handles the `index` (csharp, go: `Index`) bookkeeping. The Go, Java, Ruby, and C# helpers also key the accumulating preview by the event's `id` (csharp, go: `ID`). With the Python, TypeScript, and PHP helpers, keep that map yourself and fold each delta into the entry for its `id` (csharp, go: `ID`).
106Each SDK’s helper handles the `index` (csharp, go: `Index`) bookkeeping. The Go, Java, Ruby, and C# helpers also key the accumulating preview by the event’s `id` (csharp, go: `ID`). With the Python, TypeScript, and PHP helpers, keep that map yourself and fold each delta into the entry for its `id` (csharp, go: `ID`).
107107 
108108The following examples opt in to `agent.message` previews and reconcile them with the buffered event:
109109 
from line 558
558558 
559559In a [multiagent](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration) session, every session thread has its own event stream. It takes the same `event_deltas[]` parameter with the same values.
560560 
561A connection previews only the thread it's reading. The session-level stream previews the primary thread, and a child thread's previews are never cross-posted to it. To watch a subagent's text as the model generates it, open that subagent's thread stream.
561A connection previews only the thread it’s reading. The session-level stream previews the primary thread, and a child thread’s previews are never cross-posted to it. To watch a subagent’s text as the model generates it, open that subagent’s thread stream.
562562 
563A thread stream's path ends in `/threads/{thread_id}/stream`. `/events/stream` exists only at the session level, so there is no `/threads/{thread_id}/events/stream` endpoint.
563A thread stream’s path ends in `/threads/{thread_id}/stream`. `/events/stream` exists only at the session level, so there is no `/threads/{thread_id}/events/stream` endpoint.
564564 
565565`event_start` and `event_delta` have the same shape on a thread stream as on the session-level stream, and the [accumulate and reconcile](https://platform.claude.com/docs/en/managed-agents/event-deltas#accumulate-and-reconcile) pattern applies as written. Run one accumulator instance for each stream connection.
566566 
from line 853
853853 ```
854854</CodeGroup>
855855 
856The read loop exits on [`session.thread_status_idle`](https://platform.claude.com/docs/en/managed-agents/reference#event-types), the event emitted when the session thread's turn completes and the thread goes idle.
856The read loop exits on [`session.thread_status_idle`](https://platform.claude.com/docs/en/managed-agents/reference#event-types), the event emitted when the session thread’s turn completes and the thread goes idle.
857857 
858858## Limitations
859859 
from line 860
860860* **Best effort:** Under load, the server might shed deltas for an event. When it does, you receive a contiguous prefix of the text and then no further deltas for that event. The buffered `agent.message` still arrives complete. Never treat an accumulated preview as final.
861861* **No replay on reconnect:** Deltas are delivered only to the connection that opted in, while it is open. This applies to the session-level stream and to each session thread stream alike. A connection opened after a model request started receives no deltas for that in-flight event. There is no way to re-request missed deltas.
862862* **One thread, text only:** Previews cover assistant text on [the thread the connection is reading](https://platform.claude.com/docs/en/managed-agents/event-deltas#preview-session-thread-events). Tool use, tool results, and MCP results are never previewed.
863* **Never persisted:** `event_start` and `event_delta` exist only on the live stream. They do not appear in the session's event history (`GET /v1/sessions/{session_id}/events`) or in any session thread's event history.
863* **Never persisted:** `event_start` and `event_delta` exist only on the live stream. They do not appear in the session’s event history (`GET /v1/sessions/{session_id}/events`) or in any session thread’s event history.
864864 
865865## Troubleshoot previews
866866 
867867| You see | What it means |
868868| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
869| A stream with buffered events but no `event_start` or `event_delta` | The connection you're reading didn't opt in, or the turn never touched the thread you're streaming. `event_deltas[]` applies per connection, not per session. To find which thread ran, list the session's threads (`GET /v1/sessions/{session_id}/threads`). |
869| A stream with buffered events but no `event_start` or `event_delta` | The connection you’re reading didn’t opt in, or the turn never touched the thread you’re streaming. `event_deltas[]` applies per connection, not per session. To find which thread ran, list the session’s threads (`GET /v1/sessions/{session_id}/threads`). |
870870| A stream that drops during a preview | Deltas are not replayed. Follow the [reconnect procedure](https://platform.claude.com/docs/en/managed-agents/events-and-streaming#reconnect-without-missing-events): reopen the stream and list the event history. The history includes any buffered events emitted while you were disconnected, including the `agent.message` your preview was waiting for. |
871| A 404 on the stream URL | The path or an ID is wrong, or the request carries no managed-agents beta header at all. The thread endpoints are beta-gated, so without the header they don't exist. |
871| A 404 on the stream URL | The path or an ID is wrong, or the request carries no managed-agents beta header at all. The thread endpoints are beta-gated, so without the header they don’t exist. |
872872| A 400 naming `event_deltas` | Only `agent.message` and `agent.thinking` are accepted. |
873873 
874874## Next steps

managed-agents/events-and-streaming Changed · +15 / -15 lines

from line 17
1717Events flow in two directions: you send events to the agent, and the session sends events back to you.
1818 
1919* **Events you send:** `user.*` events start a session and steer it as it progresses. [`system.message`](https://platform.claude.com/docs/en/managed-agents/events-and-streaming#send-system-messages) events append system-level context.
20* **Events you receive:** Session events, span events, and agent events report the session's state and the agent's progress.
20* **Events you receive:** Session events, span events, and agent events report the session’s state and the agent’s progress.
2121 
2222These event type strings follow a `{domain}.{action}` naming convention. See [Event types](https://platform.claude.com/docs/en/managed-agents/reference#event-types) in the reference for the full catalog.
2323 
24[Webhook event types](https://platform.claude.com/docs/en/managed-agents/webhooks#supported-event-types) are separate, and some of their names differ from the stream's. For example, webhooks use `session.status_idled` rather than `session.status_idle`.
24[Webhook event types](https://platform.claude.com/docs/en/managed-agents/webhooks#supported-event-types) are separate, and some of their names differ from the stream’s. For example, webhooks use `session.status_idled` rather than `session.status_idle`.
2525 
2626## Stream events
2727 
from line 274
274274 
275275The session reports errors through the `session.error` event. See [Event types](https://platform.claude.com/docs/en/managed-agents/reference#event-types) for its fields.
276276 
277The agent's response text arrives as buffered `agent.message` events, each emitted only after the model request that produced it completes. To render the text while the model is still generating it, see [Preview responses with event deltas](https://platform.claude.com/docs/en/managed-agents/event-deltas).
277The agent’s response text arrives as buffered `agent.message` events, each emitted only after the model request that produced it completes. To render the text while the model is still generating it, see [Preview responses with event deltas](https://platform.claude.com/docs/en/managed-agents/event-deltas).
278278 
279279### Reconnect without missing events
280280 
from line 499
499499 
500500## Send events
501501 
502Send a `user.message` event to start or continue the agent's work:
502Send a `user.message` event to start or continue the agent’s work:
503503 
504504<CodeGroup>
505505 ```bash cURL
from line 643
643643 ```
644644</CodeGroup>
645645 
646Every event in the session's history includes a `processed_at` (java, php: `processedAt`; csharp, go: `ProcessedAt`) timestamp, set when the event finishes processing. On events you send, `processed_at` (java, php: `processedAt`; csharp, go: `ProcessedAt`) is `null` (python: `None`; ruby: `nil`; java: `Optional.empty()`; go: `time.Time{}`) while the event is still queued behind earlier events. The exceptions are `user.define_outcome`, `user.custom_tool_result`, and `user.tool_result`, which are processed on receipt and echoed back with `processed_at` (java, php: `processedAt`; csharp, go: `ProcessedAt`) already populated.
646Every event in the session’s history includes a `processed_at` (java, php: `processedAt`; csharp, go: `ProcessedAt`) timestamp, set when the event finishes processing. On events you send, `processed_at` (java, php: `processedAt`; csharp, go: `ProcessedAt`) is `null` (python: `None`; ruby: `nil`; java: `Optional.empty()`; go: `time.Time{}`) while the event is still queued behind earlier events. The exceptions are `user.define_outcome`, `user.custom_tool_result`, and `user.tool_result`, which are processed on receipt and echoed back with `processed_at` (java, php: `processedAt`; csharp, go: `ProcessedAt`) already populated.
647647 
648648## Respond when the session goes idle
649649 
from line 653
653653| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
654654| `end_turn` | The agent finished its turn, or [you interrupted it](https://platform.claude.com/docs/en/managed-agents/events-and-streaming#interrupt-the-agent). | [Send a `user.message`](https://platform.claude.com/docs/en/managed-agents/events-and-streaming#resume-an-idle-session) when you have more work for the agent. |
655655| `requires_action` | One or more tool calls need an answer from you, such as a custom tool call or a confirmation request. | [Answer each blocking tool call](https://platform.claude.com/docs/en/managed-agents/events-and-streaming#answer-tool-calls-that-pause-the-session). |
656| `budget_reached` | The session's tracked list cost reached its [budget](https://platform.claude.com/docs/en/managed-agents/budgets). | [Change or remove the budget](https://platform.claude.com/docs/en/managed-agents/budgets#resume-a-session-at-its-budget). |
656| `budget_reached` | The session’s tracked list cost reached its [budget](https://platform.claude.com/docs/en/managed-agents/budgets). | [Change or remove the budget](https://platform.claude.com/docs/en/managed-agents/budgets#resume-a-session-at-its-budget). |
657657 
658658No event you send resumes a session paused at its budget. The work that the budget paused resumes automatically when you change the budget to a value above the consumed list cost, or remove it. When the work resumes, the session emits a `workflow_run.status_running` event for each [workflow run that the budget paused](https://platform.claude.com/docs/en/managed-agents/workflow-runs#budgets-and-limits). A run that an interrupt paused stays paused; see [Resume a session at its budget](https://platform.claude.com/docs/en/managed-agents/budgets#resume-a-session-at-its-budget). See [When a session reaches its budget](https://platform.claude.com/docs/en/managed-agents/budgets#when-a-session-reaches-its-budget) for the events that mark the pause and the events the session still accepts.
659659 
from line 663
663663 
6646641. The session emits the tool call as an `agent.custom_tool_use`, `agent.tool_use`, or `agent.mcp_tool_use` event.
6656652. The session pauses with a `session.status_idle` event whose `stop_reason.type` is `requires_action`. The blocking event IDs are in the `stop_reason.event_ids` array.
6663. For each blocking event ID, send a [`user.custom_tool_result`](https://platform.claude.com/docs/en/managed-agents/events-and-streaming#return-a-custom-tool-result) or a [`user.tool_confirmation`](https://platform.claude.com/docs/en/managed-agents/events-and-streaming#confirm-a-tool-call) event. A custom tool result doesn't have to wait for step 2.
6663. For each blocking event ID, send a [`user.custom_tool_result`](https://platform.claude.com/docs/en/managed-agents/events-and-streaming#return-a-custom-tool-result) or a [`user.tool_confirmation`](https://platform.claude.com/docs/en/managed-agents/events-and-streaming#confirm-a-tool-call) event. A custom tool result doesn’t have to wait for step 2.
6676674. Once all blocking events are resolved, the session transitions back to `running`.
668668 
669In a multiagent session, a subagent's blocking events are cross-posted to the primary thread. See [Tool permissions and custom tools](https://platform.claude.com/docs/en/managed-agents/session-threads#tool-permissions-and-custom-tools).
669In a multiagent session, a subagent’s blocking events are cross-posted to the primary thread. See [Tool permissions and custom tools](https://platform.claude.com/docs/en/managed-agents/session-threads#tool-permissions-and-custom-tools).
670670 
671671### Return a custom tool result
672672 
from line 876
876876 ```
877877</CodeGroup>
878878 
879If the agent has [workflow runs](https://platform.claude.com/docs/en/managed-agents/workflow-runs) open, a custom tool call can arrive while the session stays `running`. The `requires_action` idle event arrives only when none of the [session's threads](https://platform.claude.com/docs/en/managed-agents/session-threads) is working. Don't wait for it: answer each call when its `agent.custom_tool_use` event arrives. The sample in [Follow a run](https://platform.claude.com/docs/en/managed-agents/workflow-runs#follow-a-run) shows how.
879If the agent has [workflow runs](https://platform.claude.com/docs/en/managed-agents/workflow-runs) open, a custom tool call can arrive while the session stays `running`. The `requires_action` idle event arrives only when none of the [session’s threads](https://platform.claude.com/docs/en/managed-agents/session-threads) is working. Don’t wait for it: answer each call when its `agent.custom_tool_use` event arrives. The sample in [Follow a run](https://platform.claude.com/docs/en/managed-agents/workflow-runs#follow-a-run) shows how.
880880 
881881### Confirm a tool call
882882 
from line 1073
10731073 ```
10741074</CodeGroup>
10751075 
1076The previous example approves each call after the session goes idle. If the agent has [workflow runs](https://platform.claude.com/docs/en/managed-agents/workflow-runs) open, a tool call can wait for your confirmation while the session stays `running`. Don't wait for the idle event: when an `agent.tool_use` or `agent.mcp_tool_use` event arrives whose [`evaluated_permission` (java, php: `evaluatedPermission`; csharp, go: `EvaluatedPermission`)](https://platform.claude.com/docs/en/managed-agents/permission-policies#see-how-each-call-was-evaluated) is `ask`, answer it. The sample in [Follow a run](https://platform.claude.com/docs/en/managed-agents/workflow-runs#follow-a-run) answers custom tool calls this way, and its introduction says how to add confirmations.
1076The previous example approves each call after the session goes idle. If the agent has [workflow runs](https://platform.claude.com/docs/en/managed-agents/workflow-runs) open, a tool call can wait for your confirmation while the session stays `running`. Don’t wait for the idle event: when an `agent.tool_use` or `agent.mcp_tool_use` event arrives whose [`evaluated_permission` (java, php: `evaluatedPermission`; csharp, go: `EvaluatedPermission`)](https://platform.claude.com/docs/en/managed-agents/permission-policies#see-how-each-call-was-evaluated) is `ask`, answer it. The sample in [Follow a run](https://platform.claude.com/docs/en/managed-agents/workflow-runs#follow-a-run) answers custom tool calls this way, and its introduction says how to add confirmations.
10771077 
10781078## Resume an idle session
10791079 
from line 1416
14161416 
14171417The call returns as soon as the events are queued. The interrupt then takes effect in this order:
14181418 
14191. The agent applies the interrupt. Until then, the interrupt's `processed_at` (java, php: `processedAt`; csharp, go: `ProcessedAt`) stays `null` (python: `None`; ruby: `nil`; java: `Optional.empty()`; go: `time.Time{}`) and the session stays `running`. A model response in progress stops immediately, but the interrupt can take longer to apply while tool calls are running.
14191. The agent applies the interrupt. Until then, the interrupt’s `processed_at` (java, php: `processedAt`; csharp, go: `ProcessedAt`) stays `null` (python: `None`; ruby: `nil`; java: `Optional.empty()`; go: `time.Time{}`) and the session stays `running`. A model response in progress stops immediately, but the interrupt can take longer to apply while tool calls are running.
142014202. The `user.interrupt` event appears on the stream, and the interrupted turn ends with a `session.status_idle` event.
142114213. The agent starts its next turn with the `user.message` you sent after the interrupt.
14221422 
1423The idle event's `stop_reason.type` is `end_turn`, the same value as a turn that completes on its own. If workflow runs are open, the interrupt ends none of them. A run that's running can keep the session `running`, so the `session.status_idle` event in step 2 might not arrive. See [Interrupt a session with runs open](https://platform.claude.com/docs/en/managed-agents/workflow-runs#interrupt-a-session-with-runs-open).
1423The idle event’s `stop_reason.type` is `end_turn`, the same value as a turn that completes on its own. If workflow runs are open, the interrupt ends none of them. A run that’s running can keep the session `running`, so the `session.status_idle` event in step 2 might not arrive. See [Interrupt a session with runs open](https://platform.claude.com/docs/en/managed-agents/workflow-runs#interrupt-a-session-with-runs-open).
14241424 
14251425## List past events
14261426 
from line 1593
15931593 
15941594On a [supported model](https://platform.claude.com/docs/en/managed-agents/events-and-streaming#supported-models), send a `system.message` event to give the agent privileged system-level context. The context applies to the accompanying turn and all subsequent turns. Use it when the agent needs updated system-level guidance mid-session, for example a different persona, revised constraints, or context fetched at runtime.
15951595 
1596The event's `content` (csharp, go: `Content`) accepts 1–1,000 text items. The content is appended to the session's system context as a `role: "system"` turn. It doesn't replace the top-level system prompt, which the `system` (csharp, go: `System`; ruby: `system_`) field on the agent definition sets.
1596The event’s `content` (csharp, go: `Content`) accepts 1–1,000 text items. The content is appended to the session’s system context as a `role: "system"` turn. It doesn’t replace the top-level system prompt, which the `system` (csharp, go: `System`; ruby: `system_`) field on the agent definition sets.
15971597 
15981598<CodeGroup>
15991599 ```bash cURL
from line 1746
17461746* Claude Sonnet 5.5
17471747* Claude Haiku 5.5
17481748 
1749If the agent's primary model does not support mid-conversation system injection, the event is rejected with a `model_does_not_support_mid_conversation_system` validation error. Subagent models are not checked, because `system.message` lands on the primary thread only.
1749If the agent’s primary model does not support mid-conversation system injection, the event is rejected with a `model_does_not_support_mid_conversation_system` validation error. Subagent models are not checked, because `system.message` lands on the primary thread only.
17501750 
17511751### System messages while a tool call is pending
17521752 
from line 1756
17561756 
17571757<CardGroup cols={2}>
17581758 <Card title="Preview responses with event deltas" icon="text" href="https://platform.claude.com/docs/en/managed-agents/event-deltas">
1759 Render the agent's response text while the model is still generating it.
1759 Render the agent’s response text while the model is still generating it.
17601760 </Card>
17611761 
17621762 <Card title="Inspect sessions and track usage" icon="magnifying-glass" href="https://platform.claude.com/docs/en/managed-agents/session-observability">

managed-agents/github Changed · +3 / -3 lines

from line 223
223223 ```
224224</CodeGroup>
225225 
226Then create a session that mounts the GitHub repository. With a `limited` [environment](https://platform.claude.com/docs/en/managed-agents/environments#networking), session creation fails with a 400 error when the agent declares an MCP server whose host is not in `allowed_hosts` (java, php: `allowedHosts`; csharp, go: `AllowedHosts`). Setting `allow_mcp_servers` (java: `allowMcpServers`; php: `allowMCPServers`; csharp: `AllowMcpServers`; go: `AllowMCPServers`) to `true` in the environment's networking turns this check off.
226Then create a session that mounts the GitHub repository. With a `limited` [environment](https://platform.claude.com/docs/en/managed-agents/environments#networking), session creation fails with a 400 error when the agent declares an MCP server whose host is not in `allowed_hosts` (java, php: `allowedHosts`; csharp, go: `AllowedHosts`). Setting `allow_mcp_servers` (java: `allowMcpServers`; php: `allowMCPServers`; csharp: `AllowMcpServers`; go: `AllowMCPServers`) to `true` in the environment’s networking turns this check off.
227227 
228228<CodeGroup>
229229 ```bash cURL
from line 374
374374| Field | Description |
375375| ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
376376| `type` (csharp, go: `Type`) | Required. Must be `"github_repository"`. |
377| `url` (csharp: `Url`; go: `URL`) | Required. The repository's HTTPS URL in the form `https://github.com/<owner>/<repo>`, without a `.git` suffix. Other forms, including SSH URLs, are rejected with an `invalid_request_error`. |
377| `url` (csharp: `Url`; go: `URL`) | Required. The repository’s HTTPS URL in the form `https://github.com/<owner>/<repo>`, without a `.git` suffix. Other forms, including SSH URLs, are rejected with an `invalid_request_error`. |
378378| `authorization_token` (java, php: `authorizationToken`; csharp, go: `AuthorizationToken`) | Optional. The GitHub token used to clone the repository and to push to it. Without a token, a private repository fails to clone after the session is created. A public repository clones without a token, and the agent can commit in its clone, but `git push` fails. It is not echoed in API responses. See [Token permissions](https://platform.claude.com/docs/en/managed-agents/github#token-permissions). |
379379| `mount_path` (java, php: `mountPath`; csharp, go: `MountPath`) | Optional. The directory under `/workspace` to clone the repository into. Defaults to `/workspace/<repo-name>`. |
380| `checkout` (csharp, go: `Checkout`) | Optional. A branch (`{"type": "branch", "name": "main"}`) or commit (`{"type": "commit", "sha": "..."}`) to check out. Defaults to the repository's default branch. |
380| `checkout` (csharp, go: `Checkout`) | Optional. A branch (`{"type": "branch", "name": "main"}`) or commit (`{"type": "commit", "sha": "..."}`) to check out. Defaults to the repository’s default branch. |
381381 
382382Mounting a repository also loads any skills stored in its root `.claude/skills` directory. Skills are discovered once for each session, from the repository state checked out at session start. See [Load skills from a GitHub repository](https://platform.claude.com/docs/en/managed-agents/skills#load-skills-from-a-github-repository).
383383 

managed-agents/mcp-connector Changed · +5 / -5 lines

from line 23
2323 
2424Specify MCP servers in the `mcp_servers` (java, php: `mcpServers`; csharp: `McpServers`; go: `MCPServers`) array when creating an agent. Each server has a `type` (csharp, go: `Type`) of `url`, a unique `name` (csharp, go: `Name`), and a `url` (csharp: `Url`; go: `URL`). No authentication tokens are provided at this stage.
2525 
26Each declared server also needs a matching `mcp_toolset` entry in the `tools` (csharp, go: `Tools`) array. The toolset's `mcp_server_name` (java, php: `mcpServerName`; csharp: `McpServerName`; go: `MCPServerName`) must match the server's `name` (csharp, go: `Name`).
26Each declared server also needs a matching `mcp_toolset` entry in the `tools` (csharp, go: `Tools`) array. The toolset’s `mcp_server_name` (java, php: `mcpServerName`; csharp: `McpServerName`; go: `MCPServerName`) must match the server’s `name` (csharp, go: `Name`).
2727 
2828<CodeGroup defaultLanguage="CLI">
2929 ```bash cURL
from line 270
270270 
271271When starting a session, pass `vault_ids` (java: `vaultIds`; php: `vaultIDs`; csharp: `VaultIds`; go: `VaultIDs`) to provide credentials for your MCP servers. Vaults are collections of credentials that you register once and reference by ID. See [Authenticate with vaults](https://platform.claude.com/docs/en/managed-agents/vaults) for how to create vaults and manage credentials.
272272 
273With a `limited` [environment](https://platform.claude.com/docs/en/managed-agents/environments#networking), session creation fails with a 400 error when the agent declares an MCP server whose host is not in `allowed_hosts` (java, php: `allowedHosts`; csharp, go: `AllowedHosts`). Setting `allow_mcp_servers` (java: `allowMcpServers`; php: `allowMCPServers`; csharp: `AllowMcpServers`; go: `AllowMCPServers`) to `true` in the environment's networking turns this check off.
273With a `limited` [environment](https://platform.claude.com/docs/en/managed-agents/environments#networking), session creation fails with a 400 error when the agent declares an MCP server whose host is not in `allowed_hosts` (java, php: `allowedHosts`; csharp, go: `AllowedHosts`). Setting `allow_mcp_servers` (java: `allowMcpServers`; php: `allowMCPServers`; csharp: `AllowMcpServers`; go: `AllowMCPServers`) to `true` in the environment’s networking turns this check off.
274274 
275275<CodeGroup>
276276 ```bash cURL
from line 356
356356 ```
357357</CodeGroup>
358358 
359Credentials are matched by URL, so the vault must contain a credential whose `mcp_server_url` (java: `mcpServerUrl`; php: `mcpServerURL`; csharp: `McpServerUrl`; go: `MCPServerURL`) refers to the same server as the `url` (csharp: `Url`; go: `URL`) declared in `mcp_servers` (java, php: `mcpServers`; csharp: `McpServers`; go: `MCPServers`). Both URLs are normalized before matching (scheme and host lowercased, default ports and trailing slashes stripped), so differences in host casing, a default port, or a trailing slash don't prevent a match; a different path, subdomain, or non-default port does. If none matches, the connection is attempted unauthenticated. See [Add a credential](https://platform.claude.com/docs/en/managed-agents/vaults#add-a-credential) for the `static_bearer` and `mcp_oauth` credential types.
359Credentials are matched by URL, so the vault must contain a credential whose `mcp_server_url` (java: `mcpServerUrl`; php: `mcpServerURL`; csharp: `McpServerUrl`; go: `MCPServerURL`) refers to the same server as the `url` (csharp: `Url`; go: `URL`) declared in `mcp_servers` (java, php: `mcpServers`; csharp: `McpServers`; go: `MCPServers`). Both URLs are normalized before matching (scheme and host lowercased, default ports and trailing slashes stripped), so differences in host casing, a default port, or a trailing slash don’t prevent a match; a different path, subdomain, or non-default port does. If none matches, the connection is attempted unauthenticated. See [Add a credential](https://platform.claude.com/docs/en/managed-agents/vaults#add-a-credential) for the `static_bearer` and `mcp_oauth` credential types.
360360 
361361### Handle connection and authentication failures
362362 
363Session creation does not validate MCP connectivity or credentials. It does check each declared server's host against the environment's networking: with a `limited` environment, session creation fails with a 400 error when a host is not allowed, as described under [Provide authentication at session creation](https://platform.claude.com/docs/en/managed-agents/mcp-connector#provide-authentication-at-session-creation). If an MCP server is unreachable or rejects the supplied credential, the session still starts and interaction remains possible. A [`session.error`](https://platform.claude.com/docs/en/managed-agents/reference#event-types) event is emitted with the `mcp_server_name` (java, php: `mcpServerName`; csharp: `McpServerName`; go: `MCPServerName`) of the affected server and a `retry_status` (java, php: `retryStatus`; csharp, go: `RetryStatus`):
363Session creation does not validate MCP connectivity or credentials. It does check each declared server’s host against the environment’s networking: with a `limited` environment, session creation fails with a 400 error when a host is not allowed, as described under [Provide authentication at session creation](https://platform.claude.com/docs/en/managed-agents/mcp-connector#provide-authentication-at-session-creation). If an MCP server is unreachable or rejects the supplied credential, the session still starts and interaction remains possible. A [`session.error`](https://platform.claude.com/docs/en/managed-agents/reference#event-types) event is emitted with the `mcp_server_name` (java, php: `mcpServerName`; csharp: `McpServerName`; go: `MCPServerName`) of the affected server and a `retry_status` (java, php: `retryStatus`; csharp, go: `RetryStatus`):
364364 
365365| Error type | Meaning |
366366| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
from line 367
367367| `mcp_connection_failed_error` | The MCP server could not be reached (network error, timeout, or non-authentication HTTP failure). |
368368| `mcp_authentication_failed_error` | Authentication with the MCP server failed: the server rejected the credential from the attached vault, the server required authentication when no matching credential was configured, or an OAuth token refresh failed. |
369369 
370You can decide whether to block further interaction on this error, trigger a credential rotation, or let the session continue without the affected server's tools. The connection is retried on the next `session.status_idle` to `session.status_running` transition.
370You can decide whether to block further interaction on this error, trigger a credential rotation, or let the session continue without the affected server’s tools. The connection is retried on the next `session.status_idle` to `session.status_running` transition.
371371 
372372## Next steps
373373 

managed-agents/memory Changed · +15 / -15 lines

from line 10
1010Each Managed Agents session starts with a fresh context by default. When a session ends, any state the agent built up is gone. Memory stores let the agent carry information across sessions: user preferences, project conventions, prior mistakes, and domain context.
1111 
1212<Note>
13 Don't combine `agent-memory-2026-07-22` with `managed-agents-2026-04-01` on a memory store request: sending both returns a `400` error. If your code sets beta headers explicitly, replace `managed-agents-2026-04-01` with `agent-memory-2026-07-22` on memory store calls rather than adding a second value. Session endpoints, including attaching a memory store to a session, still use `managed-agents-2026-04-01`.
13 Don’t combine `agent-memory-2026-07-22` with `managed-agents-2026-04-01` on a memory store request: sending both returns a `400` error. If your code sets beta headers explicitly, replace `managed-agents-2026-04-01` with `agent-memory-2026-07-22` on memory store calls rather than adding a second value. Session endpoints, including attaching a memory store to a session, still use `managed-agents-2026-04-01`.
1414 
1515 `GET /v1/memory_stores/{memory_store_id}/memories` behaves the same under either header: results come back in a stable, server-defined order, and `path_prefix` and `depth` apply the same way.
1616</Note>
from line 17
1717 
1818## Overview
1919 
20A **memory store** is a workspace-scoped collection of text documents optimized for Claude. When you attach a store to a session, it is mounted as a directory inside the session's sandbox. The agent reads and writes it with the same file tools it uses for the rest of the filesystem, and a note describing each mount is automatically added to the system prompt, telling the agent where to look. The [agent toolset](https://platform.claude.com/docs/en/managed-agents/tools) is required for these interactions; make sure to enable it during [agent creation](https://platform.claude.com/docs/en/managed-agents/agent-setup). On [self-hosted sandboxes](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes-memory), that directory is not a live mount. Instead, your environment worker downloads each attached store into your sandbox before the agent's tools run and keeps that copy in sync with the store.
20A **memory store** is a workspace-scoped collection of text documents optimized for Claude. When you attach a store to a session, it is mounted as a directory inside the session’s sandbox. The agent reads and writes it with the same file tools it uses for the rest of the filesystem, and a note describing each mount is automatically added to the system prompt, telling the agent where to look. The [agent toolset](https://platform.claude.com/docs/en/managed-agents/tools) is required for these interactions; make sure to enable it during [agent creation](https://platform.claude.com/docs/en/managed-agents/agent-setup). On [self-hosted sandboxes](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes-memory), that directory is not a live mount. Instead, your environment worker downloads each attached store into your sandbox before the agent’s tools run and keeps that copy in sync with the store.
2121 
2222Each **memory** in a store is addressed by a path and can be read and edited directly through the API or the Claude Console, allowing for tuning, importing, and exporting.
2323 
from line 217
217217 
218218## Attach a memory store to a session
219219 
220Memory stores are attached in the session's `resources` (csharp, go: `Resources`) array when the [session is created](https://platform.claude.com/docs/en/managed-agents/sessions#creating-a-session). Unlike file resources, memory stores can only be attached at session creation time; adding or removing one from a running session is not supported. You attach memory stores the same way for sessions on cloud and [self-hosted environments](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes-memory); self-hosted environments accept only `memory_store` resources.
220Memory stores are attached in the session’s `resources` (csharp, go: `Resources`) array when the [session is created](https://platform.claude.com/docs/en/managed-agents/sessions#creating-a-session). Unlike file resources, memory stores can only be attached at session creation time; adding or removing one from a running session is not supported. You attach memory stores the same way for sessions on cloud and [self-hosted environments](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes-memory); self-hosted environments accept only `memory_store` resources.
221221 
222Optionally include `instructions` (csharp, go: `Instructions`) to provide session-specific guidance for how the agent should use this store. It is shown to the agent alongside the store's `name` (csharp, go: `Name`) and `description` (csharp, go: `Description`), and is capped at 4,096 characters.
222Optionally include `instructions` (csharp, go: `Instructions`) to provide session-specific guidance for how the agent should use this store. It is shown to the agent alongside the store’s `name` (csharp, go: `Name`) and `description` (csharp, go: `Description`), and is capped at 4,096 characters.
223223 
224224You can configure `access` (csharp, go: `Access`) as well. It defaults to `read_write` (shown explicitly in the following example), but `read_only` is also supported.
225225 
from line 375
375375 
376376A maximum of **8 memory stores** is supported for each session. Attach multiple stores when different parts of memory have different owners or access rules. Common reasons:
377377 
378* **Shared reference material:** one read-only store attached to many sessions (standards, conventions, domain knowledge), kept separate from each session's own read-write store
379* **Mapping to your product's structure:** one store for each end user, team, or project, while sharing a single agent configuration
378* **Shared reference material:** one read-only store attached to many sessions (standards, conventions, domain knowledge), kept separate from each session’s own read-write store
379* **Mapping to your product’s structure:** one store for each end user, team, or project, while sharing a single agent configuration
380380* **Different lifecycles:** a store that outlives any single session, or one you want to archive on its own schedule
381381 
382382### How the agent accesses memory
383383 
384Each attached store is mounted inside the session's sandbox as a directory under `/mnt/memory/`. The directory name is the store's display name sanitized to a filesystem-safe slug (lowercased, with each non-alphanumeric run replaced by a single hyphen), so a store named "Demo Memory" mounts at `/mnt/memory/demo-memory/`. The exact path is returned in the `mount_path` (java, php: `mountPath`; csharp, go: `MountPath`) field on the session's memory-store resource; read it from there rather than constructing it yourself. The agent reads and writes the store with the standard [agent toolset](https://platform.claude.com/docs/en/managed-agents/tools). Writes under the mount path are persisted back to the store and stay in sync across sessions that share it; writes to any other path under `/mnt/memory/` fail, because the sandbox mounts that parent directory read-only. A short description of each mount (display name, mount path, access mode, store `description` (csharp, go: `Description`), and any `instructions` (csharp, go: `Instructions`)) is automatically added to the system prompt.
384Each attached store is mounted inside the session’s sandbox as a directory under `/mnt/memory/`. The directory name is the store’s display name sanitized to a filesystem-safe slug (lowercased, with each non-alphanumeric run replaced by a single hyphen), so a store named “Demo Memory” mounts at `/mnt/memory/demo-memory/`. The exact path is returned in the `mount_path` (java, php: `mountPath`; csharp, go: `MountPath`) field on the session’s memory-store resource; read it from there rather than constructing it yourself. The agent reads and writes the store with the standard [agent toolset](https://platform.claude.com/docs/en/managed-agents/tools). Writes under the mount path are persisted back to the store and stay in sync across sessions that share it; writes to any other path under `/mnt/memory/` fail, because the sandbox mounts that parent directory read-only. A short description of each mount (display name, mount path, access mode, store `description` (csharp, go: `Description`), and any `instructions` (csharp, go: `Instructions`)) is automatically added to the system prompt.
385385 
386386`access` (csharp, go: `Access`) is enforced at the filesystem level: a `read_only` mount rejects writes, whereas writes to a `read_write` mount produce [memory versions](https://platform.claude.com/docs/en/managed-agents/memory#audit-memory-changes) attributed to the session.
387387 
388388<Note>
389 On [self-hosted sandboxes](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes-memory), each store's directory is a local copy that the worker manages rather than a live mount. The worker reconciles each copy with its store after tool calls, at most once per sync interval (15 seconds by default), and once more when the session ends. The agent's `write` and `edit` tools change only the local copy; the worker uploads those changes at its next sync, so another session running on a self-hosted sandbox sees a change only after both workers have synced. Paths under `/mnt/memory/` outside the store directories are not scratch space there: the worker's file tools refuse to write to them, and anything a shell command writes there is never synced to a store.
389 On [self-hosted sandboxes](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes-memory), each store’s directory is a local copy that the worker manages rather than a live mount. The worker reconciles each copy with its store after tool calls, at most once per sync interval (15 seconds by default), and once more when the session ends. The agent’s `write` and `edit` tools change only the local copy; the worker uploads those changes at its next sync, so another session running on a self-hosted sandbox sees a change only after both workers have synced. Paths under `/mnt/memory/` outside the store directories are not scratch space there: the worker’s file tools refuse to write to them, and anything a shell command writes there is never synced to a store.
390390 
391 For a `read_only` store, the worker's `write` and `edit` tools refuse changes under that directory and the worker never uploads anything from it. To learn how the worker resolves write conflicts, and what the `bash` tool can still change in a read-only store's local copy, see [Read-only stores and conflicts](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes-memory#read-only-stores-and-conflicts).
391 For a `read_only` store, the worker’s `write` and `edit` tools refuse changes under that directory and the worker never uploads anything from it. To learn how the worker resolves write conflicts, and what the `bash` tool can still change in a read-only store’s local copy, see [Read-only stores and conflicts](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes-memory#read-only-stores-and-conflicts).
392392</Note>
393393 
394The agent's reads and writes appear in the [event stream](https://platform.claude.com/docs/en/managed-agents/events-and-streaming) as ordinary `agent.tool_use` and `agent.tool_result` events for whichever tool touched the mount.
394The agent’s reads and writes appear in the [event stream](https://platform.claude.com/docs/en/managed-agents/events-and-streaming) as ordinary `agent.tool_use` and `agent.tool_result` events for whichever tool touched the mount.
395395 
396396## View and edit memories
397397 
from line 922
922922 
923923### List versions
924924 
925List version history for a store, newest first. The example filters to a single memory's history:
925List version history for a store, newest first. The example filters to a single memory’s history:
926926 
927927<CodeGroup>
928928 ```bash cURL
from line 1328
13281328 
13291329## Best practices for memory management
13301330 
1331When a store reaches its 10,000-memory limit, writes to new memories fail: both direct `POST /v1/memory_stores/{memory_store_id}/memories` (python, ruby: `client.beta.memory_stores.memories.create()`; typescript: `client.beta.memoryStores.memories.create()`; go: `client.Beta.MemoryStores.Memories.New()`; java: `client.beta().memoryStores().memories().create()`; csharp: `client.Beta.MemoryStores.Memories.Create()`; php: `$client->beta->memoryStores->memories->create()`; cli: `ant beta:memory-stores:memories create`) calls and the agent's file writes to unmapped paths. Existing memories remain readable and editable. The following practices help you stay well under the limit and recover gracefully if you reach it.
1331When a store reaches its 10,000-memory limit, writes to new memories fail: both direct `POST /v1/memory_stores/{memory_store_id}/memories` (python, ruby: `client.beta.memory_stores.memories.create()`; typescript: `client.beta.memoryStores.memories.create()`; go: `client.Beta.MemoryStores.Memories.New()`; java: `client.beta().memoryStores().memories().create()`; csharp: `client.Beta.MemoryStores.Memories.Create()`; php: `$client->beta->memoryStores->memories->create()`; cli: `ant beta:memory-stores:memories create`) calls and the agent’s file writes to unmapped paths. Existing memories remain readable and editable. The following practices help you stay well under the limit and recover gracefully if you reach it.
13321332 
13331333* **Use focused stores.** Rather than one large general-purpose store, use smaller purpose-built stores: one for each user, one for shared domain knowledge, and one for project-specific context. Each store has its own 10,000-memory limit, so keeping stores scoped reduces the chance any single one fills up.
13341334 
from line 1336
13361336 
13371337* **Attach a new store when it makes sense.** If a store has grown beyond its useful scope, attach a fresh one for new content and attach the original with `read_only` access. The agent can read from both while only writing to the new one.
13381338 
1339* **Limit write access where appropriate.** Sessions that only read shared reference material don't need `read_write`. Keeping write access scoped to sessions that actually add new memories makes it easier to track where growth is coming from.
1339* **Limit write access where appropriate.** Sessions that only read shared reference material don’t need `read_write`. Keeping write access scoped to sessions that actually add new memories makes it easier to track where growth is coming from.
13401340 

managed-agents/migration Changed · +3 / -3 lines

from line 499
499499 
500500* **System prompt and model:** Same fields, now on the agent definition.
501501* **Custom tools:** Still declared with JSON Schema. Execution moves from inline handling to responding to `agent.custom_tool_use` events. See [Session event stream](https://platform.claude.com/docs/en/managed-agents/events-and-streaming).
502* **Web search and web fetch settings:** Same `allowed_domains` (java, php: `allowedDomains`; csharp, go: `AllowedDomains`), `blocked_domains` (java, php: `blockedDomains`; csharp, go: `BlockedDomains`), `max_content_tokens` (java, php: `maxContentTokens`; csharp, go: `MaxContentTokens`), and `user_location` (java, php: `userLocation`; csharp, go: `UserLocation`) fields, now set once on the `web_search` and `web_fetch` entries of the agent toolset's `configs` (csharp, go: `Configs`) array instead of on every request. The `max_uses` (java, php: `maxUses`; csharp, go: `MaxUses`), `citations` (csharp, go: `Citations`), and `cache_control` (java, php: `cacheControl`; csharp, go: `CacheControl`) fields are not available. See [Differences from the Messages API tools](https://platform.claude.com/docs/en/managed-agents/tools-web-restrictions#differences-from-the-messages-api-tools).
502* **Web search and web fetch settings:** Same `allowed_domains` (java, php: `allowedDomains`; csharp, go: `AllowedDomains`), `blocked_domains` (java, php: `blockedDomains`; csharp, go: `BlockedDomains`), `max_content_tokens` (java, php: `maxContentTokens`; csharp, go: `MaxContentTokens`), and `user_location` (java, php: `userLocation`; csharp, go: `UserLocation`) fields, now set once on the `web_search` and `web_fetch` entries of the agent toolset’s `configs` (csharp, go: `Configs`) array instead of on every request. The `max_uses` (java, php: `maxUses`; csharp, go: `MaxUses`), `citations` (csharp, go: `Citations`), and `cache_control` (java, php: `cacheControl`; csharp, go: `CacheControl`) fields are not available. See [Differences from the Messages API tools](https://platform.claude.com/docs/en/managed-agents/tools-web-restrictions#differences-from-the-messages-api-tools).
503503* **Context:** You can still inject context through the system prompt, [file resources](https://platform.claude.com/docs/en/managed-agents/files), or [skills](https://platform.claude.com/docs/en/managed-agents/skills).
504504 
505505## From the Claude Agent SDK
506506 
507If you built with the [Claude Agent SDK](https://code.claude.com/docs/en/agent-sdk/overview), you're already working with agents, tools, and sessions as concepts. The difference is where they run: the SDK runs in a process you operate, whereas Managed Agents runs in Anthropic's infrastructure. Most of the migration is mapping SDK configuration objects to their API-side equivalents.
507If you built with the [Claude Agent SDK](https://code.claude.com/docs/en/agent-sdk/overview), you’re already working with agents, tools, and sessions as concepts. The difference is where they run: the SDK runs in a process you operate, whereas Managed Agents runs in Anthropic’s infrastructure. Most of the migration is mapping SDK configuration objects to their API-side equivalents.
508508 
509509### What changes
510510 
from line 1210
12101210 
12111211### Features that move to your client
12121212 
1213The tradeoff for Anthropic running the agent loop is that a few things the SDK handled automatically become your client's responsibility.
1213The tradeoff for Anthropic running the agent loop is that a few things the SDK handled automatically become your client’s responsibility.
12141214 
12151215| SDK feature | Managed Agents approach |
12161216| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

managed-agents/multiagent-orchestration Changed · +53 / -53 lines

from line 20
2020 
2121You decide which of these the agent can use, and the agent determines when to use them. To guide that choice, tell the agent in its system prompt when to use a workflow run. See [Tell the agent when to use a run](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#tell-the-agent-when-to-use-a-run). You can also limit the agent to agents that you list.
2222 
23With subagents, the agent itself determines what happens next. A [subagent's thread](https://platform.claude.com/docs/en/managed-agents/session-threads) stays available until you archive it, so the agent can send it follow-up messages. With dynamic workflows, Claude writes a program to orchestrate agents without Claude's direct involvement. Context and results are passed programmatically from one agent to another, freeing up the main session thread to communicate with the user and check in on one or several running workflows to report on progress. The agent can't send follow-up messages to a run's threads, and the server archives each one by the end of its run.
23With subagents, the agent itself determines what happens next. A [subagent’s thread](https://platform.claude.com/docs/en/managed-agents/session-threads) stays available until you archive it, so the agent can send it follow-up messages. With dynamic workflows, Claude writes a program to orchestrate agents without Claude’s direct involvement. Context and results are passed programmatically from one agent to another, freeing up the main session thread to communicate with the user and check in on one or several running workflows to report on progress. The agent can’t send follow-up messages to a run’s threads, and the server archives each one by the end of its run.
2424 
2525| Approach | What happens | Use it when | Consider |
2626| -------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
27| [Delegate to subagents](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#delegate-to-subagents) | The agent delegates tasks to its subagents. Each subagent works in its own [session thread](https://platform.claude.com/docs/en/managed-agents/session-threads), which you can list and stream. | The agent should follow up with a subagent after it reports, or it needs specialists that you list, with their own system prompts and tools. | Delegation is one level deep, and a session can have at most 25 child threads at a time, idle ones included. Advisor threads and a workflow run's threads don't count. |
28| [Dynamic workflows](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#dynamic-workflows) | The agent writes a workflow: a program that runs many agents in phases and combines their results. The server runs it in the background as one workflow run, which you can follow. The workflow defines the agents or picks them from a list you give. | Most work that needs more than one agent: work with many pieces, parallel work, or a long task that should complete sooner. Examples are audits, migrations, deep research, and cross-checking. | Every agent in a run uses tokens, so set a [session budget](https://platform.claude.com/docs/en/managed-agents/budgets) to cap the session's spend, runs included. Tell the agent in its system prompt when to use a run. You follow the run by its phases and can read each of its threads. |
29| [Give the session an advisor](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#give-the-session-an-advisor) | The session's [primary thread](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#how-it-works) consults an advisor model mid-turn for guidance, such as planning an approach or reviewing work, and keeps doing the work itself. | One agent should do the work, with an advisor model's judgment at key moments such as planning or a final review. | Only the primary thread can consult the advisor, and consultations are billed at the advisor model's rates. |
27| [Delegate to subagents](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#delegate-to-subagents) | The agent delegates tasks to its subagents. Each subagent works in its own [session thread](https://platform.claude.com/docs/en/managed-agents/session-threads), which you can list and stream. | The agent should follow up with a subagent after it reports, or it needs specialists that you list, with their own system prompts and tools. | Delegation is one level deep, and a session can have at most 25 child threads at a time, idle ones included. Advisor threads and a workflow run’s threads don’t count. |
28| [Dynamic workflows](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#dynamic-workflows) | The agent writes a workflow: a program that runs many agents in phases and combines their results. The server runs it in the background as one workflow run, which you can follow. The workflow defines the agents or picks them from a list you give. | Most work that needs more than one agent: work with many pieces, parallel work, or a long task that should complete sooner. Examples are audits, migrations, deep research, and cross-checking. | Every agent in a run uses tokens, so set a [session budget](https://platform.claude.com/docs/en/managed-agents/budgets) to cap the session’s spend, runs included. Tell the agent in its system prompt when to use a run. You follow the run by its phases and can read each of its threads. |
29| [Give the session an advisor](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#give-the-session-an-advisor) | The session’s [primary thread](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#how-it-works) consults an advisor model mid-turn for guidance, such as planning an approach or reviewing work, and keeps doing the work itself. | One agent should do the work, with an advisor model’s judgment at key moments such as planning or a final review. | Only the primary thread can consult the advisor, and consultations are billed at the advisor model’s rates. |
3030 
31You set these up in the `multiagent` block of the agent's definition, which has a `type`. With the `multiagent_20261001` type, an agent can use all three together, and you can turn each one on or off. By default, `subagents` and `workflows` are both enabled. `subagents` and `workflows` each have `inline_agents` enabled, the setting for [agents that the agent or a workflow defines itself](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#predefined-and-inline-agents):
31You set these up in the `multiagent` block of the agent’s definition, which has a `type`. With the `multiagent_20261001` type, an agent can use all three together, and you can turn each one on or off. By default, `subagents` and `workflows` are both enabled. `subagents` and `workflows` each have `inline_agents` enabled, the setting for [agents that the agent or a workflow defines itself](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#predefined-and-inline-agents):
3232 
3333```json
3434{
from line 43
4343Every subagent, and every agent in a workflow run, is one of two kinds:
4444 
4545* **Predefined agent:** An agent that you have already [created](https://platform.claude.com/docs/en/managed-agents/agent-setup), and that you list in the `multiagent` (csharp, go: `Multiagent`) block. It uses its own configuration: model, system prompt, tools, MCP servers, and skills.
46* **Inline agent:** An agent that is not saved. The agent that the session runs, or a workflow, defines it when it hands out the work. It uses that agent's model, tools, MCP servers, and skills.
46* **Inline agent:** An agent that is not saved. The agent that the session runs, or a workflow, defines it when it hands out the work. It uses that agent’s model, tools, MCP servers, and skills.
4747 
4848Both kinds work under `subagents` and under `workflows`:
4949 
from line 54
5454 
5555`subagents.predefined_agents` and `workflows.predefined_agents` are two separate lists. An agent in one list is not added to the other. Both lists are empty by default. An entry of either list takes one of these forms:
5656 
57* `{"type": "agent", "id": agent.id}` references a previously created `agent` by ID. If no `version` is specified, the reference is pinned to the agent's latest version when the agent that lists it is created, or when an update sends the list.
57* `{"type": "agent", "id": agent.id}` references a previously created `agent` by ID. If no `version` is specified, the reference is pinned to the agent’s latest version when the agent that lists it is created, or when an update sends the list.
5858* `{"type": "agent", "id": agent.id, "version": agent.version}` pins a specific agent version.
5959* `agent.id` alone, as a string, is short for `{"type": "agent", "id": agent.id}`.
6060* `{"type": "self"}` lists the agent itself, so that copies of it can do the work. If the session was created with [agent configuration overrides](https://platform.claude.com/docs/en/managed-agents/sessions#override-agent-configuration-for-a-session), those overrides also apply to these copies. Entries referenced by ID are unaffected.
from line 101
101101 
102102All agents share the same sandbox, filesystem, and [vault credentials](https://platform.claude.com/docs/en/managed-agents/vaults), but each agent runs in its own **session thread**, a context-isolated event stream with its own conversation history. The agent that the session runs reports activity in the **primary thread**, which is the session-level [event stream](https://platform.claude.com/docs/en/managed-agents/events-and-streaming). Additional threads are spawned at runtime when it delegates work. A workflow run also creates threads.
103103 
104A subagent's thread is persistent. The agent can send a follow-up to a subagent it called earlier, and that subagent retains everything from its previous turns.
104A subagent’s thread is persistent. The agent can send a follow-up to a subagent it called earlier, and that subagent retains everything from its previous turns.
105105 
106106Which configuration a subagent uses depends on whether it is a [predefined or an inline agent](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#predefined-and-inline-agents). Session-level [agent configuration overrides](https://platform.claude.com/docs/en/managed-agents/sessions#override-agent-configuration-for-a-session) apply to the agent that the session runs and to its `self` copies. Each agent keeps its own conversation history.
107107 
from line 337
337337 
338338The agent can also delegate to inline agents unless you turn them off. For that setting, and for the forms that an entry of the `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) list of `subagents` (csharp, go: `Subagents`) takes, see [Predefined and inline agents](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#predefined-and-inline-agents).
339339 
340`"advisor": {"type": "enabled", "model": "<model id>"}` gives the session's primary thread an advisor it can consult mid-turn. The advisor is a setting in the `multiagent` (csharp, go: `Multiagent`) block, not an entry in this list. See [Give the session an advisor](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#give-the-session-an-advisor).
340`"advisor": {"type": "enabled", "model": "<model id>"}` gives the session’s primary thread an advisor it can consult mid-turn. The advisor is a setting in the `multiagent` (csharp, go: `Multiagent`) block, not an entry in this list. See [Give the session an advisor](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#give-the-session-an-advisor).
341341 
342342Dynamic workflows are also enabled by default with this type, so this agent can plan large work that runs many agents in the background. To turn them off, or to list agents that a workflow can use, see [Turn on dynamic workflows](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#turn-on-dynamic-workflows).
343343 
344344The following rules apply to the agents you list in the `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) list of `subagents` (csharp, go: `Subagents`), and also to the agents you list in the `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) list of `workflows` (csharp, go: `Workflows`):
345345 
346* **Pinning:** The agent's configuration, including the `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) list of `subagents` (csharp, go: `Subagents`), is snapshotted when the agent is created or updated. Referenced agents stay pinned to the versions resolved then and don't pick up later updates to their definitions. An update that doesn't send the list keeps the versions that are already pinned. To delegate to a newer version of a referenced agent, [update the agent](https://platform.claude.com/docs/en/managed-agents/agent-setup#update-an-agent) so the `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) list of `subagents` (csharp, go: `Subagents`) references that version.
346* **Pinning:** The agent’s configuration, including the `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) list of `subagents` (csharp, go: `Subagents`), is snapshotted when the agent is created or updated. Referenced agents stay pinned to the versions resolved then and don’t pick up later updates to their definitions. An update that doesn’t send the list keeps the versions that are already pinned. To delegate to a newer version of a referenced agent, [update the agent](https://platform.claude.com/docs/en/managed-agents/agent-setup#update-an-agent) so the `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) list of `subagents` (csharp, go: `Subagents`) references that version.
347347* **One level:** The agent can delegate to only one level of agents. Referencing another agent that has `multiagent` (csharp, go: `Multiagent`) set fails the create or update request with a 400 validation error.
348* **Up to 20 agents:** The `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) list of `subagents` (csharp, go: `Subagents`) can list up to 20 unique agents. The limit is for each list: the `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) list of `workflows` (csharp, go: `Workflows`) can also list up to 20. The agent can call multiple copies of each subagent, within the session's [thread limit](https://platform.claude.com/docs/en/managed-agents/session-threads).
348* **Up to 20 agents:** The `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) list of `subagents` (csharp, go: `Subagents`) can list up to 20 unique agents. The limit is for each list: the `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) list of `workflows` (csharp, go: `Workflows`) can also list up to 20. The agent can call multiple copies of each subagent, within the session’s [thread limit](https://platform.claude.com/docs/en/managed-agents/session-threads).
349349* **Inference geography:** The agent and every agent that you list, in the `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) list of `subagents` (csharp, go: `Subagents`) or of `workflows` (csharp, go: `Workflows`), must pin the same [inference geography](https://platform.claude.com/docs/en/manage-claude/data-residency) (`inference_geo` (java, php: `inferenceGeo`; csharp, go: `InferenceGeo`) in the `model` (csharp, go: `Model`) of the [agent definition](https://platform.claude.com/docs/en/managed-agents/agent-setup)), or none of them can pin one. A mismatch in either list is rejected with a 400 validation error. That check runs both when the agent is saved and when a [session-create override](https://platform.claude.com/docs/en/managed-agents/sessions#override-agent-configuration-for-a-session) changes any of the pins.
350350 
351351### Create the session
from line 432
432432MCP servers are agent-scoped: each agent definition declares its own servers and tools. An inline agent has no agent definition, so it uses the MCP servers and tools of the agent that the session runs. Vault credentials are session-scoped: `vault_ids` (java: `vaultIds`; php: `vaultIDs`; csharp: `VaultIds`; go: `VaultIDs`) passed at session creation apply to every thread. Two implications for your integration:
433433 
434434* To authenticate MCP servers, include a vault credential for every MCP server used across all agents.
435* To limit an agent's access, declare only the servers it needs in its agent definition. You can't limit an inline agent this way. To allow only the agents you list, disable `inline_agents` (java, php: `inlineAgents`; csharp, go: `InlineAgents`) in both `subagents` (csharp, go: `Subagents`) and `workflows` (csharp, go: `Workflows`), and list at least one agent in each. See [Predefined and inline agents](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#predefined-and-inline-agents).
435* To limit an agent’s access, declare only the servers it needs in its agent definition. You can’t limit an inline agent this way. To allow only the agents you list, disable `inline_agents` (java, php: `inlineAgents`; csharp, go: `InlineAgents`) in both `subagents` (csharp, go: `Subagents`) and `workflows` (csharp, go: `Workflows`), and list at least one agent in each. See [Predefined and inline agents](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#predefined-and-inline-agents).
436436 
437437[Agent configuration overrides](https://platform.claude.com/docs/en/managed-agents/sessions#override-agent-configuration-for-a-session) at session creation can replace the MCP servers of the agent that the session runs and those of its `self` copies.
438438 
439With a `limited` [environment](https://platform.claude.com/docs/en/managed-agents/environments#networking), session creation fails with a 400 error when the agent, or an agent you list in the `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) list of `subagents` (csharp, go: `Subagents`) or `workflows` (csharp, go: `Workflows`), declares an MCP server whose host is not in `allowed_hosts` (java, php: `allowedHosts`; csharp, go: `AllowedHosts`). Setting `allow_mcp_servers` (java: `allowMcpServers`; php: `allowMCPServers`; csharp: `AllowMcpServers`; go: `AllowMCPServers`) to `true` in the environment's networking turns this check off.
439With a `limited` [environment](https://platform.claude.com/docs/en/managed-agents/environments#networking), session creation fails with a 400 error when the agent, or an agent you list in the `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) list of `subagents` (csharp, go: `Subagents`) or `workflows` (csharp, go: `Workflows`), declares an MCP server whose host is not in `allowed_hosts` (java, php: `allowedHosts`; csharp, go: `AllowedHosts`). Setting `allow_mcp_servers` (java: `allowMcpServers`; php: `allowMCPServers`; csharp: `AllowMcpServers`; go: `AllowMCPServers`) to `true` in the environment’s networking turns this check off.
440440 
441441Create the researcher, which declares the GitHub MCP server, and the agent that delegates to the researcher:
442442 
from line 819
819819 ```
820820</CodeGroup>
821821 
822In this example, only the researcher declares the GitHub MCP server, so the agent that the session runs does not have access. The session's `vault_ids` (java: `vaultIds`; php: `vaultIDs`; csharp: `VaultIds`; go: `VaultIDs`) supply the GitHub credential to the researcher's thread.
822In this example, only the researcher declares the GitHub MCP server, so the agent that the session runs does not have access. The session’s `vault_ids` (java: `vaultIds`; php: `vaultIDs`; csharp: `VaultIds`; go: `VaultIDs`) supply the GitHub credential to the researcher’s thread.
823823 
824824<Tip>
825 If an agent's MCP calls fail to authenticate after you declare the server, confirm the credential's `mcp_server_url` (java: `mcpServerUrl`; php: `mcpServerURL`; csharp: `McpServerUrl`; go: `MCPServerURL`) refers to the same server as the agent's `mcp_servers[].url` (java, php: `mcpServers[].url`; csharp: `McpServers[].Url`; go: `MCPServers[].URL`). Both URLs are normalized before matching (scheme and host lowercased, default ports and trailing slashes stripped), so differences in host casing, a default port, or a trailing slash don't prevent a match; a different path, subdomain, or non-default port does.
825 If an agent’s MCP calls fail to authenticate after you declare the server, confirm the credential’s `mcp_server_url` (java: `mcpServerUrl`; php: `mcpServerURL`; csharp: `McpServerUrl`; go: `MCPServerURL`) refers to the same server as the agent’s `mcp_servers[].url` (java, php: `mcpServers[].url`; csharp: `McpServers[].Url`; go: `MCPServers[].URL`). Both URLs are normalized before matching (scheme and host lowercased, default ports and trailing slashes stripped), so differences in host casing, a default port, or a trailing slash don’t prevent a match; a different path, subdomain, or non-default port does.
826826</Tip>
827827 
828828### Threads
829829 
830Each agent's session thread has its own event stream. To list, interrupt, or archive threads, read their events, and handle tool permissions across them, see [Session threads](https://platform.claude.com/docs/en/managed-agents/session-threads).
830Each agent’s session thread has its own event stream. To list, interrupt, or archive threads, read their events, and handle tool permissions across them, see [Session threads](https://platform.claude.com/docs/en/managed-agents/session-threads).
831831 
832832## Dynamic workflows
833833 
834With dynamic workflows, an agent can take on work with many pieces, such as reviewing hundreds of documents or cross-checking many sources. The agent writes a **workflow:** a program that runs many agents in phases and combines what they return. The server runs it in the background as a **workflow run**, while the agent keeps working or ends its turn. You turn dynamic workflows on or off with the `workflows` (csharp, go: `Workflows`) setting in the agent's `multiagent` (csharp, go: `Multiagent`) block. A run's agents can work at the same time, so a long task can complete sooner than if one agent did each piece in turn.
834With dynamic workflows, an agent can take on work with many pieces, such as reviewing hundreds of documents or cross-checking many sources. The agent writes a **workflow:** a program that runs many agents in phases and combines what they return. The server runs it in the background as a **workflow run**, while the agent keeps working or ends its turn. You turn dynamic workflows on or off with the `workflows` (csharp, go: `Workflows`) setting in the agent’s `multiagent` (csharp, go: `Multiagent`) block. A run’s agents can work at the same time, so a long task can complete sooner than if one agent did each piece in turn.
835835 
836* **How a run starts:** You describe the work in a `user.message`. From that, the agent determines whether and when to start a run, so there's no additional API call. Instead, you influence the agent's determination by describing when to use a run in `user.message` or in the agent's system prompt. (Permission policies apply to the tools that a run's agents call, not to starting the run.)
836* **How a run starts:** You describe the work in a `user.message`. From that, the agent determines whether and when to start a run, so there’s no additional API call. Instead, you influence the agent’s determination by describing when to use a run in `user.message` or in the agent’s system prompt. (Permission policies apply to the tools that a run’s agents call, not to starting the run.)
837837* **Which agents it uses:** [Inline agents](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#predefined-and-inline-agents) that the workflow defines, predefined agents that you list in the `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) list of `workflows` (csharp, go: `Workflows`), or both. If you list none, the workflow defines them all. An inline agent uses the model of the agent that the session runs. To let a run use another model for some of its agents, create them as agents and list them in the `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) list of `workflows` (csharp, go: `Workflows`).
838* **How you follow it:** Run events arrive on the session's event stream, and each agent in the run works in a [session thread](https://platform.claude.com/docs/en/managed-agents/session-threads) that you can list, read, and stream. See [Workflow runs](https://platform.claude.com/docs/en/managed-agents/workflow-runs) for the events, interrupts, [limits](https://platform.claude.com/docs/en/managed-agents/workflow-runs#limits), and [what a run's threads show](https://platform.claude.com/docs/en/managed-agents/workflow-runs#a-runs-threads).
838* **How you follow it:** Run events arrive on the session’s event stream, and each agent in the run works in a [session thread](https://platform.claude.com/docs/en/managed-agents/session-threads) that you can list, read, and stream. See [Workflow runs](https://platform.claude.com/docs/en/managed-agents/workflow-runs) for the events, interrupts, [limits](https://platform.claude.com/docs/en/managed-agents/workflow-runs#limits), and [what a run’s threads show](https://platform.claude.com/docs/en/managed-agents/workflow-runs#a-runs-threads).
839839 
840840### Turn on dynamic workflows
841841 
842842When [defining your agent](https://platform.claude.com/docs/en/managed-agents/agent-setup), set `multiagent.type` to `"multiagent_20261001"` and enable `workflows` (csharp, go: `Workflows`). Dynamic workflows and delegating (`subagents` (csharp, go: `Subagents`)) are both on by default with this type. For dynamic workflows only, also set `subagents` (csharp, go: `Subagents`) to `{"type": "disabled"}`.
843843 
844Before you change an existing agent's `multiagent.type`, see [how to move an agent to this type](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#move-from-the-coordinator-type).
844Before you change an existing agent’s `multiagent.type`, see [how to move an agent to this type](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#move-from-the-coordinator-type).
845845 
846846<CodeGroup defaultLanguage="CLI">
847847 ```bash cURL
from line 977
977977 ```
978978</CodeGroup>
979979 
980To let a workflow use agents you've already created, list them in `workflows.predefined_agents`. The `subagents` and `advisor` settings go in the same `multiagent` block. To list subagents, see [Delegate to subagents](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#delegate-to-subagents). To set an advisor, see [Give the session an advisor](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#give-the-session-an-advisor). This agent lets a workflow use one agent you created, lists two subagents, and has an advisor:
980To let a workflow use agents you’ve already created, list them in `workflows.predefined_agents`. The `subagents` and `advisor` settings go in the same `multiagent` block. To list subagents, see [Delegate to subagents](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#delegate-to-subagents). To set an advisor, see [Give the session an advisor](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#give-the-session-an-advisor). This agent lets a workflow use one agent you created, lists two subagents, and has an advisor:
981981 
982982```json
983983{
from line 1021
10211021 
10221022* **Turning it off:** Set `workflows` (csharp, go: `Workflows`) to `{"type": "disabled"}`. It stays off until an update turns it on again. Sending `null` turns it on, because its default is enabled. The same applies to `subagents` (csharp, go: `Subagents`).
10231023 
1024* **Existing sessions:** A session copies the setting when it is created, so changing the agent later doesn't change a session that already exists.
1024* **Existing sessions:** A session copies the setting when it is created, so changing the agent later doesn’t change a session that already exists.
10251025 
1026* **Agents you can list:** An agent that has `multiagent` (csharp, go: `Multiagent`) set can't be listed as a subagent of another agent, or in the `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) list of `workflows` (csharp, go: `Workflows`) in another agent. An agent can list itself in either list with `{"type": "self"}`, which reads back as its own `id` and `version`.
1026* **Agents you can list:** An agent that has `multiagent` (csharp, go: `Multiagent`) set can’t be listed as a subagent of another agent, or in the `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) list of `workflows` (csharp, go: `Workflows`) in another agent. An agent can list itself in either list with `{"type": "self"}`, which reads back as its own `id` and `version`.
10271027 
10281028* **Tool names:** The `ant__` prefix is reserved. If your agent already has a custom tool whose name starts with `ant__`, an update fails with a 400 error until you send `tools` (csharp, go: `Tools`) without that name. A new session is refused with a 400 error too if its agent, or an agent in either list, has such a tool. Rename or remove the tool in the same update that turns dynamic workflows on. See [Custom tools](https://platform.claude.com/docs/en/managed-agents/tools#custom-tools).
10291029 
1030* **Budget:** Set a [session budget](https://platform.claude.com/docs/en/managed-agents/budgets) when you create the session to cap the session's spend, runs included; you can't add one to an existing session. Runs pause when the session reaches the budget, and runs that the budget paused resume when you raise or remove it, unless an interrupt also paused them. See [Cost and budgets](https://platform.claude.com/docs/en/managed-agents/workflow-runs#cost-and-budgets).
1030* **Budget:** Set a [session budget](https://platform.claude.com/docs/en/managed-agents/budgets) when you create the session to cap the session’s spend, runs included; you can’t add one to an existing session. Runs pause when the session reaches the budget, and runs that the budget paused resume when you raise or remove it, unless an interrupt also paused them. See [Cost and budgets](https://platform.claude.com/docs/en/managed-agents/workflow-runs#cost-and-budgets).
10311031 
10321032### Tell the agent when to use a run
10331033 
1034Turning dynamic workflows on gives the agent the ability to start workflow runs. The agent determines when to start one. To guide that choice, add an instruction such as the following to the agent's system prompt. The contract-review agent in [Turn on dynamic workflows](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#turn-on-dynamic-workflows) uses this system prompt:
1034Turning dynamic workflows on gives the agent the ability to start workflow runs. The agent determines when to start one. To guide that choice, add an instruction such as the following to the agent’s system prompt. The contract-review agent in [Turn on dynamic workflows](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#turn-on-dynamic-workflows) uses this system prompt:
10351035 
10361036```text wrap
10371037You review contracts. When you're asked to review more than a few contracts, start a workflow run that reads them in parallel and combines the findings. Review one or two contracts yourself, without a run.
10381038```
10391039 
1040To adapt it, name the tasks in your agent's domain that call for a run, and the small tasks the agent should do itself. You can also guide how a run does the work, as in these examples:
1040To adapt it, name the tasks in your agent’s domain that call for a run, and the small tasks the agent should do itself. You can also guide how a run does the work, as in these examples:
10411041 
1042* **How to split the work:** "In a run, use one agent per contract. Then have a second agent review every contract, not only the ones where the first agent found something, and look for what the first agent missed. Anything that fails the review gets redone and reviewed again."
1043* **What to do when an agent fails:** "One agent failing must not fail the run. If an agent fails to read a contract, list that contract as not covered. When the run ends, tell me which contracts weren't covered."
1044* **A time limit:** "Give the run a one-hour time limit."
1045* **Plan first:** "Before you start a run, describe its phases and how many agents each one runs. Then wait for my go-ahead." There's no approval step before a run starts, so nothing enforces this.
1042* **How to split the work:** “In a run, use one agent per contract. Then have a second agent review every contract, not only the ones where the first agent found something, and look for what the first agent missed. Anything that fails the review gets redone and reviewed again.”
1043* **What to do when an agent fails:** “One agent failing must not fail the run. If an agent fails to read a contract, list that contract as not covered. When the run ends, tell me which contracts weren’t covered.”
1044* **A time limit:** “Give the run a one-hour time limit.”
1045* **Plan first:** “Before you start a run, describe its phases and how many agents each one runs. Then wait for my go-ahead.” There’s no approval step before a run starts, so nothing enforces this.
10461046 
10471047Keep runs to the tasks that need them, because every agent in a run uses tokens.
10481048 
10491049Then [create a session](https://platform.claude.com/docs/en/managed-agents/sessions) with the agent, as you would with any agent, and describe the work in a `user.message`. You can also ask for a run in that message.
10501050 
1051For a run's events, interrupting a session with runs open, what changes while a run is open, and budgets and limits, see [Workflow runs](https://platform.claude.com/docs/en/managed-agents/workflow-runs).
1051For a run’s events, interrupting a session with runs open, what changes while a run is open, and budgets and limits, see [Workflow runs](https://platform.claude.com/docs/en/managed-agents/workflow-runs).
10521052 
10531053## Give the session an advisor
10541054 
1055An enabled `advisor` (csharp, go: `Advisor`) setting in the agent's `multiagent` (csharp, go: `Multiagent`) block gives the session's primary thread an **advisor:** a model it can consult mid-turn for strategic guidance, such as planning an approach, moving past an obstacle, or reviewing work before finishing. The setting is disabled by default. To enable it, set `advisor` (csharp, go: `Advisor`) to `{"type": "enabled", "model": "..."}`, which has exactly two fields, `type` and `model`:
1055An enabled `advisor` (csharp, go: `Advisor`) setting in the agent’s `multiagent` (csharp, go: `Multiagent`) block gives the session’s primary thread an **advisor:** a model it can consult mid-turn for strategic guidance, such as planning an approach, moving past an obstacle, or reviewing work before finishing. The setting is disabled by default. To enable it, set `advisor` (csharp, go: `Advisor`) to `{"type": "enabled", "model": "..."}`, which has exactly two fields, `type` and `model`:
10561056 
10571057<CodeGroup defaultLanguage="CLI">
10581058 ```bash cURL
from line 1201
12011201 
12021202This example sets only `advisor` (csharp, go: `Advisor`). The other two settings, `subagents` (csharp, go: `Subagents`) and `workflows` (csharp, go: `Workflows`), keep their defaults, so both are enabled, and the agent can also delegate to inline agents and start workflow runs. For an agent that consults an advisor and does all the work itself, set both to `{"type": "disabled"}`.
12031203 
1204You can't list the advisor in the `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) list of `subagents` (csharp, go: `Subagents`) or `workflows` (csharp, go: `Workflows`). An enabled `advisor` (csharp, go: `Advisor`) reserves the name `anthropic.advisor`. While it is enabled, neither list can hold an agent literally named `anthropic.advisor`: the request is rejected with a 400 validation error.
1204You can’t list the advisor in the `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) list of `subagents` (csharp, go: `Subagents`) or `workflows` (csharp, go: `Workflows`). An enabled `advisor` (csharp, go: `Advisor`) reserves the name `anthropic.advisor`. While it is enabled, neither list can hold an agent literally named `anthropic.advisor`: the request is rejected with a 400 validation error.
12051205 
1206The advisor model must meet a minimum capability level, and the agent's own model must not be more capable than its advisor; models of equal capability can pair. An invalid pairing is rejected with a 400 validation error when the agent is saved, and again when a session is created. The pairing is also checked when each consultation starts: if it's no longer valid, that consultation fails and the session continues. Valid pairings follow the advisor tool's [model compatibility](https://platform.claude.com/docs/en/agents-and-tools/tool-use/advisor-tool#model-compatibility) table.
1206The advisor model must meet a minimum capability level, and the agent’s own model must not be more capable than its advisor; models of equal capability can pair. An invalid pairing is rejected with a 400 validation error when the agent is saved, and again when a session is created. The pairing is also checked when each consultation starts: if it’s no longer valid, that consultation fails and the session continues. Valid pairings follow the advisor tool’s [model compatibility](https://platform.claude.com/docs/en/agents-and-tools/tool-use/advisor-tool#model-compatibility) table.
12071207 
12081208The advisor is also available as a [server tool on the Messages API](https://platform.claude.com/docs/en/agents-and-tools/tool-use/advisor-tool). The Managed Agents surface differs in configuration and delivery: the `advisor` (csharp, go: `Advisor`) setting has no `max_uses`, `max_tokens`, or `caching` fields, and advice arrives through thread events rather than `advisor_tool_result` blocks.
12091209 
from line 1217
121712174. `session.thread_status_idle` (`stop_reason: end_turn`)
121812185. `session.thread_status_terminated`
12191219 
1220No `agent.tool_use` events are emitted for a consultation, and no `agent.thread_message_sent` event appears on the session's event stream, because the consultation input is composed by the platform rather than sent by the agent. If you list the advisor thread's own events, the advice also appears there as an `agent.thread_message_sent` event. The advice delivery (event 3) is not guaranteed to arrive before the advisor thread's idle and terminated events, so don't treat those as a signal that the advice has already been delivered.
1220No `agent.tool_use` events are emitted for a consultation, and no `agent.thread_message_sent` event appears on the session’s event stream, because the consultation input is composed by the platform rather than sent by the agent. If you list the advisor thread’s own events, the advice also appears there as an `agent.thread_message_sent` event. The advice delivery (event 3) is not guaranteed to arrive before the advisor thread’s idle and terminated events, so don’t treat those as a signal that the advice has already been delivered.
12211221 
1222Whether your client can read the advice is the advisor model's policy. It mirrors the [result variants](https://platform.claude.com/docs/en/agents-and-tools/tool-use/advisor-tool#result-variants) split on the Messages API advisor tool. An advisor model that returns plaintext results there delivers readable text here. One that returns redacted results delivers a `[{"type": "redacted"}]` placeholder on every client surface, while the agent still reads the full advice server-side. The preceding example uses the newest Opus model available to you as the advisor. Claude Opus 5.5 returns redacted results, so your client sees only the placeholder. To read the advice on the event stream, use an advisor that returns plaintext, such as Claude Opus 4.8. A model's policy can change with no change to the API, so handle both `text` and `redacted` blocks. Some agent models pair only with redacted-result advisors; see the advisor tool's [compatibility table](https://platform.claude.com/docs/en/agents-and-tools/tool-use/advisor-tool#model-compatibility). Advisor thinking is never surfaced. Clients cannot send `redacted` blocks themselves; an event containing one is rejected with a 400 validation error.
1222Whether your client can read the advice is the advisor model’s policy. It mirrors the [result variants](https://platform.claude.com/docs/en/agents-and-tools/tool-use/advisor-tool#result-variants) split on the Messages API advisor tool. An advisor model that returns plaintext results there delivers readable text here. One that returns redacted results delivers a `[{"type": "redacted"}]` placeholder on every client surface, while the agent still reads the full advice server-side. The preceding example uses the newest Opus model available to you as the advisor. Claude Opus 5.5 returns redacted results, so your client sees only the placeholder. To read the advice on the event stream, use an advisor that returns plaintext, such as Claude Opus 4.8. A model’s policy can change with no change to the API, so handle both `text` and `redacted` blocks. Some agent models pair only with redacted-result advisors; see the advisor tool’s [compatibility table](https://platform.claude.com/docs/en/agents-and-tools/tool-use/advisor-tool#model-compatibility). Advisor thinking is never surfaced. Clients cannot send `redacted` blocks themselves; an event containing one is rejected with a 400 validation error.
12231223 
1224A failed or interrupted consultation never fails the agent's turn: the agent continues after a generic notice that the consultation failed. A session-level `user.interrupt` during a consultation terminates the advisor thread with no advice delivered; a `user.interrupt` with the advisor thread's `session_thread_id` (java: `sessionThreadId`; php: `sessionThreadID`; csharp, go: `SessionThreadID`) abandons only that consultation.
1224A failed or interrupted consultation never fails the agent’s turn: the agent continues after a generic notice that the consultation failed. A session-level `user.interrupt` during a consultation terminates the advisor thread with no advice delivered; a `user.interrupt` with the advisor thread’s `session_thread_id` (java: `sessionThreadId`; php: `sessionThreadID`; csharp, go: `SessionThreadID`) abandons only that consultation.
12251225 
12261226### Advisor threads
12271227 
1228The advisor is not a subagent. The agent's `list_agents` tool doesn't show it, and `send_to_agent`, which sends a subagent a follow-up message, can't reach it. Only the session's primary thread can consult it; subagents can't.
1228The advisor is not a subagent. The agent’s `list_agents` tool doesn’t show it, and `send_to_agent`, which sends a subagent a follow-up message, can’t reach it. Only the session’s primary thread can consult it; subagents can’t.
12291229 
1230Advisor threads are exempt from the 25-child-thread limit. They appear in the session's [thread list](https://platform.claude.com/docs/en/managed-agents/session-threads). Their `agent` is the advisor form, `{"type": "advisor", "model": ...}`, with the model you configured, and their `parent_thread_id` is the primary thread.
1230Advisor threads are exempt from the 25-child-thread limit. They appear in the session’s [thread list](https://platform.claude.com/docs/en/managed-agents/session-threads). Their `agent` is the advisor form, `{"type": "advisor", "model": ...}`, with the model you configured, and their `parent_thread_id` is the primary thread.
12311231 
1232Prompt caching on the advisor's side is automatic; there is nothing to configure. Consultations are billed at the advisor model's rates, and their tokens appear in the advisor thread's usage and in the session's usage totals. They also count against a [session budget](https://platform.claude.com/docs/en/managed-agents/budgets), at the advisor model's list price. Each consultation sends the advisor the primary thread's conversation so far, so consultations late in a long session use more input tokens.
1232Prompt caching on the advisor’s side is automatic; there is nothing to configure. Consultations are billed at the advisor model’s rates, and their tokens appear in the advisor thread’s usage and in the session’s usage totals. They also count against a [session budget](https://platform.claude.com/docs/en/managed-agents/budgets), at the advisor model’s list price. Each consultation sends the advisor the primary thread’s conversation so far, so consultations late in a long session use more input tokens.
12331233 
12341234### Removing the advisor
12351235 
1236To remove the advisor, [update the agent](https://platform.claude.com/docs/en/managed-agents/agent-setup#update-an-agent) with `advisor` (csharp, go: `Advisor`) set to `{"type": "disabled"}`. On an agent that already has the `multiagent_20261001` type, a setting that the update leaves out keeps its stored value, so you don't need to send `subagents` (csharp, go: `Subagents`) or `workflows` (csharp, go: `Workflows`) again. For the same reason, an update that leaves `advisor` (csharp, go: `Advisor`) out keeps the advisor. Before you change an existing agent's `multiagent.type`, see [how to move an agent to this type](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#move-from-the-coordinator-type).
1236To remove the advisor, [update the agent](https://platform.claude.com/docs/en/managed-agents/agent-setup#update-an-agent) with `advisor` (csharp, go: `Advisor`) set to `{"type": "disabled"}`. On an agent that already has the `multiagent_20261001` type, a setting that the update leaves out keeps its stored value, so you don’t need to send `subagents` (csharp, go: `Subagents`) or `workflows` (csharp, go: `Workflows`) again. For the same reason, an update that leaves `advisor` (csharp, go: `Advisor`) out keeps the advisor. Before you change an existing agent’s `multiagent.type`, see [how to move an agent to this type](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration#move-from-the-coordinator-type).
12371237 
12381238## Move from the `coordinator` type
12391239 
1240Like `multiagent_20261001`, the `coordinator` type lets an agent delegate to subagents that you list and consult an advisor model. With the `coordinator` type, the agent can't start workflow runs or define subagents itself. The API accepts both types.
1240Like `multiagent_20261001`, the `coordinator` type lets an agent delegate to subagents that you list and consult an advisor model. With the `coordinator` type, the agent can’t start workflow runs or define subagents itself. The API accepts both types.
12411241 
12421242To move an agent to `multiagent_20261001`, [update the agent](https://platform.claude.com/docs/en/managed-agents/agent-setup#update-an-agent) with a `multiagent` block of that type:
12431243 
124412441. Set `type` to `"multiagent_20261001"`.
124512452. List the agents that the agent can call in `subagents.predefined_agents`. The entries keep the form that they have now.
12463. If the agent has an advisor, set `advisor` to `{"type": "enabled", "model": "..."}` with the advisor's model.
12463. If the agent has an advisor, set `advisor` to `{"type": "enabled", "model": "..."}` with the advisor’s model.
124712474. Send the whole block in one update.
12481248 
12491249For example, this block gives an agent two subagents and an advisor:
from line 1275
12751275 
12761276Once the agent has the `multiagent_20261001` type, a setting that a later update leaves out keeps its stored value.
12771277 
1278The update doesn't change sessions that already exist. To use the new block, create a session after the update.
1278The update doesn’t change sessions that already exist. To use the new block, create a session after the update.
12791279 

managed-agents/onboarding Changed · +3 / -3 lines

from line 18
1818 
1919* **Model and system prompt:** Pick a model and write the system prompt in a full-width editor.
2020* **MCP servers:** Add remote MCP servers by URL and authenticate your agent to take action on your behalf.
21* **Tools:** Extend your agent's capabilities using a pre-built agent toolset and MCP tools.
22* **Skills:** Attach Anthropic or custom skills from your organization's library.
21* **Tools:** Extend your agent’s capabilities using a pre-built agent toolset and MCP tools.
22* **Skills:** Attach Anthropic or custom skills from your organization’s library.
2323 
24As you configure, Console shows the equivalent API request so you can copy it into your code once you're satisfied.
24As you configure, Console shows the equivalent API request so you can copy it into your code once you’re satisfied.
2525 
2626## Testing an agent
2727 
Feedback