# Workload identities API

The workload identities API lets you manage the workload identities that your Buildkite organization uses for [workload identity federation](/docs/apis/workload-identity-federation). A workload identity lets a trusted workload, such as a job running on a Buildkite agent, exchange the OpenID Connect (OIDC) token that its platform issues for a short-lived API access token. The API access token can only reach the pipelines and scopes that the workload identity allows.

> 📘 Required permissions
> These endpoints require both an API access token scope and organization administrator access for the user who owns the token:
> - List and get operations require the `read_workload_identities` [scope](/docs/apis/managing-api-tokens#token-scopes).
> - Create and update operations require the `write_workload_identities` scope.
> - Delete operations require the `delete_workload_identities` scope.
> Workload identities must also be enabled for your Buildkite organization. Otherwise, every endpoint returns `404 Not Found`.

## Workload identity data model

Workload identity endpoints return objects with the following fields:



| `uuid` | UUID of the workload identity. |
| --- | --- |
| `url` | Canonical API URL of the workload identity. |
| `client_id` | Client ID that workloads send in [token exchange requests](/docs/apis/workload-identity-federation#exchange-an-oidc-token). The client ID isn't a secret. |
| `name` | Name of the workload identity. |
| `description` | Description of the workload identity. |
| `oidc_issuer` | Issuer URL that the OIDC token's `iss` claim must equal. |
| `oidc_audience` | Audience that the OIDC token's `aud` claim must contain. |
| `oidc_subject` | Pattern that the OIDC token's `sub` claim must match, where `*` matches any characters. |
| `oidc_claims` | Object of top-level claims and the exact values that they must have in the OIDC token. Learn more in [Lock tokens to an owner with claims](/docs/apis/workload-identity-federation#lock-tokens-to-an-owner-with-claims). |
| `oidc_claim_conditions` | CEL expression that the OIDC token's claims must satisfy. `null` when not set. Learn more in [Write claim conditions](/docs/apis/workload-identity-federation#write-claim-conditions). |
| `jwks` | A [JSON Web Key Set (JWKS)](https://datatracker.ietf.org/doc/html/rfc7517) URL or inline JWKS JSON, as a string, that Buildkite uses instead of fetching the signing keys from the issuer. `null` when not set. |
| `grantable_token_scopes` | Array of the maximum scopes that an API access token can have. A token exchange request can ask for any subset of these scopes. |
| `default_token_scopes` | Array of the scopes that an API access token gets when the token exchange request doesn't ask for any. |
| `max_token_ttl_seconds` | Longest lifetime, in seconds, that an API access token can have. |
| `allowed_ip_addresses` | Space-separated CIDR ranges that can use the API access tokens that the workload identity mints. Any IP address can use them when not set. |
| `allowed_token_exchange_ip_addresses` | Space-separated CIDR ranges that can make token exchange requests for the workload identity. Any IP address can make them when not set. |
| `resource_grants` | Array of the pipelines that API access tokens can reach. Learn more in [Resource grants](#workload-identity-data-model-resource-grants). |
| `enabled` | Whether the workload identity accepts token exchange requests. |
| `created_at` | When the workload identity was created. |
| `updated_at` | When the workload identity was last updated. |



The API never returns API access tokens that the workload identity minted.

For example, this workload identity accepts OIDC tokens from jobs in one pipeline's builds of its `main` branch, running on Buildkite agents:

```json
{
  "uuid": "00000000-0000-4000-8000-000000000010",
  "url": "https://api.buildkite.com/v2/organizations/acme-inc/workload-identities/00000000-0000-4000-8000-000000000010",
  "client_id": "0123456789abcdef0123",
  "name": "Deploy pipeline",
  "description": "Jobs in the deploy pipeline on the main branch",
  "oidc_issuer": "https://agent.buildkite.com",
  "oidc_audience": "https://api.buildkite.com/v2/organizations/00000000-0000-4000-8000-000000000001",
  "oidc_subject": "organization:acme-inc:pipeline:deploy:ref:refs/heads/main:commit:*:step:*",
  "oidc_claims": {
    "organization_id": "00000000-0000-4000-8000-000000000001"
  },
  "oidc_claim_conditions": "claims.pipeline_id == \"00000000-0000-4000-8000-000000000002\"",
  "jwks": null,
  "grantable_token_scopes": ["read_builds", "write_builds"],
  "default_token_scopes": ["read_builds"],
  "max_token_ttl_seconds": 600,
  "allowed_ip_addresses": "192.0.2.0/24",
  "allowed_token_exchange_ip_addresses": "192.0.2.0/24",
  "resource_grants": [
    {
      "type": "pipeline",
      "access": "selected",
      "ids": ["00000000-0000-4000-8000-000000000002"]
    }
  ],
  "enabled": true,
  "created_at": "2026-11-01T10:00:00.000Z",
  "updated_at": "2026-11-01T10:00:00.000Z"
}
```

### Resource grants

The `resource_grants` array holds one object for pipeline access, with the following fields. When the array is empty, API access tokens can't reach any pipelines.



| `type` | The resource type. Must be `pipeline`. |
| --- | --- |
| `access` | `none` for no pipelines, `selected` for the pipelines in `ids`, or `all` for every pipeline in your Buildkite organization, including pipelines created later. |
| `ids` | UUIDs of up to 20 pipelines in your Buildkite organization. Required when `access` is `selected`, with at least one UUID, and not allowed otherwise. |



These fields match the **Resource Access** setting in the Buildkite interface. Learn more in [Choose pipeline access](/docs/apis/workload-identity-federation#choose-pipeline-access).

## List workload identities

Returns a cursor-paginated list of the workload identities in your Buildkite organization, ordered from newest to oldest.

```bash
curl -H "Authorization: Bearer $TOKEN" \
  -X GET "https://api.buildkite.com/v2/organizations/{org.slug}/workload-identities"
```

The response contains [workload identity objects](#workload-identity-data-model) in `items`, and pagination URLs in `links` and the `Link` response header:

```json
{
  "items": [
    {
      "uuid": "00000000-0000-4000-8000-000000000010",
      "url": "https://api.buildkite.com/v2/organizations/acme-inc/workload-identities/00000000-0000-4000-8000-000000000010",
      "client_id": "0123456789abcdef0123",
      "name": "Deploy pipeline",
      "description": "Jobs in the deploy pipeline on the main branch",
      "oidc_issuer": "https://agent.buildkite.com",
      "oidc_audience": "https://api.buildkite.com/v2/organizations/00000000-0000-4000-8000-000000000001",
      "oidc_subject": "organization:acme-inc:pipeline:deploy:ref:refs/heads/main:commit:*:step:*",
      "oidc_claims": {
        "organization_id": "00000000-0000-4000-8000-000000000001"
      },
      "oidc_claim_conditions": "claims.pipeline_id == \"00000000-0000-4000-8000-000000000002\"",
      "jwks": null,
      "grantable_token_scopes": ["read_builds", "write_builds"],
      "default_token_scopes": ["read_builds"],
      "max_token_ttl_seconds": 600,
      "allowed_ip_addresses": "192.0.2.0/24",
      "allowed_token_exchange_ip_addresses": "192.0.2.0/24",
      "resource_grants": [
        {
          "type": "pipeline",
          "access": "selected",
          "ids": ["00000000-0000-4000-8000-000000000002"]
        }
      ],
      "enabled": true,
      "created_at": "2026-11-01T10:00:00.000Z",
      "updated_at": "2026-11-01T10:00:00.000Z"
    }
  ],
  "links": {
    "self": "https://api.buildkite.com/v2/organizations/acme-inc/workload-identities",
    "next": "https://api.buildkite.com/v2/organizations/acme-inc/workload-identities?after=CURSOR"
  }
}
```

Optional [query string parameters](/docs/apis/rest-api#query-string-parameters):



| `per_page` | Number of results per page. _Default:_ `30` _Maximum:_ `100` |
| --- | --- |
| `after` | Return results after this cursor. Can't be used with `before`. |
| `before` | Return results before this cursor. Can't be used with `after`. |



Required scope: `read_workload_identities`

Success response: `200 OK`

Error response: `400 Bad Request` when the request has both `after` and `before`, an invalid cursor, or a `per_page` value below `1` or above `100`.

## Get a workload identity

Returns a [workload identity object](#workload-identity-data-model).

```bash
curl -H "Authorization: Bearer $TOKEN" \
  -X GET "https://api.buildkite.com/v2/organizations/{org.slug}/workload-identities/{uuid}"
```

Required scope: `read_workload_identities`

Success response: `200 OK`

Error response: `404 Not Found` when your Buildkite organization has no workload identity with this UUID.

## Create a workload identity

Creates a workload identity, and returns the new [workload identity object](#workload-identity-data-model), including its `client_id`.

```bash
curl -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -X POST "https://api.buildkite.com/v2/organizations/{org.slug}/workload-identities" \
  -d '{
    "name": "Deploy pipeline",
    "description": "Jobs in the deploy pipeline on the main branch",
    "oidc_issuer": "https://agent.buildkite.com",
    "oidc_audience": "https://api.buildkite.com/v2/organizations/00000000-0000-4000-8000-000000000001",
    "oidc_subject": "organization:acme-inc:pipeline:deploy:ref:refs/heads/main:commit:*:step:*",
    "oidc_claims": {
      "organization_id": "00000000-0000-4000-8000-000000000001"
    },
    "oidc_claim_conditions": "claims.pipeline_id == \"00000000-0000-4000-8000-000000000002\"",
    "grantable_token_scopes": ["read_builds", "write_builds"],
    "default_token_scopes": ["read_builds"],
    "max_token_ttl_seconds": 600,
    "allowed_ip_addresses": "192.0.2.0/24",
    "allowed_token_exchange_ip_addresses": "192.0.2.0/24",
    "resource_grants": [
      {
        "type": "pipeline",
        "access": "selected",
        "ids": ["00000000-0000-4000-8000-000000000002"]
      }
    ]
  }'
```

Required [request body properties](/docs/apis/rest-api#request-body-properties):



| `name` | Name of the workload identity. |
| --- | --- |
| `description` | Description of the workload identity. |
| `oidc_issuer` | Issuer URL that the OIDC token's `iss` claim must equal. Must be an HTTPS URL with no query or fragment. _Example:_ `"https://agent.buildkite.com"` |
| `oidc_audience` | Audience that the OIDC token's `aud` claim must contain. Use a value that's unique to your Buildkite organization. The API saves a workload identity without an audience, but Buildkite refuses its token exchange requests until it has one. |
| `oidc_subject` | Pattern that the OIDC token's `sub` claim must match, where `*` matches any characters. The pattern must contain at least one character that isn't a wildcard, so `*` on its own isn't allowed. _Example:_ `"organization:acme-inc:pipeline:deploy:*"` |
| `grantable_token_scopes` | Array of the maximum scopes that an API access token can have. Must include at least one scope. The available scopes are `read_pipelines`, `write_pipelines`, `read_builds`, `write_builds`, `retry_builds`, `read_job_env`, `read_build_logs`, `write_build_logs`, `read_artifacts`, `write_artifacts`, `read_organizations`, and `write_organizations`. |



Optional [request body properties](/docs/apis/rest-api#request-body-properties):



| `oidc_claims` | Object of top-level claims and the exact values that they must have in the OIDC token, up to 20. Learn more in [Lock tokens to an owner with claims](/docs/apis/workload-identity-federation#lock-tokens-to-an-owner-with-claims). Workload identities for [Buildkite agents](/docs/apis/workload-identity-federation/buildkite-agents) need an `organization_id` or `organization_slug` claim. _Example:_ `{"organization_id": "00000000-0000-4000-8000-000000000001"}` |
| --- | --- |
| `oidc_claim_conditions` | CEL expression of up to 4,096 bytes that the OIDC token's claims must satisfy. Learn more in [Write claim conditions](/docs/apis/workload-identity-federation#write-claim-conditions). _Example:_ `"claims.build_branch == \"main\""` |
| `jwks` | A JWKS URL, or inline JWKS JSON as a string, for when Buildkite can't fetch the signing keys from the issuer. Learn more in [OIDC token requirements](/docs/apis/workload-identity-federation#oidc-token-requirements). |
| `default_token_scopes` | Array of the scopes that an API access token gets when the token exchange request doesn't ask for any. Must be a subset of `grantable_token_scopes`. |
| `resource_grants` | Array of the pipelines that API access tokens can reach. Learn more in [Resource grants](#workload-identity-data-model-resource-grants). _Default:_ `[]`, which reaches no pipelines |
| `max_token_ttl_seconds` | Longest lifetime, in seconds, that an API access token can have, from 30 to 3,600. A token also expires when the OIDC token that it was exchanged for expires. _Default:_ `3600` |
| `allowed_ip_addresses` | Space-separated CIDR ranges that can use the API access tokens that the workload identity mints. _Example:_ `"192.0.2.0/24 198.51.100.12/32"` |
| `allowed_token_exchange_ip_addresses` | Space-separated CIDR ranges that can make token exchange requests for the workload identity. _Example:_ `"192.0.2.0/24"` |
| `enabled` | Whether the workload identity accepts token exchange requests. _Default:_ `true` |



Required scope: `write_workload_identities`

Success response: `201 Created`

Error response: `422 Unprocessable Entity` when the request has an unknown field, a value of the wrong type, or settings that Buildkite rejects, such as an unavailable scope, an invalid claim or claim condition, or a pipeline from another Buildkite organization. The response message describes each problem.

## Update a workload identity

Updates the fields in the request body, and returns the updated [workload identity object](#workload-identity-data-model). Fields that the request leaves out keep their values. An array in the request, such as `grantable_token_scopes` or `resource_grants`, replaces the existing array.

```bash
curl -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -X PATCH "https://api.buildkite.com/v2/organizations/{org.slug}/workload-identities/{uuid}" \
  -d '{
    "resource_grants": [
      {
        "type": "pipeline",
        "access": "none"
      }
    ]
  }'
```

The request body accepts the same properties as [create a workload identity](#create-a-workload-identity), and all of them are optional.

Set `enabled` to `false` to disable the workload identity. Buildkite stops accepting its token exchange requests, and revokes its active API access tokens shortly afterwards. Set `enabled` to `true` to accept token exchange requests again. Other changes only apply to new token exchanges. API access tokens that the workload identity already minted keep their pipeline access, scopes, IP restrictions, and expiry.

Required scope: `write_workload_identities`

Success response: `200 OK`

Error responses: `404 Not Found` when your Buildkite organization has no workload identity with this UUID, or `422 Unprocessable Entity` when the update is invalid. An invalid update changes nothing, including `enabled`.

## Delete a workload identity

Permanently deletes a workload identity, and revokes its API access tokens. You can't undo this action.

```bash
curl -H "Authorization: Bearer $TOKEN" \
  -X DELETE "https://api.buildkite.com/v2/organizations/{org.slug}/workload-identities/{uuid}"
```

Required scope: `delete_workload_identities`

Success response: `204 No Content`

Error response: `404 Not Found` when your Buildkite organization has no workload identity with this UUID.
