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

# GitHub Actions

> Call Reducto from GitHub Actions workflows with GitHub's OIDC token instead of a stored API key.

GitHub Actions can issue a signed OIDC token to each workflow job. Reducto verifies the token and returns a short-lived access token, so your repository needs no `REDUCTO_API_KEY` secret.

## Get a token in a workflow

Grant the job permission to request an OIDC token:

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

`id-token: write` lets the job request a token. It grants no write access to the repository. `actions/checkout` needs `contents: read`.

GitHub exposes `ACTIONS_ID_TOKEN_REQUEST_URL` and `ACTIONS_ID_TOKEN_REQUEST_TOKEN` to the job. Request a token with the audience you will configure in Reducto. URL-encode the audience:

```bash theme={null}
AUDIENCE="https://platform.reducto.ai"
SUBJECT_TOKEN=$(curl -sS --fail \
  -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
  "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=$(jq -rn --arg a "$AUDIENCE" '$a|@uri')" \
  | jq -r .value)
```

## Inspect the token

A decoded GitHub Actions token looks like this:

```json theme={null}
{
  "iss": "https://token.actions.githubusercontent.com",
  "aud": "https://platform.reducto.ai",
  "sub": "repo:acme/payments:environment:production",
  "repository": "acme/payments",
  "repository_id": "712345678",
  "repository_owner": "acme",
  "repository_owner_id": "98765432",
  "ref": "refs/heads/main",
  "ref_type": "branch",
  "event_name": "push",
  "workflow": "deploy",
  "workflow_ref": "acme/payments/.github/workflows/deploy.yml@refs/heads/main",
  "environment": "production",
  "run_id": "11234567890",
  "run_attempt": "1",
  "iat": 1793322245,
  "nbf": 1793321945,
  "exp": 1793322545
}
```

The claims you are most likely to match on:

| Claim | Example | Notes |
| - | - | - |
| `sub` | `repo:acme/payments:environment:production` | Built from the repository plus the environment, branch, tag or pull request. The format changes when the job uses an environment. |
| `repository` | `acme/payments` | Changes if the repository is renamed or transferred. |
| `repository_id` | `712345678` | Stable across renames. |
| `ref` | `refs/heads/main` | The branch or tag that triggered the run. |
| `workflow_ref` | `acme/payments/.github/workflows/deploy.yml@refs/heads/main` | The workflow file and ref. Prefer it over `workflow`, which is a display name. |
| `environment` | `production` | Present only when the job uses an environment. |

See GitHub's [OIDC reference](https://docs.github.com/en/actions/reference/security/oidc) for all claims and `sub` formats. GitHub tokens expire after about 5 minutes.

## Configure Reducto

In [Studio](https://studio.reducto.ai), open **Settings > Workload identity** and create a provider with the **GitHub Actions** preset:

| Field | Value |
| - | - |
| **Name** | `github-actions-prod` |
| **Issuer** | `https://token.actions.githubusercontent.com` |
| **Audience** | `https://platform.reducto.ai` |
| **Key source** | OIDC discovery |

Add a mapping for the workflow you trust. This mapping allows only the production deploy workflow on `main` in `acme/payments`:

| Key | Value |
| - | - |
| `repository_id` | `712345678` |
| `workflow_ref` | `acme/payments/.github/workflows/deploy.yml@refs/heads/main` |
| `environment` | `production` |

To allow every workflow and branch in one repository, use a single condition `sub` = `repo:acme/payments:*`.

## Call Reducto from the workflow

Store the provider ID as a repository or organization variable, not a secret. It identifies the provider but grants nothing by itself.

```yaml theme={null}
name: parse-documents

on:
  push:
    branches: [main]

permissions:
  id-token: write
  contents: read

jobs:
  parse:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install reductoai
      - run: python scripts/parse.py
        env:
          REDUCTO_IDENTITY_PROVIDER_ID: ${{ vars.REDUCTO_IDENTITY_PROVIDER_ID }}
```

The token provider requests a GitHub token each time the SDK needs a new access token.

```python Python theme={null}
import os

from reducto import Reducto
from reducto.auth import github_actions_token_provider

client = Reducto(
    workload_identity={
        "identity_provider_id": os.environ["REDUCTO_IDENTITY_PROVIDER_ID"],
        "provider": github_actions_token_provider(),
    }
)

result = client.parse.run(input="https://cdn.reducto.ai/samples/fidelity-example.pdf")
print(result.result.chunks[0].content[:500])
```

```javascript Node.js theme={null}
import Reducto from "reductoai";
import { githubActionsTokenProvider } from "reductoai/auth";

const client = new Reducto({
  workloadIdentity: {
    identityProviderId: process.env.REDUCTO_IDENTITY_PROVIDER_ID,
    provider: githubActionsTokenProvider(),
  },
});

const result = await client.parse.run({
  input: "https://cdn.reducto.ai/samples/fidelity-example.pdf",
});
console.log(result.result.chunks[0].content.slice(0, 500));
```

```bash cURL theme={null}
AUDIENCE="https://platform.reducto.ai"
SUBJECT_TOKEN=$(curl -sS --fail \
  -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
  "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=$(jq -rn --arg a "$AUDIENCE" '$a|@uri')" \
  | jq -r .value)

ACCESS_TOKEN=$(jq -n \
  --arg subject_token "$SUBJECT_TOKEN" \
  --arg provider_id "$REDUCTO_IDENTITY_PROVIDER_ID" \
  '{
    grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
    subject_token_type: "urn:ietf:params:oauth:token-type:jwt",
    subject_token: $subject_token,
    identity_provider_id: $provider_id
  }' \
  | curl -sS --fail-with-body -X POST "https://platform.reducto.ai/oauth/token" \
      -H "Content-Type: application/json" -d @- \
  | jq -r .access_token)
echo "::add-mask::$ACCESS_TOKEN"

curl -X POST "https://platform.reducto.ai/parse" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"input":"https://cdn.reducto.ai/samples/fidelity-example.pdf"}'
```

`github_actions_token_provider(audience=...)` in Python and `githubActionsTokenProvider({ audience })` in Node.js set the audience. It defaults to `https://platform.reducto.ai`.

## GitHub Actions best practices

* Use GitHub environments with required reviewers or branch rules for production, and match the `environment` claim.
* Match `repository_id` or `repository` and a `ref` or `workflow_ref`, not only `repository_owner`. An owner-wide mapping lets every repository in the organization call Reducto.
* Do not give mappings to workflows that run on `pull_request_target` or on pull requests from forks. They can run untrusted code.
* Use separate mappings for CI and for deploy workflows.
* Remove `REDUCTO_API_KEY` from repository and organization secrets once federation works.
* Review mappings when you rename or transfer a repository.
