{"record":{"id":"b50084386acaa407","repo":"decolua/9router","slug":"xai-token-exchange-failed-err","errorCode":null,"errorMessage":"xAI token exchange failed: ${err}","messagePattern":"xAI token exchange failed: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/lib/oauth/services/xai.js","lineNumber":147,"sourceCode":"  async exchangeXaiCode({ tokenUrl, code, redirectUri, codeVerifier }) {\n    const res = await fetch(tokenUrl, {\n      method: \"POST\",\n      headers: {\n        \"Content-Type\": \"application/x-www-form-urlencoded\",\n        Accept: \"application/json\",\n      },\n      body: new URLSearchParams({\n        grant_type: \"authorization_code\",\n        client_id: XAI_CONFIG.clientId,\n        code,\n        redirect_uri: redirectUri,\n        code_verifier: codeVerifier,\n      }),\n    });\n\n    if (!res.ok) {\n      const err = await res.text();\n      throw new Error(`xAI token exchange failed: ${err}`);\n    }\n    return await res.json();\n  }\n\n  /**\n   * Refresh an access token using a refresh_token.\n   */\n  async refreshAccessToken(refreshToken) {\n    const { tokenUrl } = await discoverEndpoints();\n    const res = await fetch(tokenUrl, {\n      method: \"POST\",\n      headers: {\n        \"Content-Type\": \"application/x-www-form-urlencoded\",\n        Accept: \"application/json\",\n      },\n      body: new URLSearchParams({\n        grant_type: \"refresh_token\",\n        client_id: XAI_CONFIG.clientId,","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/oauth/services/xai.js#L129-L165","documentation":"exchangeXaiCode swaps an OAuth authorization code (with PKCE verifier) for tokens at xAI's token endpoint. When the HTTP response is not ok, the raw response body text is read and thrown verbatim inside 'xAI token exchange failed: <body>'. The body usually contains xAI's OAuth error JSON (e.g. invalid_grant, invalid_client).","triggerScenarios":"POST to the xAI token endpoint returns 400/401/etc. — expired or already-redeemed authorization code, wrong code_verifier (PKCE mismatch), invalid client_id, or revoked/blocked client.","commonSituations":"User taking too long between authorize and exchange (code TTL expiry); exchanging the same code twice after a retry; mismatched redirect_uri or client credentials between the authorize request and the exchange.","solutions":["Parse the thrown body text — it typically contains 'error' (e.g. invalid_grant) pinpointing the cause.","For invalid_grant, restart the OAuth flow: authorization codes are single-use and short-lived.","Verify client_id, redirect_uri, and the code_verifier match exactly what was used in the authorize step.","If the exchange failed after the code was consumed, do not blindly retry — request a fresh code."],"exampleFix":"// before\nconst tokens = await exchangeXaiCode(code, verifier); // throws on 400\n// after\ntry {\n  const tokens = await exchangeXaiCode(code, verifier);\n} catch (e) {\n  if (e.message.includes('invalid_grant')) return startNewAuthorization();\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"if (typeof code !== 'string' || !code || typeof codeVerifier !== 'string' || !codeVerifier) {\n  throw new Error('cannot exchange: missing code or verifier');\n}","typeGuard":null,"tryCatchPattern":"try {\n  const tokens = await exchangeXaiCode(code, codeVerifier);\n} catch (e) {\n  if (!e.message.startsWith('xAI token exchange failed:')) throw e;\n  const body = e.message.slice('xAI token exchange failed:'.length);\n  if (body.includes('invalid_grant')) return restartAuthorizationFlow(); // code expired/used\n  if (body.includes('invalid_client')) throw new Error('check XAI client_id/credentials');\n  throw e;\n}","preventionTips":["Exchange the code immediately after the redirect — authorization codes are short-lived and single-use.","Never retry an exchange that may have succeeded (a retried code always yields invalid_grant).","Keep client_id, redirect_uri, and verifier identical between authorize and exchange calls.","Parse the embedded upstream body to branch on the exact OAuth error code."],"tags":["oauth","xai","token-exchange","pkce","http"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}