WIF reference
Environment variables, validation rules, profile configuration, and error reference for Workload Identity Federation.
This page collects the configuration surfaces, validation constraints, and error mappings for Workload Identity Federation (WIF). For setup walkthroughs, see Identity providers.
Token exchange request
POST /v1/oauth/token accepts a JSON body using the RFC 7523 jwt-bearer grant. The SDK builds this request for you from the environment variables.
| Field | Required | Description |
|---|---|---|
grant_type | Yes | Always urn:ietf:params:oauth:grant-type:jwt-bearer. |
assertion | Yes | The OIDC JWT issued by your identity provider. |
federation_rule_id | Yes | Tagged ID (fdrl_...) of the federation rule to evaluate. |
organization_id | Yes | UUID of your Anthropic organization. |
service_account_id | Yes | Tagged ID (svac_...) of the target service account. |
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. |
Token exchange response
POST /v1/oauth/token returns a standard OAuth 2.0 token response (RFC 6749 §5.1):
| Field | Type | Description |
|---|---|---|
access_token | string | The short-lived Anthropic token, prefixed sk-ant-oat01-.... Pass it as Authorization: Bearer <token>. |
token_type | string | Always Bearer. |
expires_in | integer | Seconds until the token expires. |
scope | string | The OAuth scope granted by the matched rule. |
Environment variables
The SDK reads these variables to perform a federated token exchange with no constructor arguments.
| Variable | Required | Description | Example |
|---|---|---|---|
ANTHROPIC_FEDERATION_RULE_ID | Yes | Tagged ID of the federation rule to evaluate. | fdrl_... |
ANTHROPIC_ORGANIZATION_ID | Yes | UUID of your Anthropic organization. Find it on the Organization page (platform.claude.com/settings/organization) in the Claude Console. | 00000000-0000-0000-0000-000000000000 |
ANTHROPIC_IDENTITY_TOKEN_FILE | One of _TOKEN_FILE or _TOKEN | Filesystem path to the JWT issued by your identity provider (IdP). The SDK rereads this file on every exchange so that projected tokens that rotate on disk are always current. | /var/run/secrets/anthropic.com/token |
ANTHROPIC_IDENTITY_TOKEN | One of _TOKEN_FILE or _TOKEN | The literal JWT as a string. Use when your platform injects the token as an environment variable rather than a file. | eyJhbGciOiJSUzI1NiIs... |
ANTHROPIC_SERVICE_ACCOUNT_ID | Yes | Tagged ID of the target Anthropic service account that the issued access token acts as. | svac_... |
ANTHROPIC_WORKSPACE_ID | Conditional | Tagged ID of the workspace to scope the minted token to. Required when the federation rule is enabled for more than one workspace. Optional when the rule is bound to a single workspace. The exchange scopes the minted token to this workspace, so switching workspaces requires a new exchange. Without a profile, the ant CLI does not pass this variable into the exchange. With the ant CLI, set workspace_id in a federation profile and set ANTHROPIC_PROFILE to its name. The literal default also works for the Default Workspace but is deprecated. Use its wrkspc_... ID. | wrkspc_... |
ANTHROPIC_PROFILE | No | Name of a configuration profile to load. Takes precedence over the federation environment variables in this table. | staging-profile |
The direct environment-variable federation path activates only when ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID, ANTHROPIC_SERVICE_ACCOUNT_ID, and one of ANTHROPIC_IDENTITY_TOKEN_FILE or ANTHROPIC_IDENTITY_TOKEN are all set. The SDK also reads ANTHROPIC_WORKSPACE_ID, but this path activates with or without it.
Credential precedence
The SDK resolves credentials in this order. The first source that yields a credential wins.
| Order | Source | Notes |
|---|---|---|
| 1 | Constructor argument (API key, auth token, or credentials) | Always overrides everything else. |
| 2 | ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN | Overrides federation entirely. Unset these when migrating from API keys. |
| 3 | ANTHROPIC_PROFILE | Loads <config_dir>/configs/<name>.json. If the named profile is missing, the SDK reports an error instead of trying the next source. |
| 4 | Federation environment variables | ANTHROPIC_FEDERATION_RULE_ID + ANTHROPIC_ORGANIZATION_ID + ANTHROPIC_SERVICE_ACCOUNT_ID + ANTHROPIC_IDENTITY_TOKEN[_FILE]. |
| 5 | Active profile | Resolved from <config_dir>/active_config, falling back to a profile named default. |
In the ant CLI, the settings for Claude Models on Google Cloud (Anthropic managed) rank after the federation environment variables and before the active profile. The settings are the project, the workspace ID, and the location. You set them with the --google-cloud-* flags or the ANTHROPIC_GOOGLE_CLOUD_* variables. The CLI uses these settings when any one of them is set. With these settings, the CLI gets its credential from Google Application Default Credentials.
When a profile is loaded, environment variables fill any fields the profile omits but never override fields the profile sets explicitly. For example, ANTHROPIC_WORKSPACE_ID fills workspace_id only when the active profile does not set it.
Profile configuration file
A profile is a named configuration file that the SDK and the ant CLI both read. Profiles let you ship federation parameters with your container image or switch between environments without changing code.
Configuration directory
The SDK locates the configuration directory in this order:
$ANTHROPIC_CONFIG_DIR~/.config/anthropicon Linux and macOS%APPDATA%\Anthropicon Windows
Active profile
The active profile name resolves in this order:
$ANTHROPIC_PROFILE- The contents of
<config_dir>/active_config(a one-line file written byant profile activate <name>) - The literal name
default
Claude Code and the Claude Agent SDK honor this same resolution order, so a federation profile configured here also authenticates those tools without additional setup.
File layout
| Path | Contents | Sensitivity |
|---|---|---|
<config_dir>/configs/<profile>.json | version, the authentication block, organization_id, workspace_id, and base_url. | Non-secret. Safe to commit or build into an image. |
<config_dir>/credentials/<profile>.json | version, the cached access_token, expires_at, and (for interactive login) refresh_token. | Secret. The SDK writes this file with mode 0600. |
Both the config file and the credentials file carry a top-level string version field in major.minor format, such as "1.0". The SDK writes this field automatically. If you omit it when authoring a config by hand, the SDK treats the file as the current version.
Federation profile example
{
"version": "1.0",
"authentication": {
"type": "oidc_federation",
"federation_rule_id": "fdrl_...",
"service_account_id": "svac_...",
"identity_token": {
"source": "file",
"path": "/var/run/secrets/anthropic.com/token"
}
},
"organization_id": "00000000-0000-0000-0000-000000000000",
"workspace_id": "wrkspc_...",
"base_url": "https://api.anthropic.com"
}If you omit authentication.identity_token, the SDK falls back to ANTHROPIC_IDENTITY_TOKEN_FILE or ANTHROPIC_IDENTITY_TOKEN from the environment.
OAuth scopes
The oauth_scope you set on a federation rule determines which Claude API endpoints the minted access token can call.
| Scope | Grants access to |
|---|---|
workspace:developer | All non-administrative Claude API endpoints in the rule’s workspace: Messages (including streaming and token counting), ModelsPreview with links to the API reference available — press Tab until focus enters the card, Escape to return., Managed Agents and their sessions, Files, and Skills. This matches the access a workspace API key in the same workspace has. |
workspace:inference | The inference endpoints in the rule’s workspace: Messages (including streaming and token counting), ModelsPreview with links to the API reference available — press Tab until focus enters the card, Escape to return., and the OpenAI-compatible chat endpoint. Use this for workloads that only need to call Claude and never need to manage Files, Skills, or other resources. |
workspace:manage_tunnels | The MCP 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. |
org:admin | Full access to the 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. It cannot update an issuer that backs a rule with any other scope. See Permissions and constraints. |
A request to an endpoint outside the token’s scope returns HTTP 403. Finer-grained scopes (by resource, or read versus write) are not available.
Permission boundaries
A 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 a rule can grant. 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.
Rule oauth_scope | Service account organization_role | Effective permissions |
|---|---|---|
workspace:developer | admin | Claude API access in the rule’s workspace only. The scope caps the token below the role. |
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. |
Validation rules
Anthropic enforces these constraints when you create or update issuers and rules, and when it verifies an incoming JWT at exchange time.
For complete parameter details and response schemas, see the Service accounts API reference, Federation issuers API reference, and Federation rules API reference.
Resource fields
| Field | Constraint |
|---|---|
Issuer, rule, and service account name | Must match ^[a-z0-9-]+$, length 1 to 255 characters. |
workspace_id | Required on create unless applies_to_all_workspaces is true. The workspace (wrkspc_...) whose quota, billing, and rate limits apply to tokens minted under this rule. Must be a workspace in the same organization, and the target service account must be a member of that workspace. |
applies_to_all_workspaces | Boolean. Set true to enable the rule in every workspace in the organization instead of naming one. Either this or workspace_id is required on create. |
token_lifetime_seconds | Integer between 60 and 86400 (1 minute to 24 hours). Default 3600. Anthropic rejects values outside this range at request time. See Token lifetime and refresh. |
URL fields
Anthropic validates the issuer_url, jwks.discovery_base, and jwks.url fields:
| Constraint | Detail |
|---|---|
| Scheme | Must be https. |
| Port | Must be 443 (explicit or default). |
| Host | Must be a public DNS hostname for your OIDC provider. Must resolve to public IP addresses. Anthropic does not accept IP literals. |
URL validation failures return 400 invalid_request_error with the field name as a prefix on the error message (for example, issuer_url: url must use https scheme).
JWT verification
| Constraint | Detail |
|---|---|
| Maximum size | The assertion JWT must be at most 16 KiB. |
| Signing algorithm | Anthropic accepts only asymmetric algorithms (RSA and ECDSA families: ES256, ES384, ES512, RS256, RS384, RS512, PS256, PS384, PS512). It rejects HMAC (HS256, HS384, HS512) and none. |
| Key ID | The JWT header must carry a kid that matches a key in the issuer’s JWKS. Anthropic rejects tokens without kid. |
| Required claims | sub must be present. iat must be present and not in the future. exp must be present and in the future. |
| Single use | An assertion that carries a jti claim can be exchanged only once for each issuer. Anthropic rejects a repeat exchange with the same jti 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. |
| 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). |
| Clock skew | Anthropic applies a 30-second leeway to exp, nbf, and iat. |
Rule matching semantics
A federation rule’s match block determines whether Anthropic accepts an incoming JWT. The JWT must satisfy every populated matcher. Set at least one of subject_prefix, claims, or condition. Anthropic rejects a match block that contains only audience or no matchers at all. This guards against rules that would accept every token from an issuer.
| Matcher | Type | Semantics |
|---|---|---|
subject_prefix | string | Exact match against the JWT sub claim. A trailing * makes it a prefix match (the sub value must start with the characters before the *). Case-sensitive. |
audience | string | The JWT aud claim must contain this exact string. When aud is an array, any element matching exactly satisfies the check. |
claims | map<string, string> | Each key is a top-level claim name and each value is the required exact string value. For nested, numeric, boolean, or complex claims such as lists and maps, use condition with a Common Expression Language (CEL) expression instead. |
condition | string (CEL) | A CEL expression that must evaluate to true. |
CEL evaluation environment
The condition expression has access to a single variable:
| Variable | Type | Contents |
|---|---|---|
claims | map | The full decoded JWT claim set. Nested objects are accessible as nested maps. |
Example:
claims.sub.startsWith("repo:acme-corp/") && claims.ref in ["refs/heads/main", "refs/heads/release"]Errors
Token exchange errors
POST /v1/oauth/token returns errors in the standard API error shape. The SDK reports an exchange failure as WorkloadIdentityError, which gives the HTTP status and the error details from the response body. Except in C# and PHP, the SDK also gives the request ID: on the error, in the exception’s response headers, or in the error’s message.
| Status | Error | Cause | Resolution |
|---|---|---|---|
| 400 | invalid_request_error | federation_rule_id is malformed or a required request field is missing. | Check the fdrl_ ID and that the request body includes all required fields. |
| 400 | invalid_request_error | workspace_id is present but is not a well-formed wrkspc_... ID or the literal default. | Fix the workspace_id value. The response message names the expected format. |
| 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". |
| 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. |
| 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 rereading the token file. |
| 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. |
| 401 | authentication_error | The federation_rule_id does not exist or is archived, or the JWT is not authorized for it (one error covers these cases to prevent enumeration). | Confirm the rule ID in the Claude Console and that the rule has not been archived. |
| 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. With the ant CLI, set workspace_id in a federation profile and set ANTHROPIC_PROFILE to its name. See Token exchange request. |
Every 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 authentication history in the Claude Console records the deny reason on the attempt’s entry. For example, it shows 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 confirmed (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 that Anthropic did not recognize the federation_rule_id.
Common SDK-side failures
| Symptom | Cause | Resolution |
|---|---|---|
| SDK reports missing 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. |
| 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. |
| File-not-found error 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. |
| 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. |
| Authentication fails with empty credential | A credential environment variable is exported but set to an empty string. Empty values can win their precedence slot. | Unset the variable with unset VAR rather than VAR="". |
Troubleshoot a failed exchange
A 401 authentication_error response is intentionally opaque, and its message is always Authentication failed. The deny reason appears in the authentication history, not in the response.
One common opaque failure is a replayed assertion. An assertion that carries a jti claim can be exchanged only once. A workload that resends the same JWT (a retry loop, or a refresh that rereads an unrotated token) fails on the second exchange. The authentication history page shows these attempts with the reason jti_reused. To fix this, mint a fresh assertion for each exchange.
If you need to debug from the JWT itself, work through these checks in order:
Decode the JWT
Decode the assertion you sent so you can compare each claim against your issuer and rule configuration:
jq -rR 'split(".")[1] | gsub("-";"+") | gsub("_";"/") | @base64d | fromjson' <<< "$JWT"Check iss matches the issuer
The decoded
issclaim must equal the registeredissuer_urlbyte for byte, including scheme, port, and any trailing slash. A mismatch on a single character fails verification.Check aud matches the rule
The decoded
audclaim must contain the rule’saudiencevalue as an exact match. Whenaudis an array, one element must match exactly.Check sub and each claims entry
Compare
subagainst the rule’ssubject_prefix. The comparison is case-sensitive. A trailing*makes it a prefix match, and anything else is exact. Compare every key in the rule’sclaimsmap against the same-named top-level claim.Check exp, nbf, and iat
expmust be in the future andnbf/iatmust be in the past, within the 30-second skew window. If the workload host’s clock has drifted, Anthropic rejects an otherwise valid token.Check JWKS reachability
For
discoverymode, fetch<jwks.discovery_base or issuer_url>/.well-known/openid-configurationover public HTTPS on port 443 and confirmjwks_uriresolves. Forexplicit_url, fetch the JWKS URL directly. Forinline, confirm the issuer’s signing key has not rotated since you registered the keys.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.
JWKS source modes
When you register a federation issuer, the jwks field controls how Anthropic obtains the public keys used to verify JWT signatures from that issuer. Its type field selects the source mode:
jwks.type | jwks shape | Behavior | Use when |
|---|---|---|---|
discovery (default) | { "type": "discovery", "discovery_base": "https://..." } (set the optional discovery_base when the discovery URL differs from issuer_url) | Anthropic fetches <discovery_base or issuer_url>/.well-known/openid-configuration, reads jwks_uri from the discovery document, and fetches the JWKS from there. | Your IdP serves a standard OIDC discovery document on the public internet. Most managed providers (EKS, GKE, Cloud Run, GitHub Actions, Entra ID) support this. |
explicit_url | { "type": "explicit_url", "url": "https://..." } | Anthropic fetches the JWKS directly from url. Anthropic uses the issuer_url only for string comparison against the JWT iss claim and never connects to it. | Your IdP does not serve a discovery document, or discovery is internal-only but the JWKS is publicly reachable. |
inline | { "type": "inline", "keys": [...] } | You supply the array of JWK objects inline (the keys array from the JWKS document, not the wrapper object). Anthropic makes no outbound request. Anthropic uses the issuer_url only for iss comparison. | Air-gapped environments, self-managed Kubernetes clusters with cluster-internal issuer URLs, or when you want explicit control over key rotation. |
Each type takes its own companion fields, so those fields are mutually exclusive. Both discovery and explicit_url also accept an optional ca_cert_pem string for issuers that serve TLS from a private CA.
Key rotation and caching
In discovery and explicit_url modes, Anthropic caches the fetched JWKS. After your identity provider publishes a new signing key, exchanges of tokens signed with it might fail with a signature error for up to 1 minute while the cache refreshes.
To avoid this window, publish a new signing key in the JWKS at least 15 minutes before your identity provider starts signing tokens with it. Keep the replaced key in the JWKS until the tokens it signed have expired. Managed identity providers typically do this on their own. If you operate your own issuer, confirm that your rotation policy publishes new keys ahead of first use. Examples are a self-managed Kubernetes cluster, a SPIRE OIDC discovery provider, and an Okta custom authorization server with a configured rotation schedule.
Was this page helpful?