{"record":{"id":"1575e6b10ed4e0de","repo":"jackwener/OpenCLI","slug":"sales-navigator-lead-search-api-returned-malformed","errorCode":null,"errorMessage":"Sales Navigator lead search API returned malformed payload","messagePattern":"Sales Navigator lead search API returned malformed payload","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/linkedin/salesnav-search.js","lineNumber":78,"sourceCode":"}\n\n// Sales Navigator search returns no /in/ vanity URL, but the entityUrn carries\n// the obfuscated member token, and linkedin.com/in/<token> is a valid profile\n// URL that the connect command accepts.\nfunction 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({","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/linkedin/salesnav-search.js#L60-L96","documentation":"parseLeads validates the JSON returned by the Sales Navigator lead search API: it must be an object with an elements array, and each element must be an object. Any deviation throws CommandExecutionError because search results cannot be parsed.","triggerScenarios":"The API returned null/empty JSON, an HTML login/consent page captured by the fetch script, a non-object envelope (e.g. a bare array or error body), or an elements entry that is null/not an object.","commonSituations":"Expired or missing LinkedIn session/CSRF so the endpoint returns an auth redirect body; Sales Navigator API response schema changed; rate limiting or empty-state responses with a different shape; transient network capture failure inside page.evaluate.","solutions":["Re-authenticate (refresh the LinkedIn/ Sales Navigator session and CSRF token) and retry — a login redirect body is the most common malformed payload.","Retry the search once; if the schema genuinely changed, inspect the raw JSON from the fetch result and update parseLeads to the new shape.","Catch the error and surface the raw payload for diagnosis before reporting failure to the end user."],"exampleFix":"// before\nconst leads = parseLeads(result.json);\n// after\nlet leads;\ntry {\n  leads = parseLeads(result.json);\n} catch (e) {\n  console.error('Raw payload:', JSON.stringify(result.json).slice(0, 500));\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"function looksLikeLeadSearchPayload(json) {\n  return !!json && typeof json === 'object' && Array.isArray(json.elements);\n}","typeGuard":"function isLeadSearchPayload(json) {\n  return typeof json === 'object' && json !== null && Array.isArray(json.elements) &&\n    json.elements.every((el) => el && typeof el === 'object');\n}","tryCatchPattern":"try {\n  leads = parseLeads(result.json);\n} catch (e) {\n  if (typeof result.raw === 'string' && /login|sign in|authwall/i.test(result.raw)) {\n    await reauthenticate();\n    return retrySearch();\n  }\n  throw e;\n}","preventionTips":["Verify session/CSRF validity before long search jobs","Log the raw response payload when parsing fails","Pin/monitor LinkedIn API response shape in CI with a recorded fixture","Retry once on transient failures before surfacing the error"],"tags":["linkedin","sales-navigator","malformed-response","api-response","parsing"],"backgroundTag":"api-response-schema-mismatch","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}