jackwener/OpenCLI · error · CommandExecutionError
Sales Navigator lead search API returned an unexpected respo
Error message
Sales Navigator lead search API returned an unexpected response
What it means
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.
Source
Thrown at clis/linkedin/salesnav-search.js:115
name,
title: normalizeWhitespace(pos.title || ''),
company: normalizeWhitespace(pos.companyName || ''),
location: normalizeWhitespace(el.geoRegion || ''),
degree: normalizeWhitespace(el.degree || ''),
profile_url: profileUrlFromEntityUrn(entityUrn),
lead_url: leadUrlFromEntityUrn(entityUrn),
recipient_urn: entityUrn,
});
}
return leads;
}
function requireLeadSearchResult(result) {
if (result?.authRequired) {
throw new AuthRequiredError(LINKEDIN_DOMAIN, 'LinkedIn Sales Navigator API auth failed (HTTP ' + (result.status || '') + '). Confirm the account has Sales Navigator access.');
}
if (result?.error) {
throw new CommandExecutionError('Sales Navigator lead search API returned an unexpected response', result.error);
}
if (!result || !result.json) {
throw new CommandExecutionError('Sales Navigator lead search API returned an unexpected response', 'no_json');
}
return result.json;
}
cli({
site: 'linkedin',
name: 'salesnav-search',
access: 'read',
description: 'Search LinkedIn Sales Navigator for people leads by keyword',
domain: LINKEDIN_DOMAIN,
strategy: Strategy.UI,
browser: true,
args: [
{ name: 'keywords', type: 'string', required: true, positional: true, help: 'People search keywords, e.g. "quality manager food manufacturing"' },
{ name: 'limit', type: 'number', default: 25, help: 'Maximum leads to return (1-500, fetched 25 per request)' },View on GitHub (pinned to 49907e53dc)
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.
Example fix
// before
const json = requireLeadSearchResult(result);
// after
let json;
try {
json = requireLeadSearchResult(result);
} catch (e) {
if (/429/.test(e?.detail ?? '')) {
await page.wait(60); // back off on rate limit, then retry once
json = requireLeadSearchResult(unwrapEvaluateResult(await page.evaluate(fetchLeadSearchScript(url, csrf))));
} else {
throw e;
}
} Defensive patterns
Strategy: retry
Validate before calling
// pre-flight: verify the endpoint is reachable and the session is healthy
const res = await fetch('https://www.linkedin.com/sales-api/salesApiLeadSearch?count=1',
{ credentials: 'include', headers: { 'csrf-token': csrf, accept: 'application/json' } });
if (!res.ok && res.status !== 401 && res.status !== 403) {
throw new Error('salesApiLeadSearch unhealthy: HTTP ' + res.status);
} Type guard
function hasJsonPayload(result) {
return !!result && typeof result === 'object' && result.json != null
&& typeof result.json === 'object' && !result.authRequired && !result.error;
} Try / catch
try {
const json = requireLeadSearchResult(result);
} catch (e) {
const detail = e?.detail ?? e?.message ?? '';
if (detail.includes('HTTP 429') || detail.includes('HTTP 5') || detail.includes('fetch failed') || detail === 'no_json') {
// transient: exponential backoff, e.g. retry after 30s, then 60s, max 3 attempts
} else {
throw e;
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Flomo API returned HTTP ${resp.status}
- ${label} failed: HTTP ${response.status}
- lobsters domain returned HTTP ${resp.status}
- HTTP ${probe.httpStatus} from nowcoder profile API
- HTTP ${result.httpStatus} from ${result.where}
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/83e2bf101c1cf966.
Report an issue: GitHub.