---
title: Usa WIF con GitHub Actions
url: https://platform.claude.com/docs/es/manage-claude/wif-providers/github-actions
description: Autentica los flujos de trabajo de GitHub Actions en la Claude API con tokens de identidad de corta duración en lugar de claves de API de larga duración.
---

Cada ejecución de un flujo de trabajo de GitHub Actions puede solicitar un token de identidad firmado al emisor alojado de GitHub en `https://token.actions.githubusercontent.com`. Con "Workload Identity Federation" (federación de identidades de cargas de trabajo), o WIF, tu flujo de trabajo intercambia ese token por un token de acceso de Anthropic de corta duración, de modo que tus trabajos de CI pueden llamar a la Claude API sin un secreto `ANTHROPIC_API_KEY` almacenado en tu repositorio.

El claim `sub` del token codifica el repositorio y el contexto del disparador. Para un push a una rama tiene la forma `repo:<owner>/<repo>:ref:refs/heads/<branch>`. Las ejecuciones de pull request usan `repo:<owner>/<repo>:pull_request`, y los despliegues controlados por entorno usan `repo:<owner>/<repo>:environment:<name>`. Tu regla de federación compara contra este claim (y otros, como `repository_owner` y `ref`) para decidir qué ejecuciones de flujos de trabajo tienen permitido autenticarse.

## Requisitos previos

