> ## Documentation index
> Fetch the complete documentation index at: https://docs.leadmcp.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Find contacts

`POST /contacts/search`

Returns contact rows for a filter, paged by cursor. Each returned contact costs one contact credit. To deliver a list, start an export instead of paging search.

Returns contact rows for a filter, paged by cursor. Each returned contact costs one contact credit. To deliver a list, start an export instead of paging search.

Cost: Each returned contact costs one contact credit.

## Request body

- `filter` | Filter | required. The audience to read.
  - `countries` | array of Country | optional. Contact countries to include. Send ISO-2 codes such as us, or English names such as Germany.
  - `exclude_countries` | array of Country | optional. Contact countries to exclude. Send ISO-2 codes such as us, or English names such as Germany.
  - `has_linkedin` | boolean | optional. True keeps only contacts that carry a LinkedIn URL.
  - `has_work_email` | boolean | optional. True keeps only contacts that carry a work email.
  - `has_phone` | boolean | optional. True matches contacts with a mobile or direct phone. False matches contacts without either.
  - `include_secondary_roles` | boolean | optional. By default, filters match a person's main role. Set include_secondary_roles to true to also match their other current roles; the contact's top-level fields still show the main role. Default `false`.
  - `title_query` | string or array of string | optional. Words in the current title. The match is case-insensitive and uses whole words: 'CTO' matches 'Co-founder & CTO' but not 'Director'. The words also match across punctuation: 'VP Sales' also matches 'VP, Sales' and 'VP-Sales'. Send an array to match any one of several phrases, for example ["VP Sales", "Vice President of Sales"]. A phrase that is blank after trimming answers 400 request.invalid. A leading or trailing wildcard answers 422 filter.rejected. For a substring match, use `where` with the `contains` operator on `title`.
  - `functions` | array of JobFunction | optional. Job functions to include. Values: `operations`, `general_management`, `sales_and_bizdev`, `information_technology`, `education`, `healthcare_and_social_services`, `finance_and_accounting`, `manufacturing_and_production`, `engineering`, `customer_service`, `government_and_public_safety`, `marketing_and_advertising`, `human_resources`, `arts_and_creative`, `construction_and_trades`, `legal`, `supply_chain_and_logistics`, `procurement`, `writing_and_editorial`, `research_and_science`, `other`.
  - `exclude_functions` | array of JobFunction | optional. Job functions to exclude. Values: `operations`, `general_management`, `sales_and_bizdev`, `information_technology`, `education`, `healthcare_and_social_services`, `finance_and_accounting`, `manufacturing_and_production`, `engineering`, `customer_service`, `government_and_public_safety`, `marketing_and_advertising`, `human_resources`, `arts_and_creative`, `construction_and_trades`, `legal`, `supply_chain_and_logistics`, `procurement`, `writing_and_editorial`, `research_and_science`, `other`.
  - `seniority` | array of Seniority | optional. Seniority rungs to include. Values: `c_level`, `vp`, `director`, `manager`, `senior_ic`, `entry`, `founder_or_partner`, `unknown`.
  - `exclude_seniority` | array of Seniority | optional. Seniority rungs to exclude. Values: `c_level`, `vp`, `director`, `manager`, `senior_ic`, `entry`, `founder_or_partner`, `unknown`.
  - `domains` | array of string | optional. Current employer domains. A full URL reduces to a bare domain on input.
  - `exclude_domains` | array of string | optional. Current employer domains to exclude.
  - `company_names` | array of string | optional. Current employer names. Case-insensitive exact match after normalization. Lowercase, trim and collapse whitespace. Strip one trailing legal suffix with an optional comma, then a leading "the ". Legal suffixes: inc, inc., incorporated, llc, l.l.c., ltd, ltd., limited, corp, corp., corporation, co, co., gmbh, ag, sa, s.a., bv, b.v., plc, pty ltd, srl. An empty normalized name returns 400 request.invalid. Values are OR within this key and AND across keys.
  - `exclude_company_names` | array of string | optional. The NOT of company_names after the same normalization. Lowercase, trim and collapse whitespace. Strip one trailing legal suffix with an optional comma, then a leading "the ". Legal suffixes: inc, inc., incorporated, llc, l.l.c., ltd, ltd., limited, corp, corp., corporation, co, co., gmbh, ag, sa, s.a., bv, b.v., plc, pty ltd, srl. An empty normalized name returns 400 request.invalid. Values are OR within this key and AND across keys.
  - `company_size` | array of CompanySize | optional. Company employee bands to include. Values: `1-10`, `11-50`, `51-200`, `201-500`, `501-1K`, `1K-5K`, `5K-10K`, `10K+`.
  - `company_revenue` | array of CompanyRevenue | optional. Company revenue ranges to include. Only companies with a known revenue range match; most small companies have none, so this key leaves them out. Values: `0-1M`, `1M-10M`, `10M-50M`, `50M-100M`, `100M-1B`, `1B+`.
  - `industries` | array of Industry | optional. Company industries to include, as slugs such as software_development. A label such as 'Software Development' or 'IT Services & IT Consulting' is folded to its slug. Read the values from GET /contacts/schema/industries/values.
  - `exclude_industries` | array of Industry | optional. Company industries to exclude, as slugs such as software_development. A label such as 'Software Development' or 'IT Services & IT Consulting' is folded to its slug. Read the values from GET /contacts/schema/industries/values.
  - `schools` | array of string | optional. School names. It matches any school on the record. The match is exact on the stored value, so send the name as the record holds it.
  - `skills` | array of string | optional. Skill names. It matches any skill on the record. The match is exact on the stored value, which is lowercase, and the input is lowercased. Read the values from GET /contacts/schema/skills/values.
  - `employee_count_min` | integer | optional. The smallest company employee count, inclusive.
  - `employee_count_max` | integer | optional. The largest company employee count, inclusive.
  - `founded_year_min` | integer | optional. The earliest company founding year, inclusive.
  - `founded_year_max` | integer | optional. The latest company founding year, inclusive.
  - `company_hq_countries` | array of Country | optional. Company headquarters countries to include. Send ISO-2 codes such as us, or English names such as Germany.
  - `last_round_after` | YearMonth | optional. The latest financing event is after this month, exclusive.
  - `last_round_before` | YearMonth | optional. The latest financing event is before this month, exclusive.
  - `last_round_types` | array of FundingRoundType | optional. Types of the latest financing event to include. Values: `pre_seed`, `seed`, `series_a`, `series_b`, `series_c_plus`, `private_equity`, `debt`, `other`.
  - `total_funding_min` | integer | optional. The smallest total funding in US dollars, inclusive. Missing funding is null and never matches.
  - `total_funding_max` | integer | optional. The largest total funding in US dollars, inclusive.
  - `company_ids` | array of CompanyId | optional. Company ids (`com_`) of the current employer to include. Take them from `POST /companies/search` or `POST /companies/lookup`. Values are OR within this key and AND across keys. Takes up to 1,000 ids.
  - `where` | WhereNode | optional. The condition tree. It is opened per account when the named keys cannot express the audience. The `signals` column is filtered here, with `in` or `not_in`.
    - `WhereGroup` | WhereGroup | optional. A boolean group of child nodes.
      - `and` | array of WhereNode | optional. Every child must match.
      - `or` | array of WhereNode | optional. At least one child must match.
      - `not` | WhereNode | optional. The child must not match.
    - `WhereLeaf` | WhereLeaf | optional. One bound condition on one column.
      - `field` | string | required. A contact column name, as `GET /contacts/schema` prints it, or signals. The name match is case-insensitive.
      - `op` | enum | required. The comparison to apply. The operator list is frozen. Values: `eq`, `neq`, `in`, `not_in`, `gt`, `gte`, `lt`, `lte`, `contains`, `exists`.
      - `value` | any | optional. The bound value: a string, a number, a boolean, or a list of at most 50 values for `in` and `not_in`. Text columns take eq, neq, in, not_in, contains, exists, gt, gte, lt and lte with text values. role_since takes eq, neq, gt, gte, lt, lte, in, not_in and exists with YYYY-MM-DD dates. The number columns (company_employee_count, company_founded_year, company_total_funding_usd) take eq, neq, gt, gte, lt, lte, in, not_in and exists, where exists means the value is known. has_work_email and has_phone take eq and neq with true or false. The list columns (company_industries, signals, skills, languages) take in, not_in, contains, exists, eq (the list holds the value) and neq. experience, education, certifications and social take contains and exists only. exists takes no value or true; false means the column is empty. A leaf other than exists needs a value. A refused operator, value type or shape answers 422 filter.rejected and names what the column takes.
