Token Endpoint
Exchange codes for tokens and renew access tokens using refresh tokens.
The token endpoint issues access_token and refresh_token. It supports three grant types: authorization_code, refresh_token, and urn:ietf:params:oauth:grant-type:token-exchange. This page covers the first two; see Token Exchange for the third.
Endpoint
POST https://app.whatalo.com/oauth/token
Content-Type: application/x-www-form-urlencodedToken endpoint rate limits:
- 60 requests per minute per IP
- 30 requests per minute per
client_id
Exceeding either limit returns HTTP 429 with a Retry-After header. Implement exponential backoff.
Client authentication
Confidential clients (those with a client_secret) must authenticate. Public clients (token_endpoint_auth_method: "none") send no credentials.
HTTP Basic (recommended for confidential clients)
Authorization: Basic BASE64(client_id:client_secret)Body params (alternative)
client_id=abc123def456&client_secret=s3cr3t_v4lu3_xyz987wvu654Both methods are equivalent. Do not use both simultaneously.
Grant type: authorization_code
Exchange the authorization code received from the authorization flow.
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic YWJjMTIzZGVmNDU2OihzM2NyM3RfdmFsdWU=
grant_type=authorization_code
&code=SINGLE_USE_AUTH_CODE
&redirect_uri=https%3A%2F%2Fmy-app.com%2Foauth%2Fcallback
&client_id=abc123def456ghi789jkl012
&code_verifier=THE_PKCE_CODE_VERIFIERParameters
| Parameter | Required | Description |
|---|---|---|
grant_type | Yes | authorization_code |
code | Yes | Code received in the callback |
redirect_uri | Yes | Must match exactly the URI used in the authorize request |
client_id | Yes (if not in Basic) | Your client_id |
code_verifier | Yes | The original PKCE verifier (before hashing) |
Response
{
"access_token": "DlcGJFC7UmSx2fYKj7Bg3jhT9QgpyIzNcx7OGAoFu8U",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "LKr5XU5xyiD0XscBPSO9tm0DsSK2AKsWL2fOe9an6v8",
"scope": "read:products read:orders"
}Grant type: refresh_token
When the access_token expires (every hour), use the refresh_token to obtain a new one.
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic YWJjMTIzZGVmNDU2OihzM2NyM3RfdmFsdWU=
grant_type=refresh_token
&refresh_token=LKr5XU5xyiD0XscBPSO9tm0DsSK2AKsWL2fOe9an6v8
&client_id=abc123def456ghi789jkl012Response
{
"access_token": "00e1NyN_TYwjunXFjnNpMcs6PdJgXqoVMfeTfJgkhP0",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "n-zDToBmLXUEOrPtOH4usLAYOxZkmyU1WKwqOYJRaZ8",
"scope": "read:products read:orders"
}Refresh token rotation is strict. The previous refresh token is revoked immediately. If you present an already-revoked token, Whatalo revokes the entire token family — the user will need to re-authorize from scratch. Avoid race conditions: serialize refreshes in your infrastructure.
Optional downscoping
You can request a subset of the originally granted scopes:
&scope=read:productsRequesting broader scopes than those granted returns invalid_scope.
Token durations
| Token | Duration | Notes |
|---|---|---|
access_token | 1 hour | Send as Authorization: Bearer <token> |
refresh_token | 30 days | Rotates on each use — always store the new one |
authorization_code | 10 minutes | Single-use, bound to the PKCE verifier |
Using the access token
GET https://api.whatalo.com/v1/products
Authorization: Bearer DlcGJFC7UmSx2fYKj7Bg3jhT9QgpyIzNcx7OGAoFu8UError handling
| Error | HTTP | Cause |
|---|---|---|
invalid_grant | 400 | Code expired, already used, or incorrect code_verifier |
invalid_client | 401 | Invalid or missing client credentials |
invalid_request | 400 | Missing or malformed parameter |
invalid_scope | 400 | Requested scope exceeds those granted |
rate_limit_exceeded | 429 | Rate limit exceeded — see Retry-After |
See the complete reference in OAuth errors.