# Anthropic Workload Identity Federation


Let a VM call the Claude API without storing an Anthropic API key. The VM gets
a short-lived exe.dev token, and Anthropic exchanges it for a short-lived
access token for one of your service accounts.

Usage is billed to your Anthropic organization. To use exe.dev's built-in
models instead, see the [LLM integration](integrations-llm).

Setup uses two browser tabs: the exe.dev [Integrations page](/integrations)
and the Claude Console's
[Workload identity settings](https://platform.claude.com/settings/workload-identity-federation).

## 1. Start the integration in exe.dev

On the [Integrations page](/integrations), add an **Identity Federation**
integration and choose **Anthropic**. Enter a name, such as `anthropic-wif`.
To use it through an LLM integration (step 4), you don't need to attach it to
any VMs.

The dialog shows an **Issuer** and a **Subject**. You will paste both into
Anthropic in the next step. Keep the dialog open; the subject is reserved for
15 minutes.

## 2. Create the rule in Anthropic

In the Claude Console, open
[Workload identity settings](https://platform.claude.com/settings/workload-identity-federation),
select **Connect workload**, and choose **Custom OIDC**.

| Anthropic field | Value |
| --- | --- |
| Issuer URL | The exe.dev **Issuer**. Use OIDC discovery for signing keys. |
| Subject match | The exe.dev **Subject**, exactly. Do not use a wildcard. |
| Audience match | `api.anthropic.com` |
| Workspace | The workspace that receives the API usage |
| Service account | An account in that workspace; the VM acts as it |
| Scope | `workspace:inference` to call Claude only, or `workspace:developer` for broader access |

After you save the rule, the Console shows the IDs you need in the next step.

## 3. Enter the IDs and save

Back in the exe.dev dialog, fill in:

| Field | Where it comes from |
| --- | --- |
| Organization ID | Your Anthropic organization (a UUID) |
| Federation rule ID | The rule you just created (`fdrl_...`) |
| Service account ID | The rule's service account (`svac_...`) |
| Workspace ID | Only if the rule covers more than one workspace (`wrkspc_...`, or `default`) |

Click **Run**. If you don't have the IDs yet, you can save with the fields
empty and edit the integration later to add them.

## 4. Use it from an LLM integration

On the [Integrations page](/integrations), add an
[LLM integration](integrations-llm), set its Anthropic provider to
**Workload identity**, and pick this integration. Attach the LLM integration
to your VMs; the workload identity integration doesn't need to be attached.
exe.dev exchanges and renews Anthropic tokens for you. Both integrations must
be in the same scope: personal for a personal LLM integration, team for a team
one.

Or from the CLI:

```
ssh exe.dev integrations add llm --name claude \
  --anthropic=wif --anthropic-wif=anthropic-wif \
  --openai=disabled --fireworks=disabled --attach vm:example-vm
```

On the VM, Claude Code and Anthropic SDKs need only the LLM integration's URL:

```
ANTHROPIC_API_KEY=implicit \
  ANTHROPIC_BASE_URL=https://claude.int.exe.xyz \
  claude --model opus
```

For a team integration, use `https://claude.team.exe.xyz`.

## Other uses: call Claude directly

To call Claude yourself instead of through an LLM integration, for example
from code that does its own token exchange, attach this integration to the VM.
Then run this on the VM. It reads the IDs from the integration, exchanges a
fresh exe.dev token for an Anthropic access token, and sends a message:

```
EXE_WIF_URL=https://anthropic-wif.int.exe.xyz
META="$(curl -fsS "$EXE_WIF_URL/metadata")"
JWT="$(curl -fsS "$EXE_WIF_URL/token" | jq -er .token)"

ACCESS_TOKEN="$(
  jq -n --arg assertion "$JWT" --argjson meta "$META" '{
    grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
    assertion: $assertion,
    federation_rule_id: $meta.fed_rule_id,
    organization_id: $meta.org_id,
    service_account_id: $meta.svc_account_id
  } + (if $meta.workspace_id then {workspace_id: $meta.workspace_id} else {} end)' |
  curl -fsS https://api.anthropic.com/v1/oauth/token \
    -H 'content-type: application/json' -d @- |
  jq -er .access_token
)"

curl -fsS https://api.anthropic.com/v1/messages \
  -H "authorization: Bearer $ACCESS_TOKEN" \
  -H 'anthropic-version: 2023-06-01' \
  -H 'content-type: application/json' \
  -d '{"model":"claude-opus-5-5","max_tokens":128,"messages":[{"role":"user","content":"Hello from exe.dev"}]}'

unset JWT ACCESS_TOKEN
```

Replace `anthropic-wif` with your integration's name. For a team integration,
use `https://<name>.team.exe.xyz`.

### Tokens

- Fetch a new exe.dev token from `/token` for every exchange. Anthropic rejects
  a token it has already seen.
- Get a new Anthropic access token before `expires_in` runs out.
- Both tokens are credentials. Don't print, log, or commit them.

Anthropic's SDKs can do the exchange and refresh for you. See
[Anthropic's WIF reference](https://platform.claude.com/docs/en/manage-claude/wif-reference).

## Use the CLI instead

The CLI prints the issuer and subject only after it creates the integration,
so add the IDs with a second command:

```
ssh exe.dev integrations add wif --name anthropic-wif \
  --audience api.anthropic.com --consumer anthropic \
  --attach vm:example-vm
```

The `--attach` is only needed to call Anthropic directly from the VM. Create
the Anthropic rule with the printed **Issuer** and **Subject**, then:

```
ssh exe.dev integrations edit anthropic-wif \
  --metadata=org_id=00000000-0000-0000-0000-000000000000 \
  --metadata=fed_rule_id=fdrl_EXAMPLE \
  --metadata=svc_account_id=svac_EXAMPLE
```

Add `--metadata=workspace_id=...` if the rule covers more than one workspace,
and `--team` to `add` for a team integration. `edit` replaces all metadata, so
include every ID each time.

## Troubleshooting

- **The exchange is rejected.** Check that the rule's issuer, subject, and
  audience exactly match the integration. The Console's
  [authentication history](https://platform.claude.com/settings/workload-identity-federation?tab=history)
  shows why a token was rejected.
- **`/token` or `/metadata` doesn't respond.** The integration isn't attached
  to this VM.
- **The rule covers more than one workspace.** Anthropic then requires a
  workspace ID in every exchange; add it to the integration.
