Skip to main content
POST
Token Exchange

Token Exchange

Exchanges either a one-time authorization code (from GET /apps/oauth/authorize) or a long-lived refresh_token for a fresh access/refresh token pair. Two grant types are supported:
  • authorization_code — first-time install flow.
  • refresh_token — silent token rotation after the 24h access token expires.
No other grant types are supported. In particular, RFC 8693 token exchange (urn:ietf:params:oauth:grant-type:token-exchange) is not available — an App Bridge session token cannot be exchanged for an Admin API access token. Session tokens authenticate the embedded user to your own backend; the Admin API access token comes from the install handoff code exchanged here with grant_type=authorization_code.
This endpoint is rate-limited to 10 requests per minute per IP.

Request

Body Parameters

Grant type: authorization_code

string
required
Must be authorization_code.
string
required
Your app’s public client identifier.
string
required
Your app’s client secret, sent over TLS. Compared (constant-time) against the value returned by /apps/credentials. Because the secret doubles as the HS256 signing key for App Bridge session tokens, it is stored in plaintext (not hashed) — the host must sign JWTs with the same value your app verifies with. Treat it as a signing key: never expose it client-side, and rotate it from the developer dashboard if it leaks.
string
required
The one-time authorization code from GET /apps/oauth/authorize. Single-use, expires in 10 minutes.
string
required
The server-generated state token from the authorize call. Must match what was bound to the code server-side or the request fails with Invalid state parameter.
string
Required when the authorize call sent a code_challenge. Length 43-128 chars. For S256 flows, the server SHA-256 hashes this and compares (constant-time) against the stored challenge.

Grant type: refresh_token

string
required
Must be refresh_token.
string
required
The current refresh token. Must not be expired, blacklisted, or already rotated.

Response

integer
200 on success.
string
success or error.
object

Example Response

Token Lifetimes

When you refresh, the old refresh token is replaced with a brand new pair (and its SHA-256 hash is blacklisted for 31 days as a backstop). Re-using a rotated refresh token returns 401 Invalid refresh token — the replayed value no longer matches any installation, so the lookup fails before the blacklist is even consulted. Either way the status is 401; treat any 401 from the token endpoint as “re-run the install handoff”.

Install/Re-install Limits

When grant_type=authorization_code would create a brand new active installation (or re-activate a previously disabled one), the server checks per-shop function caps before issuing tokens. If the app ships functions and the merchant already has too many active installs of apps with the same function types, the endpoint returns:
This check is skipped when re-issuing tokens for an already-active installation (no status flip = no new active install counted).

Error Codes

Refresh Loop Pattern

Security Notes

  • Always send this request server-side. Never expose client_secret in browser code.
  • client_secret is always required for grant_type=authorization_code — PKCE is verified in addition to the secret, not instead of it, so even PKCE flows must exchange the code server-side.
  • Successful exchange consumes both the code and its state — both are deleted atomically.
  • client_secret verification is brute-force resistant: requests are throttled by the IP rate limit (10/min) and compared in constant time. The secret is stored in plaintext (it is also the App Bridge session-token signing key), so keep it server-side only and rotate it if exposed.