{"record":{"id":"87d8e184dce5d4ff","repo":"decolua/9router","slug":"missing-access-token-accesstoken","errorCode":null,"errorMessage":"Missing access_token / accessToken","messagePattern":"Missing access_token / accessToken","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/app/api/oauth/grok-cli/bulk-import/route.js","lineNumber":67,"sourceCode":"\n  const results = [];\n  let success = 0;\n  let failed = 0;\n\n  for (let i = 0; i < accounts.length; i++) {\n    const raw = accounts[i];\n    try {\n      if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n        throw new Error(\"Item is not an object\");\n      }\n\n      const accessToken = raw.access_token || raw.accessToken;\n      const refreshToken = raw.refresh_token || raw.refreshToken || null;\n      const idToken = raw.id_token || raw.idToken || null;\n      let email = raw.email || null;\n\n      if (!accessToken || typeof accessToken !== \"string\") {\n        throw new Error(\"Missing access_token / accessToken\");\n      }\n\n      if (!email) {\n        email =\n          decodeXaiIdTokenEmail(idToken) ||\n          extractEmailFromAccessToken(accessToken) ||\n          null;\n      }\n\n      let expiresAt = raw.expires_at || raw.expiresAt || null;\n      const expiresIn = raw.expires_in || raw.expiresIn;\n      if (!expiresAt && typeof expiresIn === \"number\" && expiresIn > 0) {\n        expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString();\n      }\n\n      const psd = {\n        authMethod: \"device_code\",\n        ...(idToken ? { idToken } : {}),","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/app/api/oauth/grok-cli/bulk-import/route.js#L49-L85","documentation":"For each grok-cli import item the route resolves accessToken from access_token or accessToken (snake_case preferred). If neither is present as a non-empty string, it throws 'Missing access_token / accessToken'. Unlike codex import, both key spellings are accepted here.","triggerScenarios":"An accounts[] item whose only token field is named something else (e.g. token, key, apiKey), or where access_token is null/empty/non-string.","commonSituations":"Export from a provider that names the field token or api_key, a redacted export with access_token: \"\", or pasting only refresh_token / id_token entries.","solutions":["Rename the token field to access_token or accessToken in each item.","Check the value is a non-empty string, not null or \"\".","Re-export from grok-cli (its auth file stores access_token) including the token field."],"exampleFix":"// before\n{ \"accounts\": [{ \"token\": \"abc\", \"refresh_token\": \"r1\" }] }\n// after\n{ \"accounts\": [{ \"access_token\": \"abc\", \"refresh_token\": \"r1\" }] }","handlingStrategy":"validation","validationCode":"for (const a of accounts) {\n  const tok = a?.access_token ?? a?.accessToken;\n  if (!tok || typeof tok !== \"string\") {\n    throw new Error(\"item missing access_token/accessToken\");\n  }\n}","typeGuard":"const hasGrokToken = (x) => {\n  const t = x?.access_token ?? x?.accessToken;\n  return typeof t === \"string\" && t.length > 0;\n};","tryCatchPattern":"try {\n  await grokBulkImport(accounts);\n} catch (e) {\n  if (e.message.includes(\"Missing access_token\")) {\n    console.error(\"Rename token fields to access_token and resend\");\n  } else throw e;\n}","preventionTips":["Name the token field access_token or accessToken — not token/key/apiKey","Ensure the value is a non-empty string (not null or \"\")","Export directly from grok-cli's auth file which uses access_token"],"tags":["validation","bulk-import","grok","oauth"],"backgroundTag":"missing-access-token","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}