---
title: "Start Discovery"
method: POST
path: "/api/v1/leads/discover"
tags: ["leads"]
---

# Start Discovery

`POST /api/v1/leads/discover`

## Request

- Base URL: `https://api.nudgelink.co` (the document declares no server; this is the origin it was published from)
- URL: `https://api.nudgelink.co/api/v1/leads/discover`
- Auth: HTTP bearer

Kick off a discovery run. Synchronous pre-flight checks + Celery
dispatch. Returns 402 on insufficient credits, 409 if a run is already
in flight, 400 if pre-conditions aren't met (no ICP / no LinkedIn account).

## Request body

- DiscoverRequest
  - `target_limit` integer
  - `score_threshold` integer
  - `extra_keywords` string[], nullable
  - `deep_search` boolean
  - `filters` SearchFilters: Structured LinkedIn people-search filters (Discovery v2, basket 1). Every facet field carries LinkedIn *IDs* (not free text), resolved client-side via the typeahead endpoint (``GET /api/v1/leads/discover/search-parameters``), which proxies Unipile's ``/linkedin/search/parameters``. Sending the wrong shape (free text where an ID is expected) is exactly what made classic search return nothing before, so the model only carries IDs and the adapter drops anything it can't map. Form-supplied filters take priority over the ICP (the ICP only fills a facet the form left blank — see ``lead_finder._assemble_structured_filters``). This model is search-time facets only. Free-text "work keywords" + AND/OR and the company-size buckets are post-fetch SOFT signals and live as separate ``DiscoverRequest`` fields, not here (ТЗ v2 §B6/§B4). ``network_distance`` is deliberately absent: discovery is pinned to 2nd + 3rd degree (1st-degree is excluded — those are usually already in conversation — while 3rd-degree strangers are the target of cold outreach). ``to_filter_dict`` injects ``[2, 3]`` unconditionally so the rule is enforced server-side regardless of the client. Safe even if LinkedIn rejects degree 3 on a free seat: classic search then degrades to keywords-only (see ``unipile.search_profiles``). Confirm free-seat acceptance with ``backend/scripts/discovery_v2_spike.py`` (§E.2).
    - `location` string[]
    - `company` string[]
    - `past_company` string[]
    - `school` string[]
    - `industry` string[]
    - `skills` string[]
  - `work_keywords` string[]
  - `work_keywords_mode` 'and' | 'or'
  - `company_sizes` string[]
  - `filter_labels` object
  - `campaign_id` string, uuid, nullable

## Response `200`

Successful Response

- DiscoverResponse
  - `run_id` string, uuid, required
  - `status` string, required
  - `target_limit` integer, required
  - `score_threshold` integer, required
  - `estimated_credits` integer, required

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/nudgelink/apis/nudgelink-api.md) · [All operations](https://skmtc.dev/nudgelink/apis/nudgelink-api/llms.txt) · [OpenAPI document](https://skmtc.dev/nudgelink/apis/nudgelink-api/revisions/baa46e6db986?raw)
