> ## Documentation Index
> Fetch the complete documentation index at: https://www.truefoundry.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Workload Identity Federation

> Exchange Kubernetes, GitHub Actions, AWS, or GCP JWTs for short-lived TrueFoundry tokens without baking virtual account keys into pods or CI.

Workload Identity Federation lets your workloads authenticate to TrueFoundry with short-lived tokens from an identity provider you already operate — Kubernetes, GitHub Actions, AWS, or GCP — instead of long-lived Virtual Account tokens (VATs).

The workload presents a signed JWT from that provider. TrueFoundry validates it against an Identity Provider and Claims (or Subject) mapping you configure, then returns a short-lived TrueFoundry access token bound to a Virtual Account or Agent. There is no static TrueFoundry secret to mint, store in CI, rotate, or leak.

This is **inbound** federation into TrueFoundry (workload → TrueFoundry). It is not Workforce SSO, and not outbound cloud WIF used when TrueFoundry calls Vertex or other model providers.

> [!NOTE]
>
> For Okta, Entra, Auth0, and custom IdPs that send a JWT as Bearer on every API call, see [External JWT](https://www.truefoundry.com/docs/platform/identity-providers/external-jwt.md). Workload providers use **token exchange** — do not send the platform JWT as Bearer on every TrueFoundry call. For a comparison of both paths, see the [Identity Providers overview](https://www.truefoundry.com/docs/platform/identity-providers/overview.md).

## Concepts

You configure three things before a workload can federate:

1. **Identity Provider** — registers the external issuer TrueFoundry should trust (`issuer`, audiences, JWKS URI, and the lifetime of the TrueFoundry token returned by exchange).
2. **Mapping** — which claims on the JWT must match. On a Virtual Account this is **Claims**; on an Agent this is **Subject**.
3. **Principal** — the [Virtual Account](https://www.truefoundry.com/docs/platform/virtual-account-management.md) or Agent the exchanged token acts as. Permissions stay on that principal; exchange never creates a new account.

Together they mean: tokens signed by issuer X, with claims that look like Y, may act as Virtual Account or Agent Z. Teams are not supported as Workload Identity principals.

## How it works

```mermaid theme={"dark"}
sequenceDiagram
    autonumber
    participant Adm as Admin
    participant W as Workload
    participant X as TrueFoundry token endpoint
    participant A as APIs / AI Gateway

    Note over Adm,X: One-time setup
    Adm->>X: Register workload IdP
    Adm->>X: Map Claims (VA) or Subject (Agent)

    Note over W,A: Runtime
    W->>X: token-exchange (subject_token + tfy_subject)
    X-->>W: short-lived TF access_token
    W->>A: Authorization Bearer TF token
    A-->>W: response
```

1. Your platform issues a JWT to the workload (Kubernetes projected service-account token, GitHub Actions OIDC token, AWS STS / outbound federation JWT, or GCP workload token).
2. The workload exchanges that JWT at the TrueFoundry token endpoint.
3. TrueFoundry verifies the signature and issuer/audience, matches Claims or Subject on the named Virtual Account or Agent, and returns a short-lived TrueFoundry access token.
4. The workload calls the AI Gateway or APIs with `Authorization: Bearer <access_token>`, then exchanges again before expiry. There is no refresh token.

## Provider guides

Choose the guide for the environment that issues the workload JWT:

> [card] [GitHub Actions](https://www.truefoundry.com/docs/platform/identity-providers/workload-identity-federation/github-actions.md)
>
> CI OIDC tokens from `token.actions.githubusercontent.com`.

> [card] [Kubernetes](https://www.truefoundry.com/docs/platform/identity-providers/workload-identity-federation/kubernetes.md)
>
> Projected service-account tokens; match namespace and service account.

> [card] [AWS](https://www.truefoundry.com/docs/platform/identity-providers/workload-identity-federation/aws.md)
>
> IAM outbound web identity (`GetWebIdentityToken`) or EKS via Kubernetes.

> [card] [GCP](https://www.truefoundry.com/docs/platform/identity-providers/workload-identity-federation/gcp.md)
>
> Google identity tokens or GKE; match `sub` and `email`.

## Set up federation

**1. Register a workload Identity Provider**

Go to **Settings > Security & Access > Identity Providers** and add a provider. Choose **Kubernetes**, **GitHub**, **AWS**, or **GCP**.

| Field | Required | Description |
| - | - | - |
| Provider Name | Yes | Unique ID (3–32 chars: lowercase, numbers, hyphens; start with a letter; end with a letter or number). |
| Issuer URL | Yes | Must exactly match the JWT `iss` claim. |
| Allowed Audiences | Yes | At least one JWT `aud` value must intersect this list (string or array `aud`). |
| JWKS URI | Yes | Public HTTPS JWKS URL reachable from the control plane. |
| Token TTL (seconds) | No | Lifetime of the exchanged TrueFoundry token (`60`–`3600`, default `3600`). |

Workload providers do not include **Resolve Token To**. Exchange the platform JWT; do not send it as Bearer on every call. Provider-specific issuer, JWKS, and claim examples are in the [provider guides](#provider-guides).

**2. Map a Virtual Account or Agent**

Create or select a Virtual Account (or Agent) and grant it the permissions the workload needs. Then add an Identity Provider Mapping for the workload provider:

* **Virtual Account** — use **Claims** (not a single Claim Value).
* **Agent** — use **Subject** (subject key / subject value). YAML still uses the `claims` key.

Required keys depend on the provider:

| Provider | Required keys |
| - | - |
| `kubernetes` | `kubernetes.io/namespace`, `kubernetes.io/serviceaccount/name` |
| `github` | `repository` |
| `aws` | `sub` |
| `gcp` | `sub`, `email` |

Paths use `/` as a nested-object separator when reading the JWT. The dot in names such as `kubernetes.io` is part of the key, not a path separator. Values must not contain whitespace. You can add more claim matches beyond the required keys.

Full examples: [GitHub Actions](https://www.truefoundry.com/docs/platform/identity-providers/workload-identity-federation/github-actions.md), [Kubernetes](https://www.truefoundry.com/docs/platform/identity-providers/workload-identity-federation/kubernetes.md), [AWS](https://www.truefoundry.com/docs/platform/identity-providers/workload-identity-federation/aws.md), [GCP](https://www.truefoundry.com/docs/platform/identity-providers/workload-identity-federation/gcp.md). Also see [Identity provider mappings](https://www.truefoundry.com/docs/platform/virtual-account-management.md#identity-provider-mappings) and [Agent Registry](https://www.truefoundry.com/docs/agent-platform/agent-governance/agent-registry.md).

**3. Exchange the platform JWT**

Obtain the platform JWT, then call the token endpoint. No `client_id` and no `Authorization` header on this grant.

Prefer the `token_endpoint` from OpenID discovery:

`GET https://<tenant-host-origin>/<tenant>/.well-known/openid-configuration`

The worked path is `/{tenant}/oauth2/v1/token` on the **tenant-host origin** — same shape as OIDC discovery / issuer (no `/api/svc` prefix). SaaS: `https://<tenant>.<your-tenant-base-domain>`. Dedicated: the origin of your public control-plane URL (strip any `/api/svc` path if present); on dedicated installs that origin is often the same host you use for ordinary APIs, but the exchange path itself never includes `/api/svc`.

```bash theme={"dark"}
curl -sS "https://<tenant-host-origin>/<tenant>/oauth2/v1/token" \
  -H 'Content-Type: application/json' \
  -d "{
    \"grant_type\": \"urn:ietf:params:oauth:grant-type:token-exchange\",
    \"subject_token\": \"$WORKLOAD_JWT\",
    \"subject_token_type\": \"urn:ietf:params:oauth:token-type:jwt\",
    \"tfy_subject\": \"virtual_account:<virtual-account-name>\"
  }"
```

`tfy_subject` is `virtual_account:<name>` or `agent:<name>`.

Successful response:

```json theme={"dark"}
{
  "token_type": "Bearer",
  "access_token": "<truefoundry-jwt>",
  "expires_in": 900,
  "issued_token_type": "urn:ietf:params:oauth:token-type:access_token"
}
```

`expires_in` matches the provider's Token TTL (or `3600` if unset). Re-exchange before expiry — there is no refresh token.

Python example:

```python theme={"dark"}
import os
import requests

CONTROL_PLANE = os.environ["TFY_CONTROL_PLANE"]  # tenant-host origin only, no /api/svc path
# SaaS example: https://<tenant>.truefoundry.cloud  |  dedicated: origin of public CP URL
TENANT = os.environ["TFY_TENANT"]
SUBJECT_TOKEN = os.environ["WORKLOAD_JWT"]  # platform-issued JWT
TFY_SUBJECT = "virtual_account:builder"

response = requests.post(
    f"{CONTROL_PLANE}/{TENANT}/oauth2/v1/token",
    json={
        "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
        "subject_token": SUBJECT_TOKEN,
        "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
        "tfy_subject": TFY_SUBJECT,
    },
    timeout=30,
)
response.raise_for_status()
access_token = response.json()["access_token"]

# Use like a VAT: Authorization: Bearer <access_token>
```

**4. Call TrueFoundry with the access token**

Pass `Authorization: Bearer <access_token>` to the [AI Gateway](https://www.truefoundry.com/docs/ai-gateway/authentication.md), [MCP Gateway](https://www.truefoundry.com/docs/ai-gateway/mcp/mcp-overview.md), REST API, or SDK the same way you would a VAT.

## Troubleshooting

> [accordion] Exchange returns invalid\_grant
>
> TrueFoundry returns an opaque error when verification or claim matching fails. Check that:
>
> * The JWT `iss` matches the IdP issuer.
> * The JWT `aud` intersects the IdP allowed audiences.
> * Exactly one mapped Identity Provider matches the JWT **issuer** and **audience**. If the Virtual Account or Agent lists the same workload IdP more than once, exchange fails even when only one Claims / Subject set would fit. After that unique IdP row is chosen, every saved Claims / Subject entry must equal the JWT.
> * Prefer a single mapping row per workload IdP on a given Virtual Account or Agent; put distinct claim sets on different principals when you need them.
> * Every required Claims / Subject entry matches the JWT.
> * `tfy_subject` names an existing Virtual Account or Agent that maps this provider.
> * The subject JWT header includes a non-empty `kid` that exists on the provider JWKS.

> [accordion] Exchange returns unsupported\_grant\_type
>
> If exchange returns `unsupported_grant_type`, the workload identity grant is not enabled on this control plane — contact your TrueFoundry admin or support.

> [accordion] Exchange returns server\_error
>
> If exchange returns `server_error` with “Workload identity keys are not available,” the control-plane auth cache is disabled or not ready — retry after it is healthy, or escalate to TrueFoundry admin / support.

> [accordion] Direct Bearer with the platform JWT fails
>
> That path is not supported for `kubernetes`, `github`, `aws`, or `gcp`. Exchange first, then call APIs with the TrueFoundry access token.

## Related

* [GitHub Actions](https://www.truefoundry.com/docs/platform/identity-providers/workload-identity-federation/github-actions.md) · [Kubernetes](https://www.truefoundry.com/docs/platform/identity-providers/workload-identity-federation/kubernetes.md) · [AWS](https://www.truefoundry.com/docs/platform/identity-providers/workload-identity-federation/aws.md) · [GCP](https://www.truefoundry.com/docs/platform/identity-providers/workload-identity-federation/gcp.md)
* [External JWT](https://www.truefoundry.com/docs/platform/identity-providers/external-jwt.md) — direct Bearer with an IdP JWT
* [Identity Providers overview](https://www.truefoundry.com/docs/platform/identity-providers/overview.md)
* [Manage Virtual Accounts](https://www.truefoundry.com/docs/platform/virtual-account-management.md#identity-provider-mappings) — Claim value vs Claims
* [Agent Registry](https://www.truefoundry.com/docs/agent-platform/agent-governance/agent-registry.md) — Subject vs Claims for Agents
* [API Keys](https://www.truefoundry.com/docs/generating-truefoundry-api-keys.md) — PATs and VATs when federation is not in use


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.