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.
This endpoint is rate-limited to 10 requests per minute per IP.

Request

Body Parameters

Grant type: authorization_code

grant_type
string
required
Must be authorization_code.
client_id
string
required
Your app’s public client identifier.
client_secret
string
required
Your app’s client secret. Verified against the stored (hashed) secret — the raw value is never persisted.
code
string
required
The one-time authorization code from GET /apps/oauth/authorize. Single-use, expires in 10 minutes.
state
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.
code_verifier
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

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

Response

status
integer
200 on success.
state
string
success or error.
data
object

Example Response

Token Lifetimes

TokenTTLNotes
Access token24 hours (expires_in: 86400)Used as Authorization: Bearer on /api/v1/*.
Refresh token30 daysResets on every successful rotation.
Authorization code10 minutesSingle-use; deleted after exchange.
When you refresh, the old refresh token is blacklisted for 31 days (only its SHA-256 hash is stored) and a brand new pair is issued. Re-using a rotated refresh token returns 401 Token has been revoked.

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

HTTPError messageWhen
400Unsupported grant_typegrant_type is not authorization_code or refresh_token.
400Invalid or expired authorization codecode unknown (consumed or > 10 minutes old).
400Invalid state parameterstate doesn’t match the value bound to the code.
400State validation failedstate unknown or client_id doesn’t match.
400code_verifier is required for this authorization codeAuthorize call sent PKCE challenge but token call omitted verifier.
400code_verifier must be 43-128 charactersPKCE verifier length out of range.
400code_verifier does not match the code_challengePKCE verification failed (constant-time compare).
401Invalid client credentialsclient_id/client_secret doesn’t match.
401Invalid refresh tokenNo installation has this refresh token.
401Refresh token has expired. Please re-authenticate.> 30 days since last rotation.
401Token has been revokedRefresh token was rotated or revoked.
409Per-shop function limit exceeded for <type>: max <N>Function caps prevent a new active install.
429(throttler)More than 10 requests/minute from this IP.

Refresh Loop Pattern

Security Notes

  • Always send this request server-side. Never expose client_secret in browser code.
  • For browser-based public clients, use PKCE (no client_secret).
  • 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 secrets are stored hashed.