- `columns` | array of string | optional. The columns to return. Every row also carries `id`, always first. The default is the compact set: name, title, company, company_domain, work_email, country, seniority, last_updated, mobile_phone, direct_phone, has_phone. Send `["*"]` for every column.
- `limit` | integer | optional. The page size. The default is 50 and the maximum is 500, on REST and through MCP alike. Default `50`.
- `cursor` | Cursor | optional. The cursor from the previous page. Omit it on the first page. Read pages until `page.next_cursor` is null.
- `max_rows_per_company` | integer | optional. The most contacts to return from one company. On search the cap holds inside one page, so next_cursor is null when it is set; use start_export for the full capped list. On export the file can hold fewer rows than rows_to_export, and only exported rows are charged.
- `exclude_ids` | array of PersonId | optional. Person ids to leave out of count, search and export. Unknown ids have no effect. Takes up to 1,000 ids.

## Response

The contact rows of this page, in the default sort order. The field names and their types live at `GET /contacts/schema`.

- `id` | PersonId | optional. Our person id. Store this one.
- `name` | string or null | optional. The full name of the person.
- `first_name` | string or null | optional. The given name.
- `last_name` | string or null | optional. The family name.
- `headline` | string or null | optional. The short self description on the public profile.
- `bio` | string or null | optional. The long self description on the public profile.
- `location` | string or null | optional. The location as one display string.
- `city` | string or null | optional. The city of the person.
- `region` | string or null | optional. The state, province or region.
- `country` | Country or null | optional. The country as a lowercase ISO-2 slug.
- `linkedin` | string or null | optional. The full LinkedIn profile URL, `https://www.linkedin.com/in/<slug>`. It is lowercased and carries no query string.
- `work_email` | string or null | optional. The work email, lowercased.
- `has_work_email` | boolean | optional. True when a work email is present.
- `email_status` | EmailStatus (slug) | optional. The work email status comes from our data source; verified_at is the month the source last verified the address. Values: `unknown`, `valid`, `invalid`, `catch_all`.
- `verified_at` | string or null | optional. The month the source last verified the address, or null.
- `title` | string or null | optional. The current job title.
- `function` | JobFunction (slug) or null | optional. The job function of the current role.
- `seniority` | Seniority (slug) or null | optional. The seniority rung of the current role.
- `company_id` | CompanyId or null | optional. Our id for the current employer.
- `company` | string or null | optional. The name of the current employer.
- `company_domain` | string or null | optional. The bare domain of the current employer.
- `company_size` | CompanySize (slug) or null | optional. The employee band of the current employer.
- `company_revenue` | CompanyRevenue (slug) or null | optional. The yearly revenue range of the current employer. Null when the range is not known, which is the case for most small companies.
- `company_industries` | array of Industry | optional. The industries of the current employer.
- `company_location` | string or null | optional. The location of the current employer as one display string.
- `company_linkedin` | string or null | optional. The LinkedIn URL of the current employer.
- `experience` | array of object | optional. The person's current roles only, main role first. The source has no past roles.
  - `is_main` | boolean | optional. True for the person's main role, the one the top-level fields show.
  - `description` | string or null | optional. What the role involves, as the record states it, cut to 1,000 characters.
  - `title` | string or null | optional. The job title in that role.
  - `seniority` | JobSeniority (slug) or null | optional. The seniority rung of that role.
  - `job_type` | string or null | optional. How the role was held, for example full time, part time, contract or internship.
  - `company_id` | CompanyId or null | optional. Our company id when the company is in our table, else null; fetch the company row for size, industries and funding.
  - `company` | string or null | optional. The employer name.
  - `domain` | string or null | optional. The bare domain of the employer.
  - `start` | YearMonth or null | optional. The month the role started.
  - `end` | YearMonth or null | optional. Always null: the source has no ended roles.
  - `current` | boolean | optional. Always true: every role in experience is current.
  - `skills` | array of string | optional. The skills the record attaches to that role.
