Token Exchange
Endpoint
Section titled “Endpoint”POST https://auth.beshoy.ai/oauth/tokenContent-Type: application/jsonAuthorization Code Grant
Section titled “Authorization Code Grant”Exchange a one-time authorization code for tokens.
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
grant_type | string | Yes | "authorization_code" |
code | string | Yes | Authorization code from callback |
code_verifier | string | Yes | Original PKCE code verifier |
client_id | string | Yes | Project ID |
client_secret | string | Yes | Project client secret |
redirect_uri | string | Yes | Must match the authorize request |
Example Request
Section titled “Example Request”curl -X POST https://auth.beshoy.ai/oauth/token \ -H "Content-Type: application/json" \ -d '{ "grant_type": "authorization_code", "code": "abc123def456", "code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk", "client_id": "proj_gym", "client_secret": "your-client-secret-hex", "redirect_uri": "https://gym.beshoy.ai/api/auth/callback" }'Success Response
Section titled “Success Response”{ "access_token": "eyJhbGciOiJIUzI1NiJ9...", "refresh_token": "a1b2c3d4e5f6...", "token_type": "Bearer", "expires_in": 300}Verification Steps
Section titled “Verification Steps”The auth service performs these checks in order:
- Rate limit — 20 requests per 60 seconds per
client_id - Project lookup —
client_idmust exist - Client secret — Verified via Argon2 against stored hash
- Code lookup — Retrieved from KV (
pkce:{code}) - Code consumed — Deleted from KV immediately (one-time use)
- Redirect URI match — Must match what was stored with the code
- Project match — Code’s project must equal
client_id - PKCE verification —
BASE64URL(SHA256(code_verifier)) === stored_challenge
Error Responses
Section titled “Error Responses”| Error | Status | Cause |
|---|---|---|
Missing client_id | 400 | No client_id in request body |
Rate limited | 429 | Too many requests for this client |
Invalid client_id | 400 | Project not found |
Invalid client_secret | 401 | Secret doesn’t match |
Missing required fields | 400 | Missing code, code_verifier, or redirect_uri |
Invalid or expired code | 400 | Code not in KV (expired or already used) |
redirect_uri mismatch | 400 | Doesn’t match the original authorize request |
project mismatch | 400 | Code was issued for a different project |
PKCE verification failed | 400 | Code verifier doesn’t match challenge |
Unsupported grant_type | 400 | Neither authorization_code nor refresh_token |