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

# Workload Identity (OIDC)

> Call Mixpeek from Vercel, GitHub Actions or any OIDC platform without storing an API key

A workload that runs on Vercel, GitHub Actions or another platform with an OIDC
identity can call Mixpeek without a stored API key. The platform gives the
workload a short-lived signed token. The workload trades that token at
`POST /v1/auth/token-exchange` for a Mixpeek API key that expires in minutes,
and uses that key like any other.

Nothing long-lived sits in the workload's environment variables, so there is
no key to leak, rotate or revoke when a deployment or dependency is
compromised.

## How it works

1. An admin adds a **workload identity provider** to the organization. It names
   the issuer, the accepted audience, and the claims a token must carry (for
   example a Vercel project ID and environment, or a GitHub repository).
2. The workload gets a token from its platform and sends it, with the
   provider's `provider_id`, to `POST /v1/auth/token-exchange`. No API key is
   needed for this call.
3. Mixpeek checks the token's signature against the issuer's published keys,
   then its issuer, audience, expiry and every claim condition. If all hold, it
   returns an API key that expires after the provider's `token_ttl_seconds`
   (5 to 60 minutes, 15 by default).
4. The workload sends `Authorization: Bearer <access_token>` until
   `expires_at`, then exchanges a fresh token.

The returned key is an ordinary API key: permissions, scopes, rate limits and
the audit log treat it the same way. It belongs to the admin who added the
provider, it never appears in the API key list, and keys stop being issued if
that admin is suspended or removed.

## Add a provider

Providers live on the organization. Send the full list with
`PATCH /v1/organizations` (admin key required). Keep each existing provider's
`provider_id` to preserve it; omit `provider_id` to add one; leave a provider
out to remove it.

```bash Vercel theme={null}
curl -X PATCH https://api.mixpeek.com/v1/organizations \
  -H "Authorization: Bearer $MIXPEEK_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "workload_identity_providers": [{
      "name": "vercel-prod",
      "issuer": "https://oidc.vercel.com/acme",
      "audience": ["https://vercel.com/acme"],
      "claim_conditions": {
        "project_id": ["prj_123"],
        "environment": ["production"]
      },
      "permissions": ["read"]
    }]
  }'
```

```bash GitHub Actions theme={null}
curl -X PATCH https://api.mixpeek.com/v1/organizations \
  -H "Authorization: Bearer $MIXPEEK_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "workload_identity_providers": [{
      "name": "github-deploys",
      "issuer": "https://token.actions.githubusercontent.com",
      "audience": ["https://api.mixpeek.com"],
      "claim_conditions": {
        "repository": ["acme/api"],
        "ref": ["refs/heads/main"]
      },
      "permissions": ["read", "write"]
    }]
  }'
```

The response lists each provider with the `provider_id` your workload sends.

| Field | What it does |
| - | - |
| `name` | Unique within the organization. |
| `issuer` | Exact `iss` of the tokens. Vercel team issuer mode: `https://oidc.vercel.com/<team_slug>`; global mode: `https://oidc.vercel.com`. GitHub: `https://token.actions.githubusercontent.com`. |
| `audience` | Accepted `aud` values. Vercel's default is `https://vercel.com/<team_slug>`. For GitHub, the audience you request in the workflow. |
| `claim_conditions` | Claim name to allowed values. Every listed claim must match one value. A value ending in `*` matches by prefix. |
| `permissions` | `read`, `write`, `delete`. Admin cannot be granted this way. |
| `token_ttl_seconds` | Lifetime of each key, 300 to 3600. Default 900. |
| `jwks_url` | Optional. Mixpeek reads `jwks_uri` from the issuer's `/.well-known/openid-configuration` when it is not set. |
| `enabled` | Set `false` to stop exchanges without deleting the provider. |

> [!WARNING]
>
> A provider must match at least one claim besides `iss`, `aud`, `exp`, `iat`,
> `nbf` and `jti`, and a bare `*` is refused. Otherwise any project on Vercel,
> or any repository on GitHub, could get a key for your organization. Pin the
> project ID or repository, and the environment or branch.

## Exchange a token

```ts Vercel (Node.js) theme={null}
import { getVercelOidcToken } from "@vercel/oidc";

async function mixpeekKey() {
  const res = await fetch("https://api.mixpeek.com/v1/auth/token-exchange", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      provider_id: process.env.MIXPEEK_PROVIDER_ID,
      subject_token: await getVercelOidcToken(),
    }),
  });
  if (!res.ok) throw new Error(`token exchange failed: ${res.status}`);
  return (await res.json()).access_token as string;
}
```

```yaml GitHub Actions theme={null}
permissions:
  id-token: write
  contents: read

steps:
  - name: Get a Mixpeek key
    run: |
      TOKEN=$(curl -sSf -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
        "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=https://api.mixpeek.com" | jq -r .value)
      KEY=$(curl -sSf -X POST https://api.mixpeek.com/v1/auth/token-exchange \
        -H "Content-Type: application/json" \
        -d "{\"provider_id\":\"$MIXPEEK_PROVIDER_ID\",\"subject_token\":\"$TOKEN\"}" \
        | jq -r .access_token)
      echo "::add-mask::$KEY"
      echo "MIXPEEK_API_KEY=$KEY" >> "$GITHUB_ENV"
```

The provider ID is not a secret; it only says which provider to check the
token against. The response:

```json theme={null}
{
  "access_token": "mxp_sk_...",
  "token_type": "Bearer",
  "expires_in": 900,
  "expires_at": "2026-10-23T12:15:00Z",
  "key_id": "key_...",
  "organization_id": "org_...",
  "permissions": ["read"]
}
```

Reuse the key until shortly before `expires_at` rather than exchanging on every
request. Exchanges are limited to 120 per provider per minute. You can ask for
a shorter key with `"ttl_seconds"`; it is capped at the provider's
`token_ttl_seconds`.

## Errors

| Status | `error_code` | Meaning |
| - | - | - |
| 401 | `workload_identity_unknown_provider` | No provider has this `provider_id`. |
| 401 | `workload_identity_invalid_signature` | The token is not signed by a key the issuer publishes. |
| 401 | `workload_identity_wrong_issuer` | The token's `iss` differs from the provider's `issuer`. |
| 401 | `workload_identity_wrong_audience` | The token's `aud` is not in the provider's `audience`. |
| 401 | `workload_identity_token_expired` | The platform token has expired; get a fresh one. |
| 403 | `workload_identity_claim_mismatch` | A claim in `claim_conditions` holds a value the provider does not allow. `details.claim` names it. |
| 403 | `workload_identity_claim_missing` | The token lacks a claim the provider requires. |
| 403 | `workload_identity_provider_disabled` | The provider has `enabled: false`. |
| 429 | | Too many exchanges for this provider in the last minute. |

## Related

* [API Keys](https://docs.mixpeek.com/docs/operations/api-keys.md): permissions, scopes, rotation.
* [Security & Tenancy](https://docs.mixpeek.com/docs/operations/security.md): headers and isolation.
* Vercel: [OIDC federation](https://vercel.com/docs/oidc). GitHub: [OpenID Connect in GitHub Actions](https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect).


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