{"record":{"id":"018bda6b7159be2e","repo":"jackwener/OpenCLI","slug":"sales-navigator-lead-search-api-returned-malformed-018bda","errorCode":null,"errorMessage":"Sales Navigator lead search API returned malformed lead row","messagePattern":"Sales Navigator lead search API returned malformed lead row","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/linkedin/salesnav-search.js","lineNumber":83,"sourceCode":"function profileUrlFromEntityUrn(entityUrn) {\n  const match = String(entityUrn || '').match(/fs_salesProfile:\\(([^,)]+)/);\n  return match && match[1] ? 'https://www.linkedin.com/in/' + match[1] : '';\n}\n\nfunction leadUrlFromEntityUrn(entityUrn) {\n  const match = String(entityUrn || '').match(/^urn:li:fs_salesProfile:\\(([^,()]+),([^,()]+),([^,()]+)\\)$/);\n  if (!match) return '';\n  return `https://www.linkedin.com/sales/lead/${encodeURIComponent(match[1])},${encodeURIComponent(match[2])},${encodeURIComponent(match[3])}`;\n}\n\nfunction parseLeads(json) {\n  if (!json || typeof json !== 'object' || !Array.isArray(json.elements)) {\n    throw new CommandExecutionError('Sales Navigator lead search API returned malformed payload');\n  }\n  const leads = [];\n  for (const el of json.elements) {\n    if (!el || typeof el !== 'object') {\n      throw new CommandExecutionError('Sales Navigator lead search API returned malformed lead row');\n    }\n    const current = Array.isArray(el.currentPositions) ? el.currentPositions : [];\n    const past = Array.isArray(el.pastPositions) ? el.pastPositions : [];\n    const pos = current[0] || past[0] || {};\n    const name = normalizeWhitespace(el.fullName || [el.firstName, el.lastName].filter(Boolean).join(' '));\n    if (!name) {\n      throw new CommandExecutionError('Sales Navigator lead row missing name');\n    }\n    const entityUrn = normalizeWhitespace(el.entityUrn || '');\n    if (!profileUrlFromEntityUrn(entityUrn)) {\n      throw new CommandExecutionError('Sales Navigator lead row missing profile identity');\n    }\n    leads.push({\n      name,\n      title: normalizeWhitespace(pos.title || ''),\n      company: normalizeWhitespace(pos.companyName || ''),\n      location: normalizeWhitespace(el.geoRegion || ''),\n      degree: normalizeWhitespace(el.degree || ''),","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/linkedin/salesnav-search.js#L65-L101","documentation":"parseLeads validates each entry of the Sales Navigator lead search response's json.elements array and throws CommandExecutionError when an element is null or not an object. This indicates the API returned a payload whose shape deviates from the expected lead-row schema (each element should be an object with fields like fullName, entityUrn, currentPositions). The library throws eagerly rather than emitting garbage rows.","triggerScenarios":"The cli 'salesnav-search' command runs, requireLeadSearchResult returns the parsed JSON, and at least one element of json.elements is null, undefined, or a non-object (string/number) while iterating in parseLeads.","commonSituations":"LinkedIn redeployed Sales Navigator and changed the decorationId (com.linkedin.sales.deco.desktop.searchv2.LeadSearchResult-14), causing mixed/unexpected payload shapes; the response includes null placeholders in elements; LinkedIn A/B testing a new search result schema; an authenticated-but-degraded session returning partial payloads.","solutions":["Re-run the search after confirming a signed-in Sales Navigator session; transient bad payloads often clear on retry.","Check the raw response shape from https://www.linkedin.com/sales-api/salesApiLeadSearch in the browser devtools and compare elements[] against the code's expectations.","Update LEAD_SEARCH_DECORATION (line 11) from a live /sales/search/people request if LinkedIn bumped the decoration version.","Update the library/CLI to the latest version in case upstream already handles the new schema.","Report/persist the raw payload for debugging before failing, e.g. log JSON.stringify(json.elements) in a patched copy."],"exampleFix":"// before\nconst pageLeads = parseLeads(json);\n// after\nconst pageLeads = parseLeads(json).filter(Boolean); // plus upstream: skip non-object rows instead of throwing\n// or defensively in caller:\ntry {\n  const pageLeads = parseLeads(json);\n} catch (e) {\n  console.error('malformed lead row; payload:', JSON.stringify(json).slice(0, 500));\n  throw e;\n}","handlingStrategy":"validation","validationCode":"function hasValidLeadRows(json) {\n  return !!json && typeof json === 'object' && Array.isArray(json.elements)\n    && json.elements.every((el) => el && typeof el === 'object');\n}","typeGuard":"function isLeadRow(el) {\n  return typeof el === 'object' && el !== null && 'entityUrn' in el;\n}","tryCatchPattern":"try {\n  const pageLeads = parseLeads(json);\n} catch (e) {\n  if (e.message.includes('malformed lead row')) {\n    // log raw payload for debugging, fall back to skipping this page\n    console.error('Bad lead row; payload:', JSON.stringify(json).slice(0, 500));\n  } else {\n    throw e;\n  }\n}","preventionTips":["Keep the CLI/library updated so decorationId and schema expectations match current LinkedIn responses.","Wrap each search page in try/catch and log the raw JSON before failing so future schema breaks are diagnosable.","Pin a recent version and check for updates whenever LinkedIn ships a Sales Navigator redeploy.","Validate the response shape once per run before the pagination loop."],"tags":["api-response","schema-validation","sales-navigator","parsing"],"backgroundTag":"api-response-schema-mismatch","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}