Before you start
- A connected LinkedIn account to run on, or the managed path to run on our infrastructure.
- The
can_act_linkedin_searchespermission on your API key, agent or user. - A Sales Navigator seat on the executing account for the Sales Navigator methods.
A search is a discovery list, not an enriched one. Rows carry an identity (member id, vanity slug, name, avatar) and
nothing else: no headline, no title, no company, no location, no connection degree. Feed the ids into
enrichment for those, a separate surface.
result.data_request) and one activity-log entry on the executing
account. Nothing is stored, so nothing can be deduplicated away: rows arrive in LinkedIn’s own order, with no sort axis
and no cross-page deduplication. Past roughly five pages, or for anything on a schedule, use an
auto scrape.
The engines
Every method is aPOST under https://app.gtm-api.com/linkedin/v4/api/linkedin-scraping/.
The same surface also searches posts, jobs, events, groups, courses, products and schools, and reads a post’s commenters,
reactors and resharers. Same rules, and their filter vocabularies are in the API reference.
Two ways to address a search
The five list searches take either a pastedurl or a structured filters object. Same route, same parser,
same price: the structured half composes the same LinkedIn URL and runs the same page. Send filters when a program or
an agent composes the search, url when a human built it in LinkedIn or the vocabulary cannot express it.
Which half you sent only changes the label in the ledger: input_kind is url or params, and Data Requests shows
“Search people by URL” or “Search people by params” accordingly.
A pasted URL is prefix-checked first: a URL from another LinkedIn screen would scrape the wrong thing. On
https://www.linkedin.com, the regular engines take /search/results/people/, /search/results/companies/ and
/search/results/services/; the Sales Navigator pair takes /sales/search/people and /sales/search/company, no
trailing slash, checked literally. Your page overrides the page or offset baked into the URL; every other query
parameter rides through untouched.
curl
linkedin_account_sid the service picks one of your connected accounts with remaining budget; swap
filters for a pasted "url", or pin "linkedin_account_sid": "ln_ac_YOUR_ACCOUNT" to run the same page on a
specific account.
Filters
Every member, type and bound, engine by engine, is in the API reference. What cuts across all five engines:Three filters that do not mean what they look like
Types and bounds are in the reference; these are the semantics behind them, and getting one wrong costs a run rather than an error.Resolving the ids that filters need
Two typeahead endpoints turn a human term into an id, over separate id spaces: an id from one never works on the other.A lookup types into the account’s already-open tab, so it loads no page, spends no LinkedIn search and leaves the
commercial-use limit untouched. It still takes one slot of the daily
scraping budget, and is never
cached. Empty rows means the dropdown offered nothing for that term: an answer, not an error, ledger row completed.What comes back
Rows sit atresult.rows, paging at result.paging, and the ledger row at result.data_request.
Those last five are
null by construction: the result page does not carry them on either engine. Service-provider rows
use the same shape, with no ratings, review counts or service labels. When you need more, take ln_id or nickname
from a people-search row, or sn_id from a Sales Navigator row, into enrichment (as profile_id / public_identifier)
or into check-degree. ln_member_id is a join key only: it cannot be turned back into a signed URN, so no method
accepts it.
To resolve a Sales Navigator company into a vanity slug, run
company-public-identifier
(POST /api/linkedin-enrichment/company-public-identifier).
search-people carries an extra boolean, commercial_use_limit_hit: true means LinkedIn’s monthly commercial-use
paywall fired and rows may be truncated. The field is absent on the company engines, always false on service providers.
Which account runs a search
There is no silent fallback for a pinned account: out of budget it returns
429 rather than quietly moving to
another account. With no account connected at all, the call is a 422 telling you to connect one first.
Daily budget
Every method on this surface, searches and typeaheads alike, spends one shared per-account bucket calledscraping.
The free plan clamps the bucket to 15 scraping calls per day per account, and a young
account ramps lower still while it warms up. Read the bucket
before a batch rather than discovering it through a 429: smart limits and warmup.
A search is synchronous and bounded at 120 seconds. Past that the run is abandoned and the row goes to
failed. The typeaheads run on a 90 second ceiling.Zero rows is usually not a failure
Every case below returns HTTP 200 with"rows": []. Stop as soon as one explains what you see.
- Check
commercial_use_limit_hit.trueonsearch-peoplemeans LinkedIn truncated the page behind its monthly paywall. Re-run onsearch-sales-nav-people, which has no such limit, or wait for LinkedIn’s monthly reset. - Count what the page could show. The regular people parser keeps only cards carrying a vanity slug, an id and a
real name, and drops any card named “LinkedIn Member”, so out-of-network people that LinkedIn renders anonymously
never reach
rows. The Sales Navigator parser has no such filter and often returns more for the same query. - Check the executor’s session. An account whose LinkedIn session is degraded or sitting behind an auth wall returns a correctly shaped page with zero rows. Confirm the account and its browser are healthy.
- Re-read your filter names. A misspelled member is dropped silently, so the search ran less constrained than you meant.
- Check the page number. A page past the end of the results has nothing to return. A page above 100 is not this
case: it is a
422before anything runs. - Read the ledger row.
status: "completed"with zero rows means LinkedIn genuinely answered with nothing;status: "failed"with anerror_codemeans the response could not be read. Never the same thing.
Errors
The detail sits in one of two places, never both: a 422 carries it inerror.field_errors, everything else in
error.context.
The Sales Navigator refusal spells out its own fix: use an account that has a seat, or run
get-my-sales-navigator to
refresh that account’s premium status first. That check also runs on its own every 360 minutes (6 hours), so a seat
bought minutes ago usually needs the manual refresh.