decolua/9router · error · Error
xAI token exchange failed: ${error}
Error message
xAI token exchange failed: ${error} What it means
xAI's exchangeToken POSTs the authorization code to the discovered/static token endpoint with PKCE and throws this when the response is non-2xx, embedding the raw error body. Typical token-endpoint rejections are invalid_grant (code expired/used), invalid_client (bad client_id), or code_verifier/code_challenge mismatches.
Source
Thrown at src/lib/oauth/providers/xai.js:76
},
exchangeToken: async (config, code, redirectUri, codeVerifier) => {
const response = await fetch(config.tokenUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
},
body: new URLSearchParams({
grant_type: "authorization_code",
client_id: config.clientId,
code,
redirect_uri: redirectUri,
code_verifier: codeVerifier,
}),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`xAI token exchange failed: ${error}`);
}
return await response.json();
},
mapTokens: (tokens) => {
const mapped = {
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
expiresIn: tokens.expires_in,
scope: tokens.scope,
};
const email = decodeXaiIdTokenEmail(tokens.id_token);
if (email) mapped.email = email;
if (tokens.id_token) {
mapped.providerSpecificData = { idToken: tokens.id_token };
}
return mapped;
},
};View on GitHub (pinned to 90b52e06ff)
Solutions
- Read the embedded body — invalid_grant means restart the whole flow with a fresh authorize URL
- Never retry the same code: authorization codes are single-use; start a new OAuth round on failure
- Ensure the exact same redirect_uri is used in buildAuthUrl and exchangeToken
- Keep the PKCE code_verifier from flow start through to the exchange (no restarts in between)
- Clear cached endpoint discovery / restart so the current token_url is re-fetched
Example fix
// before: blind retry reuses a consumed code
let tokens;
try { tokens = await exchangeToken(cfg, code, redirectUri, verifier); }
catch { tokens = await exchangeToken(cfg, code, redirectUri, verifier); } // fails again: invalid_grant
// after: codes are single-use — restart the flow
try { tokens = await exchangeToken(cfg, code, redirectUri, verifier); }
catch (e) {
if (e.message.includes('xAI token exchange failed')) startNewOAuthFlow(); // fresh code + verifier
else throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
// pre-flight: same redirect_uri, non-empty code & verifier
if (!code || !codeVerifier) throw new Error('Missing code or PKCE verifier — restart the xAI OAuth flow');
if (redirectUriUsedInAuthUrl !== redirectUri) throw new Error('redirect_uri mismatch between authorize and token calls'); Type guard
const isTokenExchangeError = (e) => e instanceof Error && e.message.startsWith('xAI token exchange failed:');
const isInvalidGrant = (e) => isTokenExchangeError(e) && /invalid_grant/.test(e.message); Try / catch
try { tokens = await xai.exchangeToken(cfg, code, redirectUri, verifier); }
catch (e) {
if (isInvalidGrant(e)) startNewOAuthFlow(); // code is single-use — never retry it
else if (isTokenExchangeError(e)) logAndAlert(e.message); // bad client config or endpoint moved
else throw e;
} Prevention
- Never retry an authorization code — it is single-use; restart the flow instead
- Persist the PKCE verifier across the whole flow (no process restarts mid-exchange)
- Use the byte-identical redirect_uri in buildAuthUrl and exchangeToken
- Re-fetch endpoint discovery if xAI rotates endpoints; don't cache stale token URLs
When it happens
Trigger: Authorization code already exchanged or expired (single-use, short TTL); code_verifier does not match the code_challenge sent at authorize time; redirect_uri differs from the one used in buildAuthUrl; discovery fallback switched to a stale token URL; xAI endpoint returns 4xx/5xx.
Common situations: Retrying an exchange after a timeout (code already consumed); CLI restart losing the PKCE verifier between authorize and callback; redirect port busy so the callback URI changed; cached endpoint discovery pointing to a rotated token URL.
Related errors
- xAI token exchange failed: ${err}
- `Token exchange failed: ${error}`
- `Token exchange failed: ${error}`
- Token exchange failed: ${error}
- Token exchange failed: ${error}
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/ae13ed451b8ca318.
Report an issue: GitHub.