# 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.

This calls `api.anthropic.com` directly, so 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`,
and choose the VMs to attach.

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. Call Claude from the VM

Run this on an attached 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
```

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.
