# LeadMCP API reference The LeadMCP API finds people and companies, enriches them with verified contact details, and exports the results. Every route returns JSON. Every route uses the same API key. Human readable version: https://leadmcp.ai/docs/v1 ## Base URL Send every request to `https://leadmcp.ai`. ## Authentication Create a key in the app, then send it on every request. Pick one header form per request. - `X-API-Key: lb_live_YOUR_API_KEY` - `Authorization: Bearer lb_live_YOUR_API_KEY` A revoked or missing key returns `401`. The health route needs no key. ## Rate limits Accounts start at 100 requests per minute across all routes. Requests above that return `429`. Back off and retry after a short pause. Batch routes accept up to 100 items per request. Ask us to raise the limit on your account if you integrate at higher volume. ## Errors | Status | Meaning | | --- | --- | | `400` | Invalid body, invalid CSV shape, or a batch above the limit. | | `401` | Missing, malformed, or revoked API key. | | `402` | Not enough plan credits, or an export limit reached. | | `429` | Rate limit exceeded. Back off and retry. | | `502` | An enrichment provider failed or timed out. | | `503` | The contact database is unavailable. | # Health ## Service health `GET /api/health` Authentication: none. No API key. Returns JSON such as `{ "ok": true, "enrichBundleId": "..." }` with `Cache-Control: no-store`. ### Example request ```bash curl -sS "https://leadmcp.ai/api/health" \ -H "X-API-Key: lb_live_YOUR_API_KEY" ``` ## Contact database health `GET /api/v1/contacts/health` Authentication: API key. Requires API key. Pings the contact database and returns connection status plus approximate `releases` row count when healthy. ### Example request ```bash curl -sS "https://leadmcp.ai/api/v1/contacts/health" \ -H "X-API-Key: lb_live_YOUR_API_KEY" ``` # Billing & limits ## Fair use budget `GET /api/v1/usage/fair-use` Authentication: API key. Requires API key. 0 credits. Remaining fair-usage budget on unlimited plans, how many more rows you can pull today and this month across search, export, and enrich. Both windows reset on the UTC calendar (daily at the next 00:00 UTC, monthly at 00:00 UTC on the 1st), not 24 hours after you hit the cap, and not your local midnight; `binding` names the window that limits you first. Defaults are 500,000 rows/day and 6,000,000 rows/month, but caps are set per account, this endpoint is authoritative over any figure quoted elsewhere on this page, and if you're integrating at higher volume, message us and we'll raise yours. Paid and free plans get `applies: false` plus `credits_remaining`. ### Response fields | Field | Type | Description | | --- | --- | --- | | `ok` | boolean | True when the request succeeded. | | `applies` | boolean | True when fair use caps govern this account. | | `plan` | string | The plan name for this account. | | `exempt` | boolean | True when this account is exempt from the caps. | | `daily` | object | The daily window: cap, used, remaining and reset time. | | `monthly` | object | The monthly window: cap, used, remaining and reset time. | | `binding` | string | The window that limits you first. | ### Example request ```bash curl -sS "https://leadmcp.ai/api/v1/usage/fair-use" \ -H "X-API-Key: lb_live_YOUR_API_KEY" ``` ### Example response (200) ```json { "ok": true, "applies": true, "plan": "unlimited", "exempt": false, "daily": { "cap": 500000, "used": 120000, "remaining": 380000, "period": "utc_calendar_day", "period_start": "2026-08-21T00:00:00.000Z", "resets_at": "2026-08-22T00:00:00.000Z" }, "monthly": { "cap": 6000000, "used": 800000, "remaining": 5200000, "period": "utc_calendar_month", "period_start": "2026-08-01T00:00:00.000Z", "resets_at": "2026-09-01T00:00:00.000Z" }, "binding": "daily" } ``` # Person enrichment ## Enrich from a work email `POST /api/v1/enrich/from-email` Authentication: API key. JSON array of work emails. One credit per item where `success` is `true`. `creditsRemaining` may be `null` on unlimited plans. ### Body parameters | Name | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `items` | array | yes | | Each element must include `email` (string). | ### Response fields | Field | Type | Description | | --- | --- | --- | | `ok` | boolean | True when the request succeeded. | | `results` | array | One result object for each item you sent, in the same order. | | `creditsRemaining` | number | Plan credits left after this request. Null on unlimited plans. | ### Example request ```bash curl -sS -X POST "https://leadmcp.ai/api/v1/enrich/from-email" \ -H "X-API-Key: lb_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"items":[{"email":"jane@acme.com"}]}' ``` ### Example response (200) ```json { "ok": true, "results": [ { "email": "jane@acme.com", "success": true, "profileUrl": "https://www.linkedin.com/in/janedoe", "data": { "email_address": "jane@acme.com", "person_linkedin_url": "https://www.linkedin.com/in/janedoe", "first_name": "Jane", "last_name": "Doe" } }, { "email": "bob@example.com", "success": false, "profileUrl": null, "data": null } ], "creditsRemaining": 4999 } ``` ## Enrich a CSV file `POST /api/v1/enrich/csv` Authentication: API key. `Content-Type: multipart/form-data` with field `file`, or raw POST with `Content-Type: text/csv` and `mode` / `mapping` on the query string. **Column mapping**, map `email` to your work-email column (required). Omit `profileUrl` in mapping to write found professional profile URLs to `ProfileURL`. CSVs need at least two columns, delimiter auto-detection fails on single-column files with a `400`. If you only have emails, add any second column (even an empty one). **File size:** there is no row cap on CSVs, but this synchronous route processes the file inline and the gateway times out at 60 seconds, in practice around 5,000 rows per request; larger files return `504` with no partial result. For bigger files, split into ~5,000-row chunks, or use the async CSV upload flow (app to Enrichment, or the MCP tool `create_csv_enrichment_upload_link`), which processes files of any size in the background. The "~10,000 enrichments per minute" figure above is throughput across requests, not a per-file cap. **Output columns**, on success, every scalar provider field in `data` becomes an `enriched_*` column (e.g. `enriched_first_name`, `enriched_job_title`). Convenience columns `email` and `ProfileURL` are written when mapped (or use defaults above). Nested objects from the provider are omitted (same as JSON batch `data`). Successful CSV responses return `200` with body = enriched CSV (not JSON). Headers: `X-Enrich-Total`, `X-Enrich-Succeeded`, `X-Enrich-Skipped`, `X-Enrich-Partial`, `X-Credits-Remaining` (empty when unlimited). Use `curl -D headers.txt` to inspect. ### Body parameters | Name | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `mode` | string | yes | | Set to `work_email` (`email`). | | `file` | file | yes | | CSV file (multipart) or raw body (`text/csv`). | | `mapping` | JSON string | yes | | Maps semantic keys to your column headers (see below). | | `maxRows` | number | no | | Optional cap on rows processed (multipart field or `?maxRows=`). | | `fileName` | string | no | | Optional label stored in run history. | ### Example request ```bash curl -sS -X POST "https://leadmcp.ai/api/v1/enrich/csv" \ -H "X-API-Key: lb_live_YOUR_API_KEY" \ -F "file=@./contacts.csv" \ -F "mode=work_email" \ -F 'mapping={"email":"Work_Email"}' ``` ## Enrich from a profile URL `POST /api/v1/enrich/from-linkedin` Authentication: API key. JSON array of professional profile URLs. Optional `limit_per_item` (default 1, max 10). One credit per item where `success` is `true`. `creditsRemaining` may be `null` on unlimited plans. Batches are capped at 100 items per request; a larger batch returns `400`. ### Body parameters | Name | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `items` | array | yes | | Each element must include `linkedin_url` (string). | | `limit_per_item` | number | no | 1 | Matches per URL (default 1, max 10). | ### Response fields | Field | Type | Description | | --- | --- | --- | | `ok` | boolean | True when the request succeeded. | | `results` | array | One result object for each item you sent, in the same order. | | `creditsRemaining` | number | Plan credits left after this request. Null on unlimited plans. | ### Example request ```bash curl -sS -X POST "https://leadmcp.ai/api/v1/enrich/from-linkedin" \ -H "X-API-Key: lb_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"items":[{"linkedin_url":"https://www.linkedin.com/in/example"}],"limit_per_item":1}' ``` ### Example response (200) ```json { "ok": true, "results": [ { "linkedinUrl": "https://www.linkedin.com/in/example", "success": true, "email": "found@company.com", "data": { "email_address": "found@company.com", "person_linkedin_url": "https://www.linkedin.com/in/example", "first_name": "Alex", "last_name": "Example" } }, { "linkedinUrl": "https://www.linkedin.com/in/other", "success": false, "email": null, "data": null } ], "creditsRemaining": 4999 } ``` ## Enrich from a name and company `POST /api/v1/enrich/from-person` Authentication: API key. JSON array of person records. Each item needs `first_name`, `last_name`, and at least one of `company_name` or `email_domain`. Same batch size cap as other batch routes (100 items per request). `creditsRemaining` may be `null` on unlimited plans. ### Body parameters | Name | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `items` | array | yes | | Each element: `first_name`, `last_name`, and `company_name` and/or `email_domain` (non-empty strings). | ### Response fields | Field | Type | Description | | --- | --- | --- | | `ok` | boolean | True when the request succeeded. | | `results` | array | One result object for each item you sent, in the same order. | | `creditsRemaining` | number | Plan credits left after this request. Null on unlimited plans. | ### Example request ```bash curl -sS -X POST "https://leadmcp.ai/api/v1/enrich/from-person" \ -H "X-API-Key: lb_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"items":[{"first_name":"Jane","last_name":"Doe","company_name":"Acme Inc"}]}' ``` ### Example response (200) ```json { "ok": true, "results": [ { "first_name": "Jane", "last_name": "Doe", "company_name": "Acme Inc", "success": true, "email": "jane.doe@acme.com", "profileUrl": "https://www.linkedin.com/in/janedoe", "data": { "email_address": "jane.doe@acme.com", "person_linkedin_url": "https://www.linkedin.com/in/janedoe", "company_name": "Acme Inc" } }, { "first_name": "Bob", "last_name": "Smith", "email_domain": "example.com", "success": false, "email": null, "profileUrl": null, "data": null } ], "creditsRemaining": 4999 } ``` # Contacts lookup ## Look up one phone number `GET /api/v1/contacts/lookup/phone` Authentication: API key. Query param `phone` or `cellphone`. Returns the full `releases` row in `data` when found; `success: false` with no charge when not found. ### Query parameters | Name | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `phone` | string | yes | | Pass this as a query string value. | | `cellphone` | string | yes | | Pass this as a query string value. | ### Response fields | Field | Type | Description | | --- | --- | --- | | `ok` | boolean | True when the request succeeded. | | `success` | boolean | True when this request found a match. | | `data` | object | The matched record, or null when nothing matched. | | `creditsRemaining` | number | Plan credits left after this request. Null on unlimited plans. | ### Example request ```bash curl -sS "https://leadmcp.ai/api/v1/contacts/lookup/phone?phone=%2B1+415-555-0142" \ -H "X-API-Key: lb_live_YOUR_API_KEY" ``` ### Example response (200) ```json { "ok": true, "success": true, "data": { "first_name": "Jane", "last_name": "Doe", "email_address": "jane@acme.com", "cellphone": "+1 415-555-0142", "domain_org": "acme.com" }, "creditsRemaining": 4999 } ``` ## Look up phone numbers in batch `POST /api/v1/contacts/from-phone` Authentication: API key. JSON batch lookup (same pattern as `POST /api/v1/enrich/from-email`). Each item accepts `phone` or `cellphone`. US phones are stored as `+1 XXX-XXX-XXXX` (space after country code). Optional `limit_per_item` (default 1, max 10). ### Body parameters | Name | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `items` | array | no | | | | `limit_per_item` | number | no | | | ### Response fields | Field | Type | Description | | --- | --- | --- | | `ok` | boolean | True when the request succeeded. | | `results` | array | One result object for each item you sent, in the same order. | | `creditsRemaining` | number | Plan credits left after this request. Null on unlimited plans. | ### Example request ```bash curl -sS -X POST "https://leadmcp.ai/api/v1/contacts/from-phone" \ -H "X-API-Key: lb_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"items":[{"phone":"+1 415-555-0142"},{"phone":"5550000000"}],"limit_per_item":1}' ``` ### Example response (200) ```json { "ok": true, "results": [ { "phone": "+1 415-555-0142", "success": true, "data": { "first_name": "Jane", "cellphone": "+1 415-555-0142" } }, { "phone": "5551234567", "success": false, "data": null } ], "creditsRemaining": 4998 } ``` ## Find colleagues at a company `POST /api/v1/contacts/lookup/colleagues` Authentication: API key. Find people at a company from its email domain (e.g. `acme.com`) **or** its company profile URL, pass `email_domain` or `linkedin_company_url`, not both. Optional `job_titles` narrows to matching titles (same whole-word token match as search `job_titles`; omit to return everyone). Queries the LeadMCP contact database, same data as search. Defaults to verified emails only. Returns contacts with pagination (`has_more`, `next_offset`). One credit per record returned; 0 if none. Default `limit_per_item` 100 (max 5000). For very large lists, use search export with the same domain filter. MCP tool: `leadmcp_lookup_colleagues`. ### Body parameters | Name | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `email_domain` | string | no | | | | `job_titles` | array | no | | | | `limit_per_item` | number | no | | | | `offset` | number | no | | | ### Response fields | Field | Type | Description | | --- | --- | --- | | `ok` | boolean | True when the request succeeded. | | `contacts` | array | The contact records for this page. | | `total_available` | number | A pagination hint. Use the count endpoint for the exact total. | | `query_credits_used` | number | Plan credits this request spent. | | `creditsRemaining` | number | Plan credits left after this request. Null on unlimited plans. | | `offset` | number | The row offset this page started at. | | `limit` | number | The number of rows this page requested. | | `returned` | number | The number of rows this page returned. | | `has_more` | boolean | True when more rows are available. | | `next_offset` | number | Pass this as offset to read the next page. | | `normalized_inputs` | object | The inputs after LeadMCP cleaned them up. | ### Example request ```bash curl -sS -X POST "https://leadmcp.ai/api/v1/contacts/lookup/colleagues" \ -H "X-API-Key: lb_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"email_domain":"tabby.ai","limit_per_item":100}' ``` ### Example response (200) ```json { "ok": true, "contacts": [ "..." ], "total_available": 842, "query_credits_used": 100, "creditsRemaining": 4900, "offset": 0, "limit": 100, "returned": 100, "has_more": true, "next_offset": 100, "normalized_inputs": { "email_domain": "tabby.ai" } } ``` ## Find decision makers `POST /api/v1/contacts/lookup/decision-makers` Authentication: API key. Find decision makers at a company using LeadMCP database search. Matches contacts with seniority `C-Team`, `VP`, or `Director`, or any job title containing `Head` (e.g. Head of Growth, Department Head). Provide exactly one of `domain`, `company_name`, or `linkedin_company_url`. Always returns contacts with `VALID` emails. `require_email` is accepted but ignored. Up to 5000 records per request (default 5000); use `offset` for the next page. 1 credit per record returned; 0 if none. The response echoes the applied `role_filter`. ### Body parameters | Name | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `domain` | string | no | | | | `limit` | number | no | | | | `offset` | number | no | | | ### Example request ```bash curl -sS -X POST "https://leadmcp.ai/api/v1/contacts/lookup/decision-makers" \ -H "X-API-Key: lb_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"domain":"acme.com","limit":10}' ``` # Database search ## 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}' ``` ## Count matches `POST /api/v1/contacts/search/count` Authentication: API key. Aggregated match count for a filter set, free (0 credits), no records returned, no pagination needed. Takes the same auth and the same filter body as `POST /api/v1/contacts/search` (`limit`, `offset`, and `columns` are ignored). Use it to size a query before searching or exporting, or for coverage analysis. Counts are exact at any scale, with no upper cap, so there is no need to slice broad queries into segments. At least one filter (or `where_sql`) is required, an empty `filters` object returns `400` listing the accepted parameters. `exportable_rows` is how many of the matches a single CSV export would write (`min(total_matching, 50000)`, further bounded by remaining credits on the free plan); `export_capped` is `true` when the match count exceeds that. ### Body parameters | Name | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `filters` | object | no | | | ### Response fields | Field | Type | Description | | --- | --- | --- | | `ok` | boolean | True when the request succeeded. | | `total_matching` | number | The exact number of contacts that match the filters. | | `max_export_rows` | number | The largest number of rows one export can write. | | `exportable_rows` | number | How many matches a single export would write. | | `export_capped` | boolean | True when the match count is above the export limit. | | `credits_used` | number | Plan credits this request spent. | | `creditsRemaining` | null | Plan credits left after this request. Null on unlimited plans. | | `message` | string | A plain sentence that explains the result. | ### Example request ```bash curl -sS -X POST "https://leadmcp.ai/api/v1/contacts/search/count" \ -H "X-API-Key: lb_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"filters":{"countries":["Ireland"],"seniority":["C-Team"]}}' ``` ### Example response (200) ```json { "ok": true, "total_matching": 56721, "max_export_rows": 50000, "exportable_rows": 50000, "export_capped": true, "credits_used": 0, "creditsRemaining": null, "message": "Found 56721 matching contacts. Up to 50000 can be exported in one CSV (max 50000 per export)." } ``` ## Start a CSV export `POST /api/v1/contacts/search/export` Authentication: API key. Optional body field: `unmask: true` fills missing contact details up to your monthly unmask budget. The default exports rows as stored. Status includes `rows_unmasked` and `rows_masked_by_budget`. Send the same option to `POST /api/v1/contacts/search/count` for `unmask_preview: { rows, cached, new_unmasks, budget_remaining }` without spending budget. MCP `export_contacts` and `count_contacts` accept this option too. Start an async export of search matches to CSV on S3. Same filters as `POST /api/v1/contacts/search` (no `offset`). Returns immediately with `export_id` (HTTP 202); poll `GET /api/v1/contacts/search/export/{export_id}` until `job_status` is `completed`, then use `export_url`. Free plan: capped at `min(creditsRemaining, total_available)`. Unlimited: subject to daily (500k) and monthly (6M) export caps. Optional `max_rows` (1-50,000) caps how many rows this export writes; omit to export all matches up to plan/credit/50k limits. 1 credit per row exported. ### Body parameters | Name | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `filters` | object | no | | | | `max_per_company` | number | no | | | | `max_rows` | number | no | | | | `confirmed` | boolean | no | | | ### Response fields | Field | Type | Description | | --- | --- | --- | | `ok` | boolean | True when the request succeeded. | | `export_id` | string | The identifier of the export job. | | `job_status` | string | One of queued, running, completed or failed. | | `rows_available` | number | The number of rows that matched the filters. | | `export_row_cap` | number | The row cap applied to this export. | | `rows_capped_by_credits` | boolean | True when the credit balance limited the export. | | `message` | string | A plain sentence that explains the result. | ### Example request ```bash curl -sS -X POST "https://leadmcp.ai/api/v1/contacts/search/export" \ -H "X-API-Key: lb_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "filters": { "job_titles": ["Software Engineer"], "cities": ["San Francisco"] }, "max_per_company": 3, "max_rows": 5000, "confirmed": true }' ``` ### Example response (202) ```json { "ok": true, "export_id": "01KV8DNW282Y2V5YAQ51C2XWN1", "job_status": "queued", "rows_available": 46169, "export_row_cap": 5000, "rows_capped_by_credits": true, "message": "Export started. Poll GET /api/v1/contacts/search/export/{export_id} until job_status is completed." } ``` ## Check an export `GET /api/v1/contacts/search/export/{export_id}` Authentication: API key. Poll export status from `POST /api/v1/contacts/search/export`. While `job_status` is `queued` or `running`, retry every few seconds. When `completed`, the response includes a presigned `export_url` (valid 24 hours). ### Path parameters | Name | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `export_id` | string | yes | | The export id of the record you want. | ### Response fields | Field | Type | Description | | --- | --- | --- | | `ok` | boolean | True when the request succeeded. | | `export_id` | string | The identifier of the export job. | | `job_status` | string | One of queued, running, completed or failed. | | `export_url` | string | A signed CSV download link, valid for 24 hours. | | `expires_in_seconds` | number | How long the download link stays valid. | | `rows_exported` | number | The number of rows written to the CSV. | | `rows_available` | number | The number of rows that matched the filters. | | `rows_capped_by_credits` | boolean | True when the credit balance limited the export. | | `query_credits_used` | number | Plan credits this request spent. | | `creditsRemaining` | number | Plan credits left after this request. Null on unlimited plans. | ### Example request ```bash curl -sS "https://leadmcp.ai/api/v1/contacts/search/export/01KV8DNW282Y2V5YAQ51C2XWN1" \ -H "X-API-Key: lb_live_YOUR_API_KEY" ``` ### Example response (200) ```json { "ok": true, "export_id": "01KV8DNW282Y2V5YAQ51C2XWN1", "job_status": "completed", "export_url": "https://...", "expires_in_seconds": 86400, "rows_exported": 46169, "rows_available": 46169, "rows_capped_by_credits": false, "query_credits_used": 46169, "creditsRemaining": 1234 } ``` ## List filter values `GET /api/v1/contacts/filter-values` Authentication: API key. Query param `field`, returns allowed enum values for list filters used in search. Supported fields: `seniority`, `job_functions`, `company_size`, `revenue`, `regions`, `continents`, `countries`, `headquarters_countries`, `office_countries`, `job_location_country`, `industries`, `personas`, `entity_types`, `email_status`. Free-text filters have no fixed value list, asking for one returns a `400` saying so. ### Query parameters | Name | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `field` | string | yes | | Pass this as a query string value. | ### Example request ```bash curl -sS "https://leadmcp.ai/api/v1/contacts/filter-values" \ -H "X-API-Key: lb_live_YOUR_API_KEY" ``` # MCP server LeadMCP runs a remote MCP server over streamable HTTP. Connect an MCP client once, then ask it to search, enrich and export. The tools cover the REST API plus the products that have no REST route. Server URL: `https://leadmcp.ai/api/mcp` ## Client setup 1. Create an API key in the app under Integrations. 2. Add the server URL to your MCP client configuration. 3. Send the key in the `X-API-Key` header, or as an `Authorization: Bearer` header. 4. Restart the client and confirm the tools appear. ```json { "mcpServers": { "leadmcp": { "url": "https://leadmcp.ai/api/mcp", "headers": { "X-API-Key": "lb_live_YOUR_API_KEY" } } } } ``` ## Health | Tool | REST endpoint | Description | | --- | --- | --- | | `leadmcp_health` | GET /api/health | Ping the LeadMCP origin. No API key for the REST health endpoint. Returns ok, service and version when reachable. | ## Usage | Tool | REST endpoint | Description | | --- | --- | --- | | `get_fair_use` | GET /api/v1/usage/fair-use | Remaining unlimited-plan daily/monthly fair-use budget for search/export/enrich (0 credits). Includes used, remaining, caps, and UTC resets_at. Daily resets at next 00:00 UTC, not 24 hours after hitting the cap. | ## Enrichment | Tool | REST endpoint | Description | | --- | --- | --- | | `leadmcp_get_emails_from_linkedin_batch` | POST /api/v1/enrich/from-linkedin | Find work emails and contact details from professional profile URLs. Accepts up to 100 people per batch; charges one credit per successful match. | | `leadmcp_get_linkedin_urls_from_emails_batch` | POST /api/v1/enrich/from-email | Find professional profile profiles and contact details from work email addresses. Accepts up to 100 people per batch; charges one credit per successful match. | | `leadmcp_enrich_person_batch` | POST /api/v1/enrich/from-person | Enrich people from first name, last name, and company name or domain. Accepts up to 100 people per batch; charges one credit per successful match. | | `create_csv_enrichment_upload_link` | MCP only | Create a browser upload link for CSV enrichment. Upload a CSV up to 15 MB or supply a public Google Sheet, then map columns and start the run. | | `check_enrichment_upload` | MCP only | Check whether an enrichment upload has been submitted and retrieve its run_id so the client can follow the run. | | `check_enrichment_status` | MCP only | Check an enrichment run for its current status, processed rows and results. Use the run_id returned after upload. | | `get_enrichment_result` | MCP only | Retrieve the completed enrichment result as a signed CSV download link. The download link expires after one hour. | ## Contacts | Tool | REST endpoint | Description | | --- | --- | --- | | `leadmcp_contacts_health` | GET /api/v1/contacts/health | Check contact database connectivity and the approximate number of available contact records. This diagnostic does not spend credits. | | `leadmcp_lookup_contact_by_phone` | GET /api/v1/contacts/lookup/phone | Look up a contact by phone number and return the matching contact details. Charges one credit when a match is found. | | `leadmcp_lookup_contacts_by_phone_batch` | POST /api/v1/contacts/from-phone | Look up contacts from a batch of phone numbers. Each successful match costs one credit; unsuccessful matches do not spend credits. | | `leadmcp_lookup_colleagues` | POST /api/v1/contacts/lookup/colleagues | Find colleagues at a company from a work email or company domain. Returns 100 contacts per page by default, up to 5,000 through REST, at one credit per returned contact. | | `lookup_decision_makers` | POST /api/v1/contacts/lookup/decision-makers | Find decision makers with valid emails at a company: executives, VPs, directors and head-of roles. Returns up to 100 contacts per MCP call at one credit per contact. | ## Search | Tool | REST endpoint | Description | | --- | --- | --- | | `count_contacts` | POST /api/v1/contacts/search/count | Count all contacts matching a filter set without returning records or spending credits. Use the exact count to size an audience before searching or exporting. | | `search_contacts` | POST /api/v1/contacts/search | Search contacts with the same filters as REST. Returns up to 100 rows per MCP call, pagination fields including next_offset, and an export_offer when has_more is true. Charges one credit per row. | | `export_contacts` | POST /api/v1/contacts/search/export | Start an asynchronous CSV export of matching contacts. Returns export_id immediately; exports up to 50,000 rows, subject to available credits and fair-use limits. Charges one credit per exported row. | | `check_contact_export` | GET /api/v1/contacts/search/export/{export_id} | Check the status of a contact export using its export_id. Completed exports include the row count and a signed CSV download URL valid for 24 hours. | | `get_available_values` | GET /api/v1/contacts/filter-values | Get the allowed values for a named list filter, such as country, seniority or email status. Free-text filters do not have fixed value lists. This lookup is free. | | `get_available_columns` | MCP only | List available contact columns, labels, types, supported filters and export options, including populated_count and fill_share to help assess field coverage. |