- `education` | array of object | optional. The education history, newest first.
  - `school` | string or null | optional. The school name.
  - `school_domain` | string or null | optional. The bare domain of the school.
  - `school_linkedin` | string or null | optional. The full LinkedIn URL of the school, `https://www.linkedin.com/school/<slug>`, or the company URL when the record carries that one.
  - `degree` | string or null | optional. The degree earned.
  - `field` | string or null | optional. The field of study.
  - `start` | string or null | optional. When the study started, `YYYY-MM` or a year.
  - `end` | string or null | optional. When the study ended, `YYYY-MM` or a year.
  - `activities` | string or null | optional. The clubs, societies and activities the record lists.
  - `description` | string or null | optional. The free text the record carries about the study.
- `certifications` | array of object | optional. The certifications on the record.
  - `name` | string or null | optional. The certification name.
  - `issuer` | string or null | optional. The body that issued it.
  - `year` | integer or null | optional. The year it was issued.
- `social` | array of object | optional. The other public profiles on the record.
  - `network` | string or null | optional. The network the profile sits on, for example `twitter` or `github`.
  - `url` | string or null | optional. The full profile URL.
  - `username` | string or null | optional. The username on that network.
  - `followers` | integer or null | optional. The follower count, or null when the record carries none.
  - `connections` | integer or null | optional. The connection count, or null when the record carries none.
- `signals` | array of Signal | optional. The signals on the contact. Filter them with `in` or `not_in` on `signals` inside `where`. Values: `one_year_at_company`, `new_in_role_90d`, `recent_job_change`, `founder`, `at_public_company`.
- `skills` | array of string | optional. The skills on the record.
- `languages` | array of string | optional. The languages on the record.
- `role_since` | CalendarDate or null | optional. The day the current role started.
- `last_updated` | YearMonth or null | optional. The month any field on the record changed.
- `last_refreshed` | YearMonth or null | optional. The month we re-checked the record, even when nothing changed. Null until a refresh exists: nothing re-checks records yet.
- `mobile_phone` | string or null | optional. The mobile phone in E.164 form, or null.
- `direct_phone` | string or null | optional. The direct work line in E.164 form, or null.
- `has_phone` | boolean | optional. True when a mobile or direct phone is present.
- `job_description` | string or null | optional. The description of the current role from the public profile.

## Failures

| Status | Meaning |
| --- | --- |
| `400` | The request shape, a filter key, a value, a cursor or the enrichment mode was rejected. |
| `401` | The credential is missing, unknown or revoked. |
| `402` | The daily or the monthly contact credit cap is spent, or the free plan's lifetime pool is. |
| `422` | The filter was rejected before it ran: more than 32 predicates, deeper than 4, more than 25 leaves, more than 1,000 domains or company names, more than 50 values in another list, a leading wildcard in a where leaf, a leading or trailing wildcard in title_query, an operator, value type or shape the column does not take, a leaf other than exists with no value, or an empty `and` or `or` group. |
| `429` | The key passed 300 requests a minute or 2 concurrent requests. |
| `503` | The data is unavailable for a short time. |
| `504` | The query passed its time budget and was stopped. |

## Example

```bash
curl -sS -X POST "https://api.leadmcp.ai/v1/contacts/search" \
  -H "Authorization: Bearer lm_live_YOUR_API_KEY" \
  -H "LeadMCP-Version: 2026-09-19" \
  -H "Content-Type: application/json" \
  -d '{"filter":{"domains":["example.com"],"seniority":["vp"],"functions":["marketing_and_advertising"],"title_query":"demand generation"},"limit":50,"max_rows_per_company":3}'
```

Response `200`:

```json
{
  "ok": true,
  "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC",
  "release": "b_2026_09_15",
  "data": [
    {
      "id": "per_01JBQ2W8K9V6M3T0X5Y7Z4A1BC",
      "name": "Dana Reyes",
      "title": "VP Demand Generation",
      "company": "Example Corp",
      "company_domain": "example.com",
      "work_email": "dana.reyes@example.com",
      "country": "us",
      "seniority": "c_level",
      "last_updated": "2026-08",
      "mobile_phone": "+14155552671",
      "direct_phone": null,
      "has_phone": true
    }
  ],
  "page": {
    "returned": 50,
    "limit": 50,
    "total": 4210,
    "next_cursor": "eyJ2IjoxLCJiIjoiYl8yMDI2XzA5XzE1In0"
  },
  "quota": {
    "plan": "free",
    "contact_credits_used": 50,
    "month_remaining": 950,
    "today_remaining": 950,
    "binding": "month",
    "resets_at": "9999-12-31T23:59:59.000Z"
  },
  "warnings": []
}
```
