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

  1. Read result.error (the CommandExecutionError's detail) to see the exact cause: 'HTTP 429' → back off and retry with longer waits between pages.
  2. If HTTP 429, increase the page.wait() delay (line 164) or reduce --limit so fewer paginated requests run; resume later.
  3. 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.
  4. 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.
  5. 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

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


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/83e2bf101c1cf966. Report an issue: GitHub.