jackwener/OpenCLI · error · CommandExecutionError
Sales Navigator lead search API returned malformed lead row
Error message
Sales Navigator lead search API returned malformed lead row
What it means
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.
Source
Thrown at clis/linkedin/salesnav-search.js:83
function profileUrlFromEntityUrn(entityUrn) {
const match = String(entityUrn || '').match(/fs_salesProfile:\(([^,)]+)/);
return match && match[1] ? 'https://www.linkedin.com/in/' + match[1] : '';
}
function leadUrlFromEntityUrn(entityUrn) {
const match = String(entityUrn || '').match(/^urn:li:fs_salesProfile:\(([^,()]+),([^,()]+),([^,()]+)\)$/);
if (!match) return '';
return `https://www.linkedin.com/sales/lead/${encodeURIComponent(match[1])},${encodeURIComponent(match[2])},${encodeURIComponent(match[3])}`;
}
function parseLeads(json) {
if (!json || typeof json !== 'object' || !Array.isArray(json.elements)) {
throw new CommandExecutionError('Sales Navigator lead search API returned malformed payload');
}
const leads = [];
for (const el of json.elements) {
if (!el || typeof el !== 'object') {
throw new CommandExecutionError('Sales Navigator lead search API returned malformed lead row');
}
const current = Array.isArray(el.currentPositions) ? el.currentPositions : [];
const past = Array.isArray(el.pastPositions) ? el.pastPositions : [];
const pos = current[0] || past[0] || {};
const name = normalizeWhitespace(el.fullName || [el.firstName, el.lastName].filter(Boolean).join(' '));
if (!name) {
throw new CommandExecutionError('Sales Navigator lead row missing name');
}
const entityUrn = normalizeWhitespace(el.entityUrn || '');
if (!profileUrlFromEntityUrn(entityUrn)) {
throw new CommandExecutionError('Sales Navigator lead row missing profile identity');
}
leads.push({
name,
title: normalizeWhitespace(pos.title || ''),
company: normalizeWhitespace(pos.companyName || ''),
location: normalizeWhitespace(el.geoRegion || ''),
degree: normalizeWhitespace(el.degree || ''),View on GitHub (pinned to 49907e53dc)
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.
Example fix
// before
const pageLeads = parseLeads(json);
// after
const pageLeads = parseLeads(json).filter(Boolean); // plus upstream: skip non-object rows instead of throwing
// or defensively in caller:
try {
const pageLeads = parseLeads(json);
} catch (e) {
console.error('malformed lead row; payload:', JSON.stringify(json).slice(0, 500));
throw e;
} Defensive patterns
Strategy: validation
Validate before calling
function hasValidLeadRows(json) {
return !!json && typeof json === 'object' && Array.isArray(json.elements)
&& json.elements.every((el) => el && typeof el === 'object');
} Type guard
function isLeadRow(el) {
return typeof el === 'object' && el !== null && 'entityUrn' in el;
} Try / catch
try {
const pageLeads = parseLeads(json);
} catch (e) {
if (e.message.includes('malformed lead row')) {
// log raw payload for debugging, fall back to skipping this page
console.error('Bad lead row; payload:', JSON.stringify(json).slice(0, 500));
} else {
throw e;
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Sales Navigator lead search API returned malformed payload
- Sales Navigator lead row missing name
- Sales Navigator lead row missing profile identity
- Sales Navigator messaging thread API returned malformed payl
- Sales Navigator messaging thread API returned a thread witho
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/018bda6b7159be2e.
Report an issue: GitHub.