{"record":{"id":"83e2bf101c1cf966","repo":"jackwener/OpenCLI","slug":"sales-navigator-lead-search-api-returned-an-unexpe","errorCode":null,"errorMessage":"Sales Navigator lead search API returned an unexpected response","messagePattern":"Sales Navigator lead search API returned an unexpected response","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/linkedin/salesnav-search.js","lineNumber":115,"sourceCode":"      name,\n      title: normalizeWhitespace(pos.title || ''),\n      company: normalizeWhitespace(pos.companyName || ''),\n      location: normalizeWhitespace(el.geoRegion || ''),\n      degree: normalizeWhitespace(el.degree || ''),\n      profile_url: profileUrlFromEntityUrn(entityUrn),\n      lead_url: leadUrlFromEntityUrn(entityUrn),\n      recipient_urn: entityUrn,\n    });\n  }\n  return leads;\n}\n\nfunction requireLeadSearchResult(result) {\n  if (result?.authRequired) {\n    throw new AuthRequiredError(LINKEDIN_DOMAIN, 'LinkedIn Sales Navigator API auth failed (HTTP ' + (result.status || '') + '). Confirm the account has Sales Navigator access.');\n  }\n  if (result?.error) {\n    throw new CommandExecutionError('Sales Navigator lead search API returned an unexpected response', result.error);\n  }\n  if (!result || !result.json) {\n    throw new CommandExecutionError('Sales Navigator lead search API returned an unexpected response', 'no_json');\n  }\n  return result.json;\n}\n\ncli({\n  site: 'linkedin',\n  name: 'salesnav-search',\n  access: 'read',\n  description: 'Search LinkedIn Sales Navigator for people leads by keyword',\n  domain: LINKEDIN_DOMAIN,\n  strategy: Strategy.UI,\n  browser: true,\n  args: [\n    { name: 'keywords', type: 'string', required: true, positional: true, help: 'People search keywords, e.g. \"quality manager food manufacturing\"' },\n    { name: 'limit', type: 'number', default: 25, help: 'Maximum leads to return (1-500, fetched 25 per request)' },","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/linkedin/salesnav-search.js#L97-L133","documentation":"requireLeadSearchResult throws CommandExecutionError('Sales Navigator lead search API returned an unexpected response', detail) in two cases: (1) the in-page fetch returned a non-2xx status other than 401/403 (result.error = 'HTTP <status>'), a network failure inside the page ('fetch failed: ...'), or (2) the result resolved without a json body (result.json missing, detail 'no_json'). It signals the search request failed for reasons other than authentication.","triggerScenarios":"The salesApiLeadSearch call returns e.g. HTTP 429 (rate limited) or 5xx; the page fetch throws (network drop, aborted navigation, CSP/DNS issue) producing 'fetch failed: ...'; or unwrapEvaluateResult returns a result object lacking json because the response body could not be parsed.","commonSituations":"Pagination loop hitting LinkedIn rate limits after several pages (HTTP 429); unstable network or browser tab navigation interrupting fetch; LinkedIn serving an HTML error page instead of JSON; server-side Sales Navigator outage; the decoration/query params being rejected with 400 after a schema change.","solutions":["Read result.error (the CommandExecutionError's detail) to see the exact cause: 'HTTP 429' → back off and retry with longer waits between pages.","If HTTP 429, increase the page.wait() delay (line 164) or reduce --limit so fewer paginated requests run; resume later.","If HTTP 4xx/5xx persists, verify the request URL manually in the browser (leadSearchUrl output) — a 400 usually means the decorationId or query format changed.","If 'fetch failed', check network connectivity and that the page is still on a www.linkedin.com URL (page.goto(SALES_HOME) at line 141); re-run the command.","Retry the whole search after a few minutes — most 5xx/429 conditions are transient."],"exampleFix":"// before\nconst json = requireLeadSearchResult(result);\n// after\nlet json;\ntry {\n  json = requireLeadSearchResult(result);\n} catch (e) {\n  if (/429/.test(e?.detail ?? '')) {\n    await page.wait(60); // back off on rate limit, then retry once\n    json = requireLeadSearchResult(unwrapEvaluateResult(await page.evaluate(fetchLeadSearchScript(url, csrf))));\n  } else {\n    throw e;\n  }\n}","handlingStrategy":"retry","validationCode":"// pre-flight: verify the endpoint is reachable and the session is healthy\nconst res = await fetch('https://www.linkedin.com/sales-api/salesApiLeadSearch?count=1',\n  { credentials: 'include', headers: { 'csrf-token': csrf, accept: 'application/json' } });\nif (!res.ok && res.status !== 401 && res.status !== 403) {\n  throw new Error('salesApiLeadSearch unhealthy: HTTP ' + res.status);\n}","typeGuard":"function hasJsonPayload(result) {\n  return !!result && typeof result === 'object' && result.json != null\n    && typeof result.json === 'object' && !result.authRequired && !result.error;\n}","tryCatchPattern":"try {\n  const json = requireLeadSearchResult(result);\n} catch (e) {\n  const detail = e?.detail ?? e?.message ?? '';\n  if (detail.includes('HTTP 429') || detail.includes('HTTP 5') || detail.includes('fetch failed') || detail === 'no_json') {\n    // transient: exponential backoff, e.g. retry after 30s, then 60s, max 3 attempts\n  } else {\n    throw e;\n  }\n}","preventionTips":["Add exponential backoff around paginated searches; HTTP 429 is the most common non-auth failure.","Cap --limit and pagination pages per run so long crawls don't trip rate limits.","Retry with fresh network state: re-goto(SALES_HOME) before re-issuing the fetch if 'fetch failed' occurs.","Log the error detail (second CommandExecutionError arg) — it carries the exact HTTP status or fetch failure cause.","Monitor LinkedIn status/outages before bulk searches; 5xx during incidents is not actionable."],"tags":["network","http-error","rate-limit","sales-navigator"],"backgroundTag":"unexpected-api-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}