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

  1. Read the embedded body — invalid_grant means restart the whole flow with a fresh authorize URL
  2. Never retry the same code: authorization codes are single-use; start a new OAuth round on failure
  3. Ensure the exact same redirect_uri is used in buildAuthUrl and exchangeToken
  4. Keep the PKCE code_verifier from flow start through to the exchange (no restarts in between)
  5. 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

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


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/ae13ed451b8ca318. Report an issue: GitHub.