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

# Search contacts

`POST /api/v1/contacts/search`

Authentication: API key.

Filtered search over the contact index. Each request returns up to 5,000 records (default 1000; a larger `limit` is clamped to 5,000 and the response echoes the effective `limit`). Paginate with `offset` until `has_more` is `false` to walk up to 50,000 matches per search, or use `POST /api/v1/contacts/search/export` for a full CSV on S3. Searches matching more than 50,000 contacts can't be paged reliably, result order isn't stable above the cap, so pages re-serve and skip rows. Size the query with `POST /api/v1/contacts/search/count` first and narrow with filters until it's under 50,000. Pass filters in a nested `filters` object or as top-level keys, both work. All filter fields are combined with AND logic. Optional `limit` (default 1000, max 5,000), `offset` (default 0), and `max_per_company` (1-50) sit at the top level. Responses include `has_more` and `next_offset` for pagination. `total_available` in the response is a pagination hint bounded by the per-request window (it carries `total_available_is_minimum: true` when more rows match), for the true match count use `POST /api/v1/contacts/search/count`.

Seniority aliases: `C-Suite`, `C-Level`, and `Executive` map to `C-Team`; `Vice President`, `SVP`, and `EVP` map to `VP`. See `GET /filter-values` for allowed enum values.

### Body parameters

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `domains` | string[] | no |  | Company website domains (e.g. `acme.com`, `globex.com`). |
| `company_name` | string | no |  | Company name substring match on `org_company_name`. |
| `email_domain` | string | no |  | Email domain filter (e.g. `gmail.com`). Comma-separated values OK. |
| `domain_list_id` | string | no |  | Saved domain list id (`fv_...` or `@fvid:...`). |
| `company_linkedin_urls` | string[] | no |  | company profile URLs (e.g. `https://www.linkedin.com/company/acme/`). Exact `/company/{slug}` match on the company and current-employer professional profile URLs; several URLs are OR'd. Non-company professional profile URLs return `400`. |
| `job_titles` | string[] | no |  | Job title keywords, whole-word match on title tokens (`"CTO"` matches "CTO & Co-Executive", not "Director"). Acronyms and spelled-out forms don't cross-match, include both (`["CTO", "Chief Technology Officer"]`). |
| `seniority` | string[] | no |  | Job level: `C-Team`, `VP`, `Director`, `Manager`, `Staff`, `Other`. Aliases normalized (`C-Suite` to `C-Team`). |
| `job_functions` | string[] | no |  | Department: `Sales & Business Development`, `Engineering`, `Information Technology`, etc. |
| `personas` | string[] | no |  | Buyer personas (e.g. `CEO / Executive`, `CTO`, `DevOps`). |
| `job_start_date_min` | string | no |  | Earliest current-role start date (`YYYY-MM-DD`). |
| `job_start_date_max` | string | no |  | Latest current-role start date (`YYYY-MM-DD`). |
| `industries` | string[] | no |  | professional profile industries (441 categories). |
| `company_size` | string[] | no |  | Exact professional profile size-band labels (e.g. `["51 to 200"]`). Use `GET /filter-values?field=company_size` for the 8 bands. Prefer this when you have band names; use `company_size_min`/`company_size_max` for numeric bounds. |
| `company_size_min` | number | no |  | Minimum employees, server maps to employee count ranges. |
| `company_size_max` | number | no |  | Maximum employees, server maps to employee count ranges. |
| `revenue` | string[] | no |  | Revenue ranges: `<$1M`, `$1M to <$10M`, `$10M to <$50M`, `$50M to <$100M`, `$100M to <$1B`, `$1B+`. |
| `headquarters_countries` | string[] | no |  | Company HQ country (`headquarters_country_name`). Use `GET /filter-values?field=headquarters_countries` for valid values. |
| `company_description` | string | no |  | Substring search on company about text (`org_about_us`). |
| `entity_types` | string[] | no |  | Legal entity type (`Public Company`, `Privately Held`, `Non Profit`, ...). Use `field=entity_types` for values. |
| `technologies` | string[] | no |  | Technologies/tools the company uses (e.g. `Salesforce`, `HubSpot`). Matches across all technographic fields. |
| `has_mobile_app` | boolean | no |  | Only companies that have (`true`) / do not have (`false`) a mobile app. |
| `has_web_app` | boolean | no |  | Only companies that have (`true`) / do not have (`false`) a web application. |
| `naics_codes` | string[] | no |  | NAICS code prefix match. `["5415"]` catches every 5415xx code. |
| `naics_descriptions` | string[] | no |  | NAICS category descriptions, substring match. |
| `sic_codes` | string[] | no |  | SIC codes. |
| `sic_descriptions` | string[] | no |  | SIC category descriptions, substring match. |
| `uk_industry_codes` | string[] | no |  | UK SIC codes. |
| `uk_industry_descriptions` | string[] | no |  | UK SIC descriptions, substring match. |
| `crunchbase_categories` | string[] | no |  | Crunchbase categories, substring match. |
| `employees_min` | number | no |  | Minimum employees. Matches whole professional profile headcount bands that overlap your range, not exact counts, e.g. `employees_max: 9` returns the entire 1-10 band. |
| `employees_max` | number | no |  | Maximum employees. Band-overlap matching, same as `employees_min`. |
| `revenue_min` | number | no |  | Minimum revenue in USD. |
| `revenue_max` | number | no |  | Maximum revenue in USD. |
| `followers_min` | number | no |  | Minimum professional profile followers. |
| `followers_max` | number | no |  | Maximum professional profile followers. |
| `founded_year_min` | number | no |  | Earliest founding year. |
| `founded_year_max` | number | no |  | Latest founding year. |
| `total_funding_min` | number | no |  | Minimum total funding raised (USD). |
| `total_funding_max` | number | no |  | Maximum total funding raised (USD). |
| `monthly_traffic_min` | number | no |  | Minimum total monthly web traffic. |
| `monthly_traffic_max` | number | no |  | Maximum total monthly web traffic. |
| `employee_growth_rate_min` | number | no |  | Minimum professional profile employee growth rate in percent (`5` = 5%). |
| `employee_growth_rate_max` | number | no |  | Maximum professional profile employee growth rate in percent. |
| `monthly_google_adspend_min` | number | no |  | Minimum estimated monthly Google Ads spend (USD). |
| `monthly_google_adspend_max` | number | no |  | Maximum estimated monthly Google Ads spend (USD). |
| `employee_profiles_on_linkedin_min` | number | no |  | Minimum exact professional profile employee-profile headcount, a hard cutoff, unlike the band-overlap behaviour of `employees_min`/`employees_max`. |
| `employee_profiles_on_linkedin_max` | number | no |  | Maximum exact professional profile employee-profile headcount (hard cutoff). |
| `valid_email_count_min` | number | no |  | Minimum count of verified emails held at the company, a proxy for how reachable the account is. |
| `valid_email_count_max` | number | no |  | Maximum count of verified emails held at the company. |
| `specialties` | string | no |  | Keyword search over company professional profile specialties. Comma-separate keywords to OR them. |
| `company_headline` | string | no |  | Keyword search over the company professional profile headline/tagline. Comma-separate to OR. |
| `countries` | string[] | no |  | Person's country (`person_country_name`). |
| `regions` | string[] | no |  | Macro-regions: `NORAM`, `EMEA`, `APAC`, `LATAM`. |
| `continents` | string[] | no |  | `North America`, `Europe`, `Asia`, `South America`, `Africa`, `Oceania`, `Antarctica`. |
| `cities` | string[] | no |  | Person's city, substring match. |
| `states` | string[] | no |  | Person's state/province, substring match. |
| `job_location_country` | string[] | no |  | Country where the company/office is located. |
| `job_location_state` | string[] | no |  | State/province where the office is located, substring match. |
| `job_location_city` | string[] | no |  | City where the office is located, substring match. |
| `office_countries` | string[] | no |  | Countries where the company has offices. Use `GET /filter-values?field=office_countries` for values, the fullest canonical country list. |
| `office_states` | string[] | no |  | Office state/province, substring match. |
| `office_cities` | string[] | no |  | Office city, substring match. |
| `first_name` | string | no |  | First name, substring match. |
| `last_name` | string | no |  | Last name, substring match. |
| `email_address` | string | no |  | Specific email address, exact match. |
| `linkedin_url` | string | no |  | professional profile URL, substring match. |
| `person_description` | string | no |  | Substring search on person bio (`about_me`). |
| `skills` | string | no |  | Substring search on the contact's skills. |
| `education` | string | no |  | Keyword search over education history, schools, degrees, fields of study. |
| `languages` | string | no |  | Keyword search over listed languages. |
| `certifications` | string | no |  | Keyword search over certifications. |
| `linkedin_headline` | string | no |  | Keyword search over the person's professional profile headline. Comma-separate keywords to OR them. |
| `job_description` | string | no |  | Keyword search over the current role description. Comma-separate to OR. |
| `email_status` | string[] | no |  | Verification status enum: `VALID`, `CATCH_ALL`, `INVALID`. Use `["VALID"]` for deliverable emails. Do not send `"verified"`. |
| `require_email` | boolean | no |  | Only return contacts with a non-empty `email_address`. |
| `require_phone` | boolean | no | false | Only return contacts with a phone number (`cellphone`). Default `false`. |
| `exclude_domains` | string[] | no |  | Exclude these company domains. |
| `exclude_countries` | string[] | no |  | Exclude these person countries. |
| `exclude_headquarters_countries` | string[] | no |  | Exclude these company HQ countries. |
| `exclude_industries` | string[] | no |  | Exclude these industries. |
| `exclude_job_titles` | string[] | no |  | Exclude contacts whose title matches these keywords, whole-word token match, same semantics as `job_titles`. |
| `limit` | integer | no | 1000 | Max rows this page (default 1000, max 5,000, larger values are clamped and the response echoes the effective `limit`). |
| `offset` | integer | no | 0 | Skip rows for pagination (default 0). Use `next_offset` from the prior response. |
| `max_per_company` | integer | no |  | Cap contacts per company (1-50). Use for diverse results across many domains. |
| `columns` | string[] | no |  | Output columns by display label or internal name. When omitted, a default set plus any filtered columns is returned. REST JSON keeps raw column names; MCP responses and CSV exports use display labels. |
| `where_sql` | string | no |  | Advanced: a raw SQL `WHERE` predicate over the catalog's internal column names, AND-combined with the filters above. Reaches fields that have no dedicated filter, e.g. `"MONTHLY_GOOGLE_ADSPEND_ORG > 0"` matches companies actively running Google Ads. Also accepted by `search/count` and `search/export`. Invalid column names or syntax return `400`. Maximum 4,000 characters, longer predicates return `400`; split wide title lists across requests or move them into `job_titles`. Known limitation: a string literal containing the bare word `system` (e.g. `'%system%'`) is currently rejected by the SQL-keyword guard with a "subqueries, joins, or DDL" message, use a longer substring such as `'%system administrator%'` or the `job_titles` filter instead. |

### Example request

```bash
curl -sS -X POST "https://leadmcp.ai/api/v1/contacts/search" \
  -H "X-API-Key: lb_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"filters":{"domains":["acme.com"],"email_status":["VALID"]},"limit":10}'
```