* Familiaridad con los [conceptos de WIF](https://platform.claude.com/docs/es/manage-claude/workload-identity-federation#concepts): cuentas de servicio, emisores de federación y reglas de federación.
* Un repositorio de GitHub donde puedas editar archivos de flujos de trabajo y otorgar el permiso `id-token: write`.
* Permiso para crear cuentas de servicio, emisores de federación y reglas de federación en la Claude Console para tu organización de Anthropic.
* El ID de tu organización de Anthropic. Puedes encontrarlo en la página [Organization](https://platform.claude.com/settings/organization) (`platform.claude.com/settings/organization`) en la Claude Console.

## Configura tu flujo de trabajo

GitHub solo emite un token de identidad a los trabajos que lo solicitan explícitamente. Agrega el permiso `id-token: write` a nivel del flujo de trabajo o del trabajo:

```yaml
permissions:
  id-token: write
  contents: read
```

Dentro del trabajo, el runner expone dos variables de entorno: `ACTIONS_ID_TOKEN_REQUEST_URL` y `ACTIONS_ID_TOKEN_REQUEST_TOKEN`. Llama a la URL de solicitud con el token de solicitud como credencial bearer y la audiencia que elijas como parámetro de consulta, y luego escribe el "JSON Web Token" (token web JSON), o JWT, devuelto en un archivo:

```yaml
- name: Fetch GitHub OIDC token
  run: |
    curl -sS -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
      "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=https://api.anthropic.com" \
      | jq -r .value > /tmp/gha-jwt
```

Si prefieres JavaScript, `actions/github-script` expone la misma capacidad a través de `core.getIDToken(audience)`:

```yaml
- name: Fetch GitHub OIDC token
  uses: actions/github-script@v8
  with:
    script: |
      const fs = require('fs');
      const token = await core.getIDToken('https://api.anthropic.com');
      fs.writeFileSync('/tmp/gha-jwt', token);
```

El token decodificado contiene claims que describen la ejecución del flujo de trabajo. Tu regla de federación compara contra estos:

```json
{
  "iss": "https://token.actions.githubusercontent.com",
  "sub": "repo:your-org/your-repo:ref:refs/heads/main",
  "aud": "https://api.anthropic.com",
  "repository": "your-org/your-repo",
  "repository_owner": "your-org",
  "ref": "refs/heads/main",
  "sha": "abc123...",
  "workflow": "CI",
  "actor": "octocat",
  "event_name": "push"
}
```

Consulta la [referencia del claim subject de OIDC de GitHub](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#example-subject-claims) para ver la lista completa de formatos de `sub`.

## Configura Anthropic

En la Claude Console, ve a la página [Workload identity](https://platform.claude.com/settings/workload-identity-federation) (`platform.claude.com/settings/workload-identity-federation`), haz clic en **Connect workload** y selecciona el mosaico **GitHub Actions**. El asistente te guía para registrar el emisor, crear una cuenta de servicio y crear una regla de federación.

El asistente crea estos recursos por ti. Usa los siguientes valores, ya sea que los ingreses en el asistente o los envíes a la [Admin API](https://platform.claude.com/docs/es/manage-claude/wif-admin-api):

**Emisor de federación:** GitHub publica su documento de descubrimiento OIDC y su JWKS de forma pública, así que usa el modo de descubrimiento. Anthropic actualiza las claves automáticamente cuando GitHub las rota.

```json
{
  "name": "github-actions",
  "issuer_url": "https://token.actions.githubusercontent.com",
  "jwks": { "type": "discovery" }
}
```

**Regla de federación:** Haz coincidir solo las ejecuciones de flujos de trabajo en las que pretendes confiar. Consulta [Restringe qué flujos de trabajo pueden autenticarse](https://platform.claude.com/docs/es/manage-claude/wif-providers/github-actions#restrict-which-workflows-can-authenticate) para saber cómo delimitar estos claims de forma segura.

```json
{
  "name": "gha-main",
  "issuer_id": "fdis_...",
  "match": {
    "subject_prefix": "repo:your-org/your-repo:ref:refs/heads/main",
    "audience": "https://api.anthropic.com",
    "claims": {
      "repository_owner": "your-org"
    }
  },
  "target": {
    "type": "service_account",
    "service_account_id": "svac_..."
  },
  "workspace_id": "wrkspc_...",
  "oauth_scope": "workspace:developer",
  "token_lifetime_seconds": 600
}
```

Sé tan específico como lo permita la carga de trabajo. Relaja `subject_prefix` a `repo:your-org/your-repo:*` (junto con una restricción `claims.ref`) solo si la regla debe coincidir con varios tipos de eventos del mismo repositorio, porque el segmento final de `sub` varía entre los eventos `ref:...`, `environment:...` y `pull_request`.

## Obtén y usa un token

Define las variables de entorno de federación en el trabajo y llama al SDK de la forma habitual. `Anthropic()` (typescript: `new Anthropic()`; csharp: `new AnthropicClient()`; go: `anthropic.NewClient()`; java: `AnthropicOkHttpClient.fromEnv()`; php: `new Client()`; ruby: `Anthropic::Client.new`) lee `ANTHROPIC_IDENTITY_TOKEN_FILE`, intercambia el JWT en la primera solicitud y actualiza el token de acceso automáticamente antes de que caduque.

<CodeGroup>
  ```yaml Workflow
  name: Call Claude
  on: push

  permissions:
    id-token: write
    contents: read

  jobs:
    call-claude:
      runs-on: ubuntu-latest
      env:
        ANTHROPIC_FEDERATION_RULE_ID: fdrl_...
        ANTHROPIC_ORGANIZATION_ID: 00000000-0000-0000-0000-000000000000
        ANTHROPIC_SERVICE_ACCOUNT_ID: svac_...
        ANTHROPIC_WORKSPACE_ID: wrkspc_...  # required when the rule covers multiple workspaces
        ANTHROPIC_IDENTITY_TOKEN_FILE: /tmp/gha-jwt
      steps:
        - uses: actions/checkout@v5
        - name: Fetch GitHub OIDC token
          run: |
            curl -sS -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
              "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=https://api.anthropic.com" \
              | jq -r .value > "$ANTHROPIC_IDENTITY_TOKEN_FILE"
        - name: Run your script
          run: |
            pip install anthropic
            python your_script.py
  ```

  ```bash cURL
  JWT=$(cat /tmp/gha-jwt)

  RESPONSE=$(curl -sS https://api.anthropic.com/v1/oauth/token \
    -H "content-type: application/json" \
    --data @- <<JSON
  {
    "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
    "assertion": "$JWT",
    "federation_rule_id": "$ANTHROPIC_FEDERATION_RULE_ID",
    "organization_id": "$ANTHROPIC_ORGANIZATION_ID",
    "service_account_id": "$ANTHROPIC_SERVICE_ACCOUNT_ID",
    "workspace_id": "$ANTHROPIC_WORKSPACE_ID"
  }
  JSON
  )

  ACCESS_TOKEN=$(echo "$RESPONSE" | jq -r .access_token)

  curl 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": 1024,
      "messages": [{"role": "user", "content": "Hello, Claude"}]
    }' | jq -r '.content[] | select(.type == "text") | .text'
  ```

  ```python Python
  import anthropic

  # Lee ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  # ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID y ANTHROPIC_IDENTITY_TOKEN_FILE
  # del entorno del job.
  client = anthropic.Anthropic()

  message = client.messages.create(
      model="claude-opus-5-5",
      max_tokens=1024,
      messages=[{"role": "user", "content": "Hello, Claude"}],
  )
  print(next(block.text for block in message.content if block.type == "text"))
  ```

  ```typescript TypeScript
  import Anthropic from "@anthropic-ai/sdk";

  // Lee ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  // ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID y ANTHROPIC_IDENTITY_TOKEN_FILE
  // del entorno del job.
  const client = new Anthropic();

  const message = await client.messages.create({
    model: "claude-opus-5-5",
    max_tokens: 1024,
    messages: [{ role: "user", content: "Hello, Claude" }]
  });
  for (const block of message.content) {
    if (block.type === "text") {
      console.log(block.text);
    }
  }
  ```

  ```go Go
  // Lee ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  // ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID y ANTHROPIC_IDENTITY_TOKEN_FILE
  // del entorno del job.
  client := anthropic.NewClient()

  message, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
  	Model:     anthropic.ModelClaudeOpus5_5,
  	MaxTokens: 1024,
  	Messages: []anthropic.MessageParam{
  		anthropic.NewUserMessage(anthropic.NewTextBlock("Hello, Claude")),
  	},
  })
  if err != nil {
  	panic(err)
  }
  for _, block := range message.Content {
  	if textBlock, ok := block.AsAny().(anthropic.TextBlock); ok {
  		fmt.Println(textBlock.Text)
  		break
  	}
  }
  ```

  ```java Java
  AnthropicClient client = AnthropicOkHttpClient.fromEnv();

  var message = client.messages().create(MessageCreateParams.builder()
          .model(Model.CLAUDE_OPUS_5_5)
          .maxTokens(1024)
          .addUserMessage("Hello, Claude")
          .build());

  IO.println(message.content());
  ```

  ```csharp C#
  // Lee ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  // ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID y ANTHROPIC_IDENTITY_TOKEN_FILE
  // del entorno del job.
  using var client = new AnthropicClient();

  var message = await client.Messages.Create(new()
  {
      Model = Model.ClaudeOpus5_5,
      MaxTokens = 1024,
      Messages = [new() { Role = Role.User, Content = "Hello, Claude" }],
  });
  foreach (var block in message.Content)
  {
      if (block.Value is TextBlock textBlock)
      {
          Console.WriteLine(textBlock.Text);
      }
  }
  ```

  ```bash CLI
  # Lee ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  # ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID y ANTHROPIC_IDENTITY_TOKEN_FILE
  # del entorno del job.
  ant messages create \
    --model claude-opus-5-5 \
    --max-tokens 1024 \
    --message '{role: user, content: "Hello, Claude"}'
  ```

  ```php PHP
  use Anthropic\Client;

  // Lee ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  // ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID y ANTHROPIC_IDENTITY_TOKEN_FILE
  // del entorno del job.
  $client = new Client();

  $message = $client->messages->create(
      model: 'claude-opus-5-5',
      maxTokens: 1024,
      messages: [['role' => 'user', 'content' => 'Hello, Claude']],
  );
  $textBlock = array_find($message->content, static fn ($block): bool => $block->type === 'text');
  echo $textBlock->text, PHP_EOL;
  ```

  ```ruby Ruby
  require "anthropic"

  # Lee ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  # ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID y ANTHROPIC_IDENTITY_TOKEN_FILE
  # del entorno del job.
  client = Anthropic::Client.new

  message = client.messages.create(
    model: "claude-opus-5-5",
    max_tokens: 1024,
    messages: [{role: "user", content: "Hello, Claude"}]
  )
  puts message.content.find { it.type == :text }.text
  ```
</CodeGroup>

Cada token de identidad emitido por GitHub expira aproximadamente cinco minutos después de su emisión. El endpoint de solicitud de tokens (`ACTIONS_ID_TOKEN_REQUEST_URL`) sigue siendo válido durante todo el trabajo, por lo que puedes obtener un token nuevo en cualquier momento. El SDK intercambia el token en el primer uso y almacena en caché el token de acceso de Anthropic resultante. Para trabajos que se ejecutan durante más tiempo que la vida útil del token de Anthropic, el SDK vuelve a leer `ANTHROPIC_IDENTITY_TOKEN_FILE` en cada actualización, así que vuelve a ejecutar el paso de obtención periódicamente (o envuélvelo en un bucle en segundo plano) para mantener el archivo actualizado. Como alternativa, excepto en Java, pasa al SDK un proveedor de tokens que llame a `ACTIONS_ID_TOKEN_REQUEST_URL` directamente en lugar de usar la ruta del archivo.

## Verifica la configuración

Un intercambio exitoso devuelve un `access_token` que comienza con `sk-ant-oat01-` y un valor `expires_in` en segundos. Un intercambio denegado devuelve un `401` `authentication_error` opaco con el mensaje fijo `Authentication failed`, sin importar qué verificación haya fallado; en la mayoría de los casos, el motivo de la denegación se registra en la entrada del intento en la [página del historial de autenticación](https://platform.claude.com/settings/workload-identity-federation?tab=history) en la Claude Console, y [Soluciona un intercambio fallido](https://platform.claude.com/docs/es/manage-claude/wif-reference#troubleshoot-a-failed-exchange) recorre las verificaciones en orden. La causa más común del lado de GitHub Actions es que el formato del claim `sub` no coincide (su segmento final varía entre eventos `ref:...`, `environment:...` y `pull_request`); la entrada del historial muestra el motivo `match_subject_prefix`.

## Restringe qué flujos de trabajo pueden autenticarse

<Warning>
  Un `subject_prefix` de `repo:your-org/*` por sí solo coincide con todos los repositorios de tu organización, y sin una restricción `ref` también coincide con las ejecuciones `pull_request` disparadas desde forks. Cualquiera que pueda abrir un pull request contra un repositorio coincidente podría obtener un token federado de Anthropic.
</Warning>

Limita el bloque `match` de la regla al alcance más estrecho que se ajuste a tu caso de uso:

* **Fija a un único repositorio:** Usa `subject_prefix: "repo:your-org/your-repo:*"` para que otros repositorios de la organización no coincidan.
* **Fija a una rama protegida:** Agrega `"ref": "refs/heads/main"` (o tu rama de lanzamiento) bajo `claims` para que las ejecuciones de pull request y las ramas de funcionalidades no coincidan.
* **Fija el propietario explícitamente:** Agrega `"repository_owner": "your-org"` bajo `claims` como una verificación de defensa en profundidad contra casos límite en el análisis de `sub`.
* **Fija a un entorno de despliegue:** Para trabajos de despliegue, haz coincidir `subject_prefix: "repo:your-org/your-repo:environment:production"` y controla ese entorno con revisores obligatorios en GitHub.

## Próximos pasos

* [Workload Identity Federation](https://platform.claude.com/docs/es/manage-claude/workload-identity-federation): recorrido completo de configuración, variables de entorno y precedencia de credenciales.
* [Autenticación](https://platform.claude.com/docs/es/manage-claude/authentication): cómo se compara la federación con las claves de API.
