GXS Internal
Identity server

Exchange an organization API key

Use the Graphxsource token exchange extension to obtain an API access token for an organization.

Use this flow when a backend holds an organization API key and needs a short-lived access token for a resource. This is a Graphxsource extension of the OAuth token endpoint. Better Auth's API key documentation explains API key management; the exchange rules below come from the identity server's custom plugin.

Prepare the credentials

You need a confidential OAuth client registered for urn:ietf:params:oauth:grant-type:token-exchange, a nonempty client_credentials_scopes allowance, and a valid organization API key. The server accepts keys from the org-keys configuration for this flow. User API keys are not exchangeable here.

The requested scope must be allowed by the key, the client's machine scope allowance, and the resource policy. The server also excludes OIDC scopes, scopes ending in .admin, and scopes ending in .* from exchange. Client authentication remains required even though you present an API key.

Request a resource token

curl --fail-with-body \
  --request POST "$IDENTITY_ORIGIN/api/auth/oauth2/token" \
  --user "$CLIENT_ID:$CLIENT_SECRET" \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
  --data-urlencode "subject_token=$ORGANIZATION_API_KEY" \
  --data-urlencode 'subject_token_type=urn:graphxsource:params:oauth:token-type:api-key' \
  --data-urlencode 'requested_token_type=urn:ietf:params:oauth:token-type:access_token' \
  --data-urlencode "resource=$RESOURCE" \
  --data-urlencode 'scope=orders.read'

This example uses client_secret_basic. Use the authentication method registered for your client. Send exactly one resource. This extension rejects audience, actor_token, and actor_token_type parameters. It currently resolves organization API keys as subject tokens; it does not accept arbitrary user access tokens for on-behalf-of exchange.

The response includes an access_token, expires_in, and issued_token_type set to urn:ietf:params:oauth:token-type:access_token. Use the token to call the resource API as described in Protect a resource.

Enforce the organization context

The exchanged token includes tenant_id, active_organization.id, and act.sub, which identifies the OAuth client acting with the key. Its subject is a client, not a signed-in user.

The receiving API must validate the token's signature, issuer, audience, expiry, and scopes, then restrict data access to the verified organization. The user permission-check endpoints return UNSUPPORTED_SUBJECT for this token. Do not replace that denial with an automatic user grant.

The generated OpenAPI token schema may omit these extension-specific fields. Use this page for the exchange parameters and the OAuth API reference for the shared token response.

On this page