OpenAI Workload Identity Federation

Let a VM call the OpenAI API without storing an OpenAI API key. The VM gets a short-lived exe.dev token, and OpenAI exchanges it for a short-lived access token for one of your project's service accounts.

Usage is billed to your OpenAI organization. To use exe.dev's built-in models instead, see the LLM integration.

OpenAI's documentation says that OIDC issuers other than the providers in its setup guides aren't supported yet, and asks you to contact OpenAI support if your provider isn't listed. exe.dev is a custom OIDC issuer, so your organization may need OpenAI to enable it.

Setup uses two browser tabs: the exe.dev Integrations page and OpenAI's Workload Identity Provider settings. You need permission to manage Workload Identity Providers in your OpenAI organization.

1. Start the integration in exe.dev

On the Integrations page, add an Identity Federation integration and choose OpenAI. Enter a name, such as openai-wif. To use it through an LLM integration (step 4), you don't need to attach it to any VMs.

The dialog shows an Issuer and a Subject. You will paste both into OpenAI in the next step. Keep the dialog open; the subject is reserved for 15 minutes.

The integration's exe.dev tokens last 15 minutes, the longest allowed. An OpenAI access token never outlives the token it was exchanged for, so a shorter lifetime only means more frequent exchanges.

2. Create the provider and mapping in OpenAI

In OpenAI, open Workload Identity Provider settings and create a provider:

OpenAI field Value
Name Any unique name, such as exe-dev
OIDC Issuer URL The exe.dev Issuer
Audience https://api.openai.com/v1
Custom URL for OIDC discovery, uploaded JWKS Leave both off; OpenAI uses the issuer's OIDC discovery

Then, on the provider's details page, add a service account mapping:

OpenAI field Value
Key sub
Value The exe.dev Subject, exactly. Do not use a wildcard.
Project The project that receives the API usage
Service account A service account in that project; the VM acts as it
Permissions Optional. Leave empty to allow everything the service account can do, or narrow it, for example to api.model.request and api.model.read

OpenAI shows the provider's ID on its details page and the service account's ID in the project's settings. You need both in the next step.

3. Enter the IDs and save

Back in the exe.dev dialog, fill in:

Field Where it comes from
Identity provider ID The Workload Identity Provider you just created
Service account ID The mapping's service account
Project ID (optional) The mapping's project, proj_..., from the project's settings

Click Run. If you don't have the IDs yet, you can save with the fields empty and edit the integration later to add them.

The access token is always bound to the mapping's project, so the project ID is optional. When it's set, requests send it as the OpenAI-Project header. OpenAI accepts that header only if it names the token's project and rejects any other project with 401 mismatched_project. The header doesn't switch projects; it makes requests fail instead of using a project you didn't expect, for example after someone points the mapping at a different project.

4. Use it from an LLM integration

On the Integrations page, add an LLM integration, set its OpenAI provider to Workload identity, and pick this integration. Attach the LLM integration to your VMs; the workload identity integration doesn't need to be attached. exe.dev exchanges and renews OpenAI tokens for you and, if you set a project ID, sends it as OpenAI-Project. Both integrations must be in the same scope: personal for a personal LLM integration, team for a team one.

Or from the CLI:

ssh exe.dev integrations add llm --name gpt \
  --openai=wif --openai-wif=openai-wif \
  --anthropic=disabled --fireworks=disabled --attach vm:example-vm

On the VM, OpenAI SDKs and tools use the LLM integration's /v1 URL as their base URL and need no API key:

curl https://gpt.int.exe.xyz/v1/responses \
  -H 'content-type: application/json' \
  -d '{"model":"gpt-5.5","input":"Hello from exe.dev"}'

For a team integration, use https://gpt.team.exe.xyz. To run Codex, see Use with Codex.

Other uses: call OpenAI directly

To call OpenAI yourself instead of through an LLM integration, for example from code that does its own token exchange, attach this integration to the VM. Then run this on the VM. It reads the IDs from the integration, exchanges a fresh exe.dev token for an OpenAI access token, and sends a request:

EXE_WIF_URL=https://openai-wif.int.exe.xyz
META="$(curl -fsS "$EXE_WIF_URL/metadata")"
PROJECT_ID="$(echo "$META" | jq -r '.project_id // empty')"
JWT="$(curl -fsS "$EXE_WIF_URL/token" | jq -er .token)"

ACCESS_TOKEN="$(
  jq -n --arg jwt "$JWT" --argjson meta "$META" '{
    grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
    subject_token_type: "urn:ietf:params:oauth:token-type:jwt",
    subject_token: $jwt,
    identity_provider_id: $meta.identity_provider_id,
    service_account_id: $meta.service_account_id
  }' |
  curl -fsS https://auth.openai.com/oauth/token \
    -H 'content-type: application/json' -d @- |
  jq -er .access_token
)"

curl -fsS https://api.openai.com/v1/responses \
  -H "authorization: Bearer $ACCESS_TOKEN" \
  ${PROJECT_ID:+-H "OpenAI-Project: $PROJECT_ID"} \
  -H 'content-type: application/json' \
  -d '{"model":"gpt-5.5","input":"Hello from exe.dev"}'

unset JWT ACCESS_TOKEN

Replace openai-wif with your integration's name. For a team integration, use https://<name>.team.exe.xyz.

Tokens

  • Fetch a new exe.dev token from /token for every exchange.
  • Exchange again before expires_at (or expires_in seconds after the exchange). OpenAI returns no refresh token.
  • Both tokens are credentials. Don't print, log, or commit them.

OpenAI's SDKs can do the exchange and renewal for you. See OpenAI's workload identity federation guide and its token exchange reference.

Use the CLI instead

The CLI prints the issuer and subject only after it creates the integration, so add the IDs with a second command:

ssh exe.dev integrations add wif --name openai-wif \
  --audience https://api.openai.com/v1 --consumer openai --ttl 15m \
  --attach vm:example-vm

The --attach is only needed to call OpenAI directly from the VM. Create the OpenAI provider and mapping with the printed Issuer and Subject, then:

ssh exe.dev integrations edit openai-wif \
  --metadata=identity_provider_id=IDENTITY_PROVIDER_ID \
  --metadata=service_account_id=SERVICE_ACCOUNT_ID \
  --metadata=project_id=PROJECT_ID

The project_id line is optional. Add --team to add for a team integration. edit replaces all metadata, so include every ID each time.

Troubleshooting

  • The exchange is rejected. Check that the provider's issuer and audience and the mapping's sub value exactly match the integration, that the mapping is enabled, and that the request names the right provider and service account. OpenAI's error reference lists the causes.
  • The exchange works but API calls fail. The access token has the service account's project access and the mapping's permissions. Check those, and the project's IP allowlist if it has one. 401 mismatched_project means the integration's project ID isn't the mapping's project.
  • /token or /metadata doesn't respond. The integration isn't attached to this VM.