# Madnion LinkedIn V2 search and Sales Navigator imports

Release: 22 October 2026. Native V2 only; no V1 fallback. Search is read-only but
uses governed read/import capacity. It never sends messages or starts campaigns.

## Request

`POST https://api.madnion.com/functions/v1/custom-proxy`

Backend header: `x-service-account-key: <existing Madnion app service key>`
with `integrations` scope and explicit account/customer read authorization.
Alternatively use the account owner's Madnion SSO token as
`Authorization: Bearer <token>`. Send `Content-Type: application/json`.
Never expose a service key in browser code.

```json
{
  "slug": "madnion-linkedin",
  "action": "leads.importSalesNavigatorSearch",
  "app_id": "app_HNTaxkT8g6pU1jJ8RFaOQIw1",
  "app_workspace_id": "044uma",
  "account_id": "acc_01m5hsjksresp9zajd6pw3nfdh",
  "key_name": "UnipileV2",
  "query": {"limit": 1, "offset": 0},
  "data": {"url": "https://www.linkedin.com/sales/search/people?query=YOUR_ENCODED_FILTERS"}
}
```

The alias selects Sales Navigator and the import lane. Default page size is 25;
maximum 100. Invalid explicit sizes are rejected. Offset defaults to zero upstream.
The requested page size is the governor's import cost, not proof of returned rows.

Imports have a separate **2,000 requested-lead units per canonical LinkedIn
account per UTC day**. All apps/customer workspaces using that same account share
this allowance. It does not debit invitation, message, InMail, publishing or
ordinary profile/read daily allowances. Each page still counts as one request
against shared minute-rate/pacing protection, and real provider restrictions and
account cooldowns still apply. Failed/uncertain admitted pages retain their
reservation; this is not a guarantee of 2,000 unique successful profiles. Reusing
flow IDs is attribution, not request idempotency. Reset is 00:00 UTC.

Pagination goes in top-level **`query`**, never `params` (path IDs) or the upstream
POST body. Older `data.limit`/`data.offset` inputs are moved to query; conflicting
copies are rejected. V1 `data.api`/`data.category` are not V2 fields.

For plain `search`, include top-level `product: "sales-navigator"` and
`search_type: "url"`. Omitting product selects Classic, which cannot parse Sales
Navigator URLs. The import alias needs no extra selectors.

## Next page

Keep account, workspace, action and URL identical. Set `query.offset` to the prior
offset plus the number of returned items. After one result at zero, use
`{"limit":1,"offset":1}`. The native result is under `data`, usually rows at
`data.data` and total at `data.total_count` when provided. Stop on an empty page
or exhausted total. Stop and investigate repeated IDs; do not loop on errors.
Sales Navigator uses offsets, not V1 cursors. Preserve filters and deduplicate
by provider identity. A request retrieves one page; it does not schedule a crawl.

## LEAD_LIST filters versus saved-list links

- `https://www.linkedin.com/sales/search/people?...` with a `LEAD_LIST` filter is
  sent unchanged to Sales Navigator URL search. Keep the complete encoded query.
- `https://www.linkedin.com/sales/lists/people/LEAD_123` is routed to the dedicated
  saved-lead-list browse endpoint, not Classic search. Numeric list IDs in that
  URL become `LEAD_<number>`. The same query paging applies. No list is modified.
- Other product/list URL patterns are rejected, not guessed. The list must be
  accessible under this account's active Sales Navigator contract. A URL is not
  authorization to access another user's list.

Richard's recovered ID retains his identity/history. Do not use his deleted V1
or old V2 connection. Other accounts may require a Sales Navigator session or
contract; an app grant alone does not prove product eligibility.

## Errors

400: invalid product/URL/paging. 403: missing grant, owner membership or scoped
account access. 404: missing account or inaccessible resource; inspect the error.
429: governor/provider restriction; respect Retry-After. 503/504: temporary lookup
or upstream failure. Never bypass refusals through V1 or another account.
Imports require the existing `search` grant, not campaign-write approval.

Classic people search is separate: use `query.cursor` with `next_cursor`, not
Sales Navigator limit/offset paging.
