{"record":{"id":"d101279d4ab9079f","repo":"jackwener/OpenCLI","slug":"http-result-httpstatus-from-voyager-api-me","errorCode":null,"errorMessage":"HTTP ${result.httpStatus} from /voyager/api/me","messagePattern":"HTTP (.+?) from /voyager/api/me","errorType":"http","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/linkedin-learning/auth.js","lineNumber":43,"sourceCode":"      const d = await res.json();\n      const mini = d && d.miniProfile;\n      if (!mini || !mini.publicIdentifier) {\n        return { kind: 'auth', detail: 'LinkedIn /voyager/api/me 200 but miniProfile missing' };\n      }\n      const firstName = (mini.firstName && (mini.firstName.text || mini.firstName)) || '';\n      const lastName = (mini.lastName && (mini.lastName.text || mini.lastName)) || '';\n      return {\n        ok: true,\n        public_id: String(mini.publicIdentifier),\n        plain_id: String(d.plainId || ''),\n        name: String((firstName + ' ' + lastName).trim()),\n      };\n    } catch (e) {\n      return { kind: 'exception', detail: String(e && e.message || e) };\n    }\n  })()`);\n  if (result?.kind === 'auth') throw new AuthRequiredError('linkedin.com', result.detail);\n  if (result?.kind === 'http') throw new CommandExecutionError(`HTTP ${result.httpStatus} from /voyager/api/me`);\n  if (result?.kind === 'exception') throw new CommandExecutionError(`LinkedIn Learning whoami failed: ${result.detail}`);\n  if (!result?.ok) throw new CommandExecutionError(`Unexpected LinkedIn Learning probe: ${JSON.stringify(result)}`);\n  return { public_id: result.public_id, plain_id: result.plain_id, name: result.name };\n}\n\nregisterSiteAuthCommands({\n  site: 'linkedin-learning',\n  domain: 'linkedin.com',\n  loginUrl: 'https://www.linkedin.com/login?session_redirect=%2Flearning%2F',\n  columns: ['public_id', 'plain_id', 'name'],\n  quickCheck: hasLinkedinSessionCookie,\n  verify: verifyLinkedinLearningIdentity,\n  poll: async (page) => {\n    if (!await hasLinkedinSessionCookie(page)) {\n      throw new AuthRequiredError('linkedin.com', 'Waiting for LinkedIn li_at cookie');\n    }\n    return verifyLinkedinLearningIdentity(page);\n  },","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/linkedin-learning/auth.js#L25-L61","documentation":"When the in-page probe reaches /voyager/api/me but receives an HTTP error status, the probe returns { kind:'http', httpStatus } and the command rethrows it as CommandExecutionError with the status embedded. This means the request was made with the browser's credentials but LinkedIn's API rejected it at the HTTP level.","triggerScenarios":"page.evaluate fetch of /voyager/api/me resolves with an error httpStatus (commonly 403 Forbidden, sometimes 429 or 5xx), producing result.kind === 'http'.","commonSituations":"LinkedIn blocking Voyager API calls without a valid csrf-token header after an API change; rate limiting from rapid repeated probes; LinkedIn WAF/anti-bot blocking the automation browser; region or account restrictions on Learning API.","solutions":["Check httpStatus: 403 usually means missing/invalid csrf-token or anti-bot block; 429 means slow down and retry.","Ensure the probe sends the JSESSIONID-derived csrf-token header and referer headers exactly as a logged-in browser does.","Retry after a delay if 429/5xx.","Use a less automated-looking browser profile (real user-agent, headful mode) if LinkedIn is blocking the client."],"exampleFix":"// before\n// probe: fetch('/voyager/api/me') -> 403\n// after\n// probe: include csrf header\nconst csrf = (document.cookie.split('; ').find(c=>c.startsWith('JSESSIONID='))||'').split('=')[1] || '';\nawait fetch('/voyager/api/me', { headers: { 'csrf-token': decodeURIComponent(csrf), 'accept': 'application/json' } });","handlingStrategy":"retry","validationCode":"const csrf = (document.cookie.split('; ').find(c => c.startsWith('JSESSIONID=')) || '').split('=')[1] || '';\nif (!csrf) throw new Error('no JSESSIONID csrf token — Learning session missing');","typeGuard":"function isHttpKindResult(r) { return r?.kind === 'http' && Number.isInteger(r.httpStatus); }","tryCatchPattern":"try {\n  const identity = await verifyLinkedinLearningIdentity(page);\n} catch (e) {\n  const m = /HTTP (\\d{3}) from \\/voyager\\/api\\/me/.exec(e.message);\n  if (m) {\n    const status = Number(m[1]);\n    if (status === 429 || status >= 500) await sleep(30_000); // then retry\n    else if (status === 403) await refreshCsrfAndProfile(page); // csrf/anti-bot issue\n    else throw e;\n  } else throw e;\n}","preventionTips":["Always send the JSESSIONID-derived csrf-token header to Voyager endpoints.","Throttle repeated probes to avoid 429s.","Use a realistic user-agent and headful mode to dodge anti-bot blocks.","Map statuses to actions: 403 = auth/headers, 429 = backoff, 5xx = retry."],"tags":["http","linkedin","api"],"backgroundTag":"http-error-status","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}