> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.mixlayer.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.mixlayer.com/_mcp/server.

# GitHub Actions Workload Identity

> Authenticate GitHub Actions workflows without repository API-key secrets

GitHub Actions can issue a short-lived OIDC token for each workflow job. Mixlayer verifies the token's repository and workflow claims and exchanges it for a short-lived Mixlayer access token, so the repository does not need a long-lived Mixlayer API-key secret.

## 1. Record stable repository identifiers

Find the repository and owner numeric IDs with the GitHub CLI:

```bash
gh api repos/example-org/example-repo \
  --jq '{repository_id: .id, repository_owner_id: .owner.id}'
```

Numeric IDs remain stable across repository and organization renames. Use names such as `repository` for readability, but use IDs for the main admission boundary.

## 2. Configure the Mixlayer provider

Create an OIDC provider in [Workload Identity](https://console.mixlayer.com/app/workload-identity):

| Setting          | Value                                         |
| ---------------- | --------------------------------------------- |
| Issuer URI       | `https://token.actions.githubusercontent.com` |
| Allowed audience | `https://api.mixlayer.com`                    |
| JWKS source      | OIDC discovery                                |

Map the job and repository identity:

```text
mixlayer.subject             = assertion.sub
attribute.repository_id      = assertion.repository_id
attribute.repository_owner_id = assertion.repository_owner_id
attribute.repository         = assertion.repository
attribute.ref                = assertion.ref
attribute.job_workflow_ref   = assertion.job_workflow_ref
```

Restrict admission to the exact owner and repository IDs:

```cel
attribute.repository_owner_id == "12345678" &&
attribute.repository_id == "87654321"
```

For production access, add a more restrictive first authorization rule before the fallback:

```json
[
  {
    "condition": "attribute.ref == \"refs/heads/main\" && attribute.job_workflow_ref == \"example-org/example-repo/.github/workflows/deploy.yml@refs/heads/main\"",
    "permissions": ["api-read", "inference"]
  },
  {
    "condition": "true",
    "permissions": ["inference"]
  }
]
```

The fallback permissions apply to every admitted job. If only the production workflow should authenticate, move its checks into the admission condition instead of relying on the rule above.

> **Warning**
>
> GitHub's default `sub` format can vary by repository settings and creation
> date. Inspect a real token and prefer stable `repository_id` and
> `repository_owner_id` claims for the trust boundary.

## 3. Request and exchange the job token

The job needs `id-token: write`. This permission allows the job to request an OIDC token; it does not grant write access to repository contents.

Store the non-secret Mixlayer provider ID in a GitHub Actions configuration variable named `MIXLAYER_IDENTITY_PROVIDER_ID`.

**`file=.github/workflows/mixlayer.yml`**

```yaml file=.github/workflows/mixlayer.yml
name: Call Mixlayer

on:
  workflow_dispatch:

permissions:
  contents: read
  id-token: write

jobs:
  call-mixlayer:
    runs-on: ubuntu-latest
    env:
      MIXLAYER_WIF_AUDIENCE: https://api.mixlayer.com
      MIXLAYER_IDENTITY_PROVIDER_ID: ${{ vars.MIXLAYER_IDENTITY_PROVIDER_ID }}
    steps:
      - name: Exchange GitHub identity and call Mixlayer
        shell: bash
        run: |
          encoded_audience=$(jq -rn \
            --arg audience "$MIXLAYER_WIF_AUDIENCE" \
            '$audience | @uri')

          subject_token=$(curl --fail-with-body --silent --show-error \
            -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
            "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=$encoded_audience" |
            jq -er .value)

          mixlayer_token=$(jq -n \
            --arg provider "$MIXLAYER_IDENTITY_PROVIDER_ID" \
            --arg token "$subject_token" \
            '{
              grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
              subject_token_type: "urn:ietf:params:oauth:token-type:jwt",
              subject_token: $token,
              identity_provider_id: $provider
            }' |
            curl --fail-with-body --silent --show-error \
              https://api.mixlayer.com/v1/workload_identity/token \
              -H "Content-Type: application/json" \
              --data-binary @- |
            jq -er .access_token)

          echo "::add-mask::$mixlayer_token"

          curl --fail-with-body https://mixlayer.ai/v1/chat/completions \
            -H "Authorization: Bearer $mixlayer_token" \
            -H "Content-Type: application/json" \
            -d '{
              "model": "qwen/qwen3.5-4b-free",
              "messages": [{"role": "user", "content": "Hello from GitHub Actions"}]
            }'
```

No Mixlayer secret is stored in GitHub. The workflow receives a new GitHub OIDC token for the job and exchanges it at runtime.

## Tighten production policies

* Use GitHub environments and required reviewers for production deployments.
* Match the exact `job_workflow_ref` when a centrally controlled reusable workflow should be the only caller.
* Match immutable repository and owner IDs before mutable names.
* Do not grant access based only on the public GitHub issuer and audience; any GitHub repository can request a token for a caller-selected audience.
* Do not print the GitHub or Mixlayer tokens in workflow logs.