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 change · api

wif-reference changedmanage-claude/wif-reference

Nearest release: v2.1.312, published under an hour before this site recorded the change. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.

Recorded here
Lines+22added
Lines−22removed
From line 17 where the diff opens
First seen 14 Aug 2026 this site's first read of the page
Recorded edits15to this page, all time

The whole hunk

from line 17, old and new numbered
/
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 
Feedback