# How do I run in production without a stored API key?

> Register the identity that your cloud or your CI system gives a workload. The SDK proves it and gets a short-lived token, so the app stores no API key.

Canonical: https://provely.sh/docs/workload-identity  
Last updated: 2026-10-18  

**Register the workload identity of the app once, as an admin, and remove the stored API key. The SDK gets an identity token from the cloud or the CI system and exchanges it for a short-lived Provely token.**

The API key keeps working. A workload identity is a second way to authenticate, not a replacement that you must use.

## Which identities can a workload prove?

| Provider | The proof | The subject that you register |
| --- | --- | --- |
| GitHub Actions | The OIDC token of the workflow run | `repo:acme/payments:ref:refs/heads/main` |
| AWS | A signed `sts:GetCallerIdentity` request | `arn:aws:iam::123456789012:role/payments-prod` |
| GCP | The identity token of the attached service account | `payments@acme-prod.iam.gserviceaccount.com` |
| Kubernetes | A projected service account token | `system:serviceaccount:payments:api` |
| Another OIDC issuer | An identity token in a file | The `sub` claim of the token |

In a subject, a star matches a run of characters that holds no colon. A GitHub subject must name one literal owner and repository, so a subject for every repository is refused. A GCP subject is one exact service account email.

## How do I register an identity?

### Register a workload identity

1. **Open the page** Sign in to the console as an owner or an admin. Open Account settings, then Workload identities.
2. **Choose the provider** Choose the provider. AWS, GCP, and GitHub Actions fix the issuer. For Kubernetes and another OIDC issuer, give the https issuer URL.
3. **State the subject** State the subject, and any extra claims that the token must carry with an exact value.
4. **Copy the audience** Copy the audience that the page shows, for example `https://api.provely.sh/o/org_01EXAMPLE`. Set `PROVELY_WORKLOAD_AUDIENCE` to it in the app.

> Only a signed-in person with the admin permission registers, changes, or revokes an identity. An API key gets 403. The agent holds the API key and is untrusted, so its credential never decides who else may act for the organization.

## Why does the server issue the audience?

The audience names your organization, so a token for your audience never matches the registration of another organization. A registration cannot choose another audience.

With a shared audience, another organization could register your subject and make your exchange ambiguous. The exchange then refuses, and your app stops. The audience of each organization makes this impossible.

An app that sends another audience gets `audience_not_registered`. The message names `PROVELY_WORKLOAD_AUDIENCE` and the Workload identities page.

## How do I set up each provider?

### GitHub Actions

Give the job the permission to request an OIDC token. The SDK reads `ACTIONS_ID_TOKEN_REQUEST_URL` and `ACTIONS_ID_TOKEN_REQUEST_TOKEN`, which GitHub sets when the permission is present.

```yaml
permissions:
  id-token: write
  contents: read
env:
  PROVELY_WORKLOAD_AUDIENCE: https://api.provely.sh/o/org_01EXAMPLE
```

### AWS

Run the app with role credentials: Lambda, ECS, or any compute that sets `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_SESSION_TOKEN`. The SDK signs one `sts:GetCallerIdentity` request with those credentials and sends the signed request. Provely sends it to AWS STS, and AWS states the role. No AWS credential leaves the app. Register the role ARN, not the assumed-role ARN.

```bash
PROVELY_WORKLOAD_AUDIENCE=https://api.provely.sh/o/org_01EXAMPLE
```

### GCP

Attach a service account to the Cloud Run service, the Cloud Function, the GKE workload, or the Compute Engine instance. The SDK asks the metadata server for an identity token for the audience in `PROVELY_WORKLOAD_AUDIENCE`. Register the email of the service account.

### Kubernetes

Mount a projected service account token with the audience of the organization at `/var/run/secrets/provely/token`, or set `PROVELY_KUBERNETES_TOKEN_FILE` to its path. The issuer of the cluster must publish its discovery document and its key set on a public https URL. EKS, GKE, and AKS do this.

```yaml
volumes:
  - name: provely-token
    projected:
      sources:
        - serviceAccountToken:
            path: token
            audience: https://api.provely.sh/o/org_01EXAMPLE
            expirationSeconds: 3600
# mount provely-token at /var/run/secrets/provely
```

### Another OIDC issuer

Write the identity token to a file, and set `PROVELY_IDENTITY_TOKEN_FILE` to its path. The SDK reads the file again at each refresh, so a rotated token is used.

## Which credential does the SDK use?

1. The API key, when `PROVELY_API_KEY` is set.
2. A workload identity, when the SDK detects one.
3. The CLI login file that `provely login` writes.

Get your API key: sign in at [app.provely.sh/account/api-keys](https://app.provely.sh/account/api-keys), open API keys, and create a key. The dashboard shows the key once. Keep it in an environment variable, never in code. Or run `provely login` once on your machine: the SDKs then read the key that it stores.

| Variable | What it does |
| --- | --- |
| `PROVELY_WORKLOAD_IDENTITY` | `auto` (the default) detects the environment. `off` disables workload identity. `github_actions`, `aws`, `gcp`, `kubernetes`, or `file` selects one provider. |
| `PROVELY_WORKLOAD_AUDIENCE` | The audience that the Workload identities page shows. Set it in production. The default, the API origin, matches no registration. |
| `PROVELY_IDENTITY_TOKEN_FILE` | The file that holds the token of another OIDC issuer. |
| `PROVELY_KUBERNETES_TOKEN_FILE` | The projected token file. The default is `/var/run/secrets/provely/token`. |

The SDK refreshes the token before it expires. It never writes the identity token or the Provely token to a log or an error.

## What does the token permit?

The exchange answers the same short-lived token that `POST /v1/tokens` answers for an API key. The token belongs to the organization of the registration. It carries the agent scopes of a normal SDK key and no admin scope. It cannot mint another token. Runs are metered for the organization, whatever the credential.

A revoked identity stops the next exchange at once. A token that it already bought expires at its own expiry, within 15 minutes.

## What does this not cover?

- The SDK does not read EC2 instance metadata credentials today. On EC2, set the role credentials in the environment, or run on ECS or Lambda.
- A stolen identity token acts as the workload until it expires. Keep the token life short at the issuer.
- GitHub and Google sign tokens for every customer. The subject is the security boundary, so register an exact subject.

## Read next

- [Read the credential boundary](https://provely.sh/docs/security)
- [Read the quick start](https://provely.sh/docs/quick-start)
- [Read the API reference](https://provely.sh/docs/api)
