Skip to content

Token Exchange

POST https://auth.beshoy.ai/oauth/token
Content-Type: application/json

Exchange a one-time authorization code for tokens.

FieldTypeRequiredDescription
grant_typestringYes"authorization_code"
codestringYesAuthorization code from callback
code_verifierstringYesOriginal PKCE code verifier
client_idstringYesProject ID
client_secretstringYesProject client secret
redirect_uristringYesMust match the authorize request
Terminal window
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"
}'
{
"access_token": "eyJhbGciOiJIUzI1NiJ9...",
"refresh_token": "a1b2c3d4e5f6...",
"token_type": "Bearer",
"expires_in": 300
}

The auth service performs these checks in order:

  1. Rate limit — 20 requests per 60 seconds per client_id
  2. Project lookupclient_id must exist
  3. Client secret — Verified via Argon2 against stored hash
  4. Code lookup — Retrieved from KV (pkce:{code})
  5. Code consumed — Deleted from KV immediately (one-time use)
  6. Redirect URI match — Must match what was stored with the code
  7. Project match — Code’s project must equal client_id
  8. PKCE verificationBASE64URL(SHA256(code_verifier)) === stored_challenge
ErrorStatusCause
Missing client_id400No client_id in request body
Rate limited429Too many requests for this client
Invalid client_id400Project not found
Invalid client_secret401Secret doesn’t match
Missing required fields400Missing code, code_verifier, or redirect_uri
Invalid or expired code400Code not in KV (expired or already used)
redirect_uri mismatch400Doesn’t match the original authorize request
project mismatch400Code was issued for a different project
PKCE verification failed400Code verifier doesn’t match challenge
Unsupported grant_type400Neither authorization_code nor refresh_token