# LeadMCP API and MCP server LeadMCP is a contact-data API and an MCP server for AI agents. An agent sends structured filters and gets a count, a page of contact rows, a file, or an enrichment answer. Human readable version: https://docs.leadmcp.ai Contract version: 2026-09-19 ## Overview LeadMCP is a contact-data API and an MCP server for AI agents. An agent sends structured filters and gets a count, a page of contact rows, a file, or an enrichment answer. Page: https://docs.leadmcp.ai Our data, licensed from multiple sources. The store holds hundreds of millions of professionals and the companies they work for. We publish no row totals. To size an audience, run a count against your own filters. A count is free. The REST API and the MCP server sit on the same routes. Your code calls the API. Your agent calls the MCP server and gets the same answers. A session runs in one order. Count the audience first, because a count is free. Then search to look at a sample of the rows, or export to deliver the whole list. Enrich when you already hold a work email, phone number or LinkedIn URL. To collect a list, start an export. Do not page a search to build one. The paid plan costs $500 a month and has unlimited contact credits, under the Fair Usage Policy. One contact credit is one contact delivered. Premium units (engagers, verified emails) draw on a separate monthly premium credits allowance. Stored contact delivery uses contact credits. Phones come free in every row where available. ### Company fields Company facts describe the current employer. The Company API serves the full company record: `POST /companies/search` finds companies, `POST /companies/lookup` reads them by id, domain or LinkedIn URL, and `company_ids` on the contact filter finds their people. - `id` | CompanyId | optional. Our company id. Send it to the contact filter in company_ids. - `name` | string or null | optional. The company name. - `description` | string or null | optional. The company description. - `domain` | string or null | optional. The company domain. - `domains` | array of string | optional. The company domains. - `linkedin` | string or null | optional. The company linkedin. - `linkedin_urls` | array of string | optional. The company linkedin urls. - `social` | array of object | optional. The company social. - `network` | string or null | optional. The network. - `url` | string or null | optional. The url. - `username` | string or null | optional. The username. - `followers` | integer or null | optional. The followers. - `phone` | string or null | optional. The company phone. - `employee_count` | integer or null | optional. The company employee count. - `headcount_band` | CompanySize (slug) or null | optional. The company employee band. - `founded_year` | integer or null | optional. The company founded year. - `revenue_range` | CompanyRevenue (slug) or null | optional. The yearly revenue range of the company. Null when it is not known, which is the case for most small companies. - `employee_growth_pct` | integer or null | optional. The change in the employee count over the latest month, in percent. Negative when the company shrank. Null when unknown. - `hq_city` | string or null | optional. The company hq city. - `hq_country` | string or null | optional. The company hq country. - `locations` | array of object | optional. The company locations. - `name` | string or null | optional. The name. - `country` | string or null | optional. The country. - `is_primary` | boolean or null | optional. The is primary. - `street` | string or null | optional. The street address. Null when the location is not the address on record. - `postcode` | string or null | optional. The postal code. Null when the location is not the address on record. - `industries` | array of string | optional. The company industries. - `naics` | array of string | optional. The NAICS industry codes. - `specialties` | array of string | optional. What the company says it specializes in, in its own words. - `is_nonprofit` | boolean or null | optional. Whether the company is nonprofit. Null when unknown. - `is_government` | boolean or null | optional. Whether the company is government. Null when unknown. - `is_public` | boolean or null | optional. Whether the company is public. Null when unknown. - `total_funding_usd` | integer or null | optional. The company total funding usd. - `last_round_type` | FundingRoundType (slug) or null | optional. The latest funding round category. - `last_round_usd` | integer or null | optional. The company last round usd. - `last_round_date` | CalendarDate or null | optional. The latest funding date. - `investors` | array of string | optional. The lead investors of the company's funding rounds. - `investor_count` | integer or null | optional. The number of investors in the latest funding round. Null when unknown. - `monthly_visits` | integer or null | optional. The company monthly visits. - `sic` | array of string | optional. The SIC industry codes. - `last_updated` | YearMonth or null | optional. The month the company record changed. ### Base URLs | Surface | URL | | --- | --- | | REST API | `https://api.leadmcp.ai/v1` | | MCP server | `https://mcp.leadmcp.ai` | ### Legal and data These pages state how we license the data, what we hold, and how a person leaves it: [Terms](https://leadmcp.ai/terms), [Privacy](https://leadmcp.ai/privacy), [Opt out](https://leadmcp.ai/opt-out). ### Read next - [Quickstart](https://docs.leadmcp.ai/get-started): Get a key and make the first call. - [Worked examples](https://docs.leadmcp.ai/examples): Buyer briefs, and the calls an agent makes for each one. - [Authentication](https://docs.leadmcp.ai/authentication): Send the key on every request. - [The envelope](https://docs.leadmcp.ai/envelope): Every success answer has the same shape. - [Filters](https://docs.leadmcp.ai/filters): Describe the audience you want. - [Errors and warnings](https://docs.leadmcp.ai/errors): Read a failure and a partial answer. - [Exports](https://docs.leadmcp.ai/exports): Deliver a list as a file. - [Enrichment](https://docs.leadmcp.ai/enrichment): Match identifiers to stored rows. - [Limits](https://docs.leadmcp.ai/limits): The caps on contact credits, requests and time. - [All routes](https://docs.leadmcp.ai/routes): Every REST route, with what it does and what it costs. - [Get account](https://docs.leadmcp.ai/account/get-account): Returns the account, the plan and its row limits, the last four characters of the active key and the quota block. This route is the source of truth for the plan and its limits. Free. - [Get credit balances](https://docs.leadmcp.ai/credits/get-credits): Returns both meters, contact credits and premium credits, as separate objects. Premium credits pay for engagers and verified emails. Stored contact delivery uses contact credits. When a premium unit finds no room, the API answers `402 premium_credits.exhausted` with the reset time and an `upgrade_url` for the account owner. Free. - [Health check](https://docs.leadmcp.ai/health/get-health): Returns the service state, the build id and the month the current data was loaded. No row totals. Free. - [List columns](https://docs.leadmcp.ai/contacts/get-contacts-schema): Returns every public column with its format, and every filter key with its type and its value route. Top-level job fields (title, company, company_domain, function, seniority, role_since) describe the main role. experience lists the person's current roles only, main role first; the source has no past roles. A housekeeping date (`last_updated`, `last_refreshed`) carries the month. A role start date (`role_since`) carries the day, because a job-change signal is only worth acting on when you know the day it happened. Free. - [List column values](https://docs.leadmcp.ai/contacts/get-column-values): Returns the allowed slugs of one closed-list column, or the observed values of an open list column (city, region, skills). A column that identifies a person has no values route and is answered as not found. Free. - [Count matches](https://docs.leadmcp.ai/contacts/count-contacts): Counts the matching contacts. Free, and exact when it completes inside the 3 s budget. On zero rows the response carries a `no_rows` warning that names the keys applied. - [Find contacts](https://docs.leadmcp.ai/contacts/search-contacts): 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. - [Start an export](https://docs.leadmcp.ai/exports/start-export): Starts one file job for a filter and reserves the quota without charging it. The job counts the filter itself. `POST /contacts/count` is free and tells you the price before you pay it. One export runs per account: a second call while one runs returns 409 export.running with the running id. A job delivers at most 50,000 rows. Near a cap the job is truncated to the contact credits left under the cap that runs out first, and a warning gives the reason and the reset time. On the free plan the cap is the lifetime pool: the reason is `monthly_cap`, `resets_at` is `9999-12-31T23:59:59.000Z`, and the hint says the pool does not reset. When the count passes its budget, the server checks for one matching row. A filter that matches nothing still answers 400 export.no_matches, because that check finds no row, and no job starts. A started job then has rows_available null and a count_unavailable warning. - [Get an export](https://docs.leadmcp.ai/exports/get-export): Returns the status of one export job, then the download URL and the rows exported. Poll every 3 to 5 seconds until the status is completed. The URL is on our own domain and needs no key. The file is gzip-compressed, `{export_id}.csv.gz` or `{export_id}.jsonl.gz`: download it and gunzip it. The file and the URL live 7 days from the moment the job completes. After that we delete the file, and the download link answers 410 with the code `export.expired`. When max_rows_per_company keeps fewer rows than rows_to_export, the job returns an export_truncated warning, and only exported rows are charged. A job runs to completion: there is no cancel. - [Enrich contacts](https://docs.leadmcp.ai/enrichments/enrich-contacts): Takes up to 100 work emails, phone numbers or LinkedIn URLs and returns the matching stored rows. The result keeps the input order, one result per input. You pay one contact credit for each match, and a miss costs nothing. Matching is exact: an email is lowercased and a LinkedIn URL reduces to its slug. When two contacts share a work email, the later `last_updated` wins and a `duplicate_work_email` warning says so. When two contacts share a LinkedIn slug, the later `last_updated` wins and a `duplicate_linkedin` warning says so. Both duplicate warnings include `keys_applied` and a `hint`. Phones match mobile_phone or direct_phone. A number shared by multiple contacts returns matched false and reason ambiguous. It costs zero contact credits. An input that is no email, phone or LinkedIn profile URL returns reason invalid and costs zero contact credits. The forms `mailto:` and `Name ` are accepted. Send the full profile URL. To find a person by name, use `POST /contacts/search` with `domains` and a `where` leaf on `name` with `contains`. Each matched input costs one contact credit, including repeated inputs. - [list signals](https://docs.leadmcp.ai/signals/list-signals): Returns signals newest first, with a cursor for the next page. The page size is at most 500. source_type and source_id read one source; a cursor from a filtered page keeps its filter. Reading is free: a row's contact was charged once, when the row was collected. - [collect signals](https://docs.leadmcp.ai/signals/collect-signals): Collects one LinkedIn profile, post or company page once. Company followers have a minimum order of 100, accept max_followers, reserve exactly the requested count and always answer 202. Without max_followers, a company job asks for at most the premium credits available and adds a max_followers_missing warning. One credit per unique lead settles when results arrive. A page below the minimum answers `signals.below_minimum` (HTTP 400) with the follower count in error.details. With dry_run true, returns a free estimate without admission. Engagement collects support wait for estimates at most 500. The 202 answer carries job.id. Poll get_collect (GET /signals/collect/{id}) every 30 to 60 seconds until status is completed or failed. Then read the rows with list_signals (GET /signals): source_type collect and source_id the job id. - [get collect status](https://docs.leadmcp.ai/signals/get-collect): Returns the status of one collect job: queued, running, completed or failed. It also returns the leads and contacts so far, the premium credits held and charged, and the list_signals arguments that read the rows. After collect_signals, call it every 30 to 60 seconds until status is completed or failed. Then read the rows with GET /signals and the query in read.arguments. Free. - [create alert](https://docs.leadmcp.ai/signals/create-alert): An alert is a source of signals. Read them with `list_signals`. Starts a premium source for one premium credit per lead. Holds premium credits for the first job. With dry_run true, estimates without writing a row. - [list alerts](https://docs.leadmcp.ai/signals/list-alerts): An alert is a source of signals. Read them with `list_signals`. Lists sources with signal counts, lead counts and last job state. Free. - [get alert](https://docs.leadmcp.ai/signals/get-alert): An alert is a source of signals. Read them with `list_signals`. Returns one source and its last job. Free. - [update alert](https://docs.leadmcp.ai/signals/update-alert): An alert is a source of signals. Read them with `list_signals`. Changes whether a source is active and its cadence. Free. - [stop alert](https://docs.leadmcp.ai/signals/stop-alert): An alert is a source of signals. Read them with `list_signals`. Removes a source. Its signals stay available. Free. - [poll alert](https://docs.leadmcp.ai/signals/poll-alert): An alert is a source of signals. Read them with `list_signals`. Runs a poll now, holding premium credits for new leads. One credit per lead for premium signals. - [Send feedback](https://docs.leadmcp.ai/feedback/send-feedback): Reports a problem with one call to the LeadMCP team: a missing feature or filter, a bug, wrong data in a row, or an unclear error. Send the request_id of that call. The request_id must be from the last 7 days. The same request_id and category sent again returns the first feedback_id and does not count again. A key can send 20 reports a day, and an account can send 100 a day. A day is the last 24 hours. Free. - [Get the company schema](https://docs.leadmcp.ai/companies/get-companies-schema): Returns every public company column and every company filter key. Free. - [List company column values](https://docs.leadmcp.ai/companies/get-company-column-values): Returns the allowed values of one closed-list company column: hq_country, headcount_band, revenue_range, last_round_type or industries. Company names and domains have no values route and are answered as not found. Free. - [Count companies](https://docs.leadmcp.ai/companies/count-companies): Counts the matching companies. Free, and exact when it completes inside the 3 s budget. On zero rows the response carries a `no_rows` warning that names the keys applied. - [Search companies](https://docs.leadmcp.ai/companies/search-companies): Returns one page of company records for a company filter, in a fixed order. Each returned company costs one contact credit, through the same plan caps as contacts. Page with `cursor` until `page.next_cursor` is null. To find the people at these companies, send their ids to `POST /contacts/search` in the `company_ids` filter key. - [Look up companies](https://docs.leadmcp.ai/companies/lookup-companies): Takes up to 100 company ids, domains or company LinkedIn URLs and returns the matching company records. The result keeps the input order, one result per input. You pay one contact credit for each match, and a miss costs nothing. Each matched input costs one contact credit, including repeated inputs. A domain matches a company that carries it among its domains. A LinkedIn URL reduces to its company page. - [List API keys](https://docs.leadmcp.ai/keys/list-api-keys): Lists the account's active API key with its id, name, created_at and last_used_at. Never returns the key or its hash. Only an OAuth session may call it; a request signed with an API key answers 403 auth.oauth_required. Free. - [Create an API key](https://docs.leadmcp.ai/keys/create-api-key): Creates the account's one API key and returns it once. While a key is active the answer is 409 key.limit_reached: revoke it first. Only an OAuth session may call it; a request signed with an API key answers 403 auth.oauth_required. Free. - [Revoke an API key](https://docs.leadmcp.ai/keys/revoke-api-key): Revokes one API key at once: every call with it fails from then on. An unknown id, or the id of another account's key, answers 404 key.not_found. Only an OAuth session may call it; a request signed with an API key answers 403 auth.oauth_required. Free. - [Tools](https://docs.leadmcp.ai/mcp/tools): What your agent can do with the MCP server. - [Connect a client](https://docs.leadmcp.ai/mcp/connect): Add the server to your agent. - [Changelog](https://docs.leadmcp.ai/changelog): What changed in the contract. ## Quickstart Get a key and make the first call. Page: https://docs.leadmcp.ai/get-started ### The steps 1. **Sign up.** Sign up with your Google Workspace account. Email codes sign in existing accounts. No card is needed. 2. **Get 1,000 free contact credits.** A new account receives 1,000 contact credits to spend. One delivered contact row is one unit. Counts are always free. 3. **Create your key.** The app issues one live key with the prefix lm_live_. The key is shown once. Store it. 4. **Connect your client.** Add the MCP server to your agent, or call the REST API with the key. The table below gives each client. 5. **Watch your usage.** Every response carries a quota block. The dashboard shows the contact credits used and the contact credits left, per day and per month. 6. **Pay $500 a month.** One paid plan, one price. Pay in the app when the free contact credits run out. 7. **Run on the full plan.** The paid plan has unlimited contact credits, under the Fair Usage Policy, with no overage. Premium units draw on a monthly premium credits allowance; there are no credit packs to buy. [Read the Fair Usage Policy](https://leadmcp.ai/fair-usage). ### Your first call A count is free. Send a filter and read the number. A live key starts with `lm_live_`. ```bash curl -sS -X POST "https://api.leadmcp.ai/v1/contacts/count" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "LeadMCP-Version: 2026-09-19" \ -H "Content-Type: application/json" \ -d '{"filter":{"countries":["us"],"seniority":["vp"],"functions":["marketing_and_advertising"],"has_work_email":true}}' ``` ## Worked examples Buyer briefs, and the calls an agent makes for each one. Page: https://docs.leadmcp.ai/examples Each brief below is one sentence a buyer says. Under it are the calls an agent makes, in order, with the body it sends and the shape that comes back. ### A country, a seniority and a function Brief: We sell to consulting leaders in the United States: size the audience, then show me a few of them. `count_matches` (`POST /contacts/count`) ```json { "filter": { "countries": [ "us" ], "seniority": [ "c_level", "vp" ], "functions": [ "general_management" ], "has_work_email": true } } ``` An envelope whose `data.count` is a whole number, or null with a `count_unavailable` warning. The call is free. `find_contacts` (`POST /contacts/search`) ```json { "filter": { "countries": [ "us" ], "seniority": [ "c_level", "vp" ], "functions": [ "general_management" ], "has_work_email": true }, "columns": [ "id", "name", "title", "company_domain", "country", "seniority" ], "limit": 10 } ``` Up to ten rows in `data`, a `page` block with `returned`, `total` and `next_cursor`, and a `quota` block that is ten contact credits lighter. ### A list of target accounts Brief: Show me everyone we can reach by email at our three named target accounts. `count_matches` (`POST /contacts/count`) ```json { "filter": { "domains": [ "acme.example.com", "northwind.example.net", "globex.example.org" ], "has_work_email": true } } ``` One number over all three domains, because a list key is OR inside one key. `find_contacts` (`POST /contacts/search`) ```json { "filter": { "domains": [ "acme.example.com", "northwind.example.net", "globex.example.org" ], "has_work_email": true }, "columns": [ "id", "name", "title", "company", "company_domain", "work_email" ], "limit": 25 } ``` Rows from any of the three domains, mixed. On zero rows a `no_rows` warning names the keys that were applied. ### Decision makers at one company Brief: Find the people who decide at one named account, with a work email. `count_matches` (`POST /contacts/count`) ```json { "filter": { "domains": [ "acme.example.com" ], "seniority": [ "c_level", "vp", "director" ], "has_work_email": true } } ``` The number of leaders at the one domain, free. Add `functions` to keep only the buyer's department. `find_contacts` (`POST /contacts/search`) ```json { "filter": { "domains": [ "acme.example.com" ], "seniority": [ "c_level", "vp", "director" ], "has_work_email": true }, "columns": [ "id", "name", "title", "company_domain", "seniority", "work_email" ], "limit": 10 } ``` The leaders at that domain, one call for the whole account. On zero rows a `no_rows` warning names the keys that were applied. ### Decision makers across many accounts Brief: Give me up to three leaders at each of our target accounts, in one list. `count_matches` (`POST /contacts/count`) ```json { "filter": { "domains": [ "acme.example.com", "northwind.example.net", "globex.example.org", "initech.example.com" ], "seniority": [ "c_level", "vp", "director" ], "has_work_email": true } } ``` One number over every domain, free. The whole list goes in one call, never one call for each domain. One call takes up to 1,000 domains. `find_contacts` (`POST /contacts/search`) ```json { "filter": { "domains": [ "acme.example.com", "northwind.example.net", "globex.example.org", "initech.example.com" ], "seniority": [ "c_level", "vp", "director" ], "has_work_email": true }, "columns": [ "id", "name", "title", "company", "company_domain", "seniority" ], "max_rows_per_company": 3, "limit": 25 } ``` A page with at most three rows for each `company_domain`, so no single account fills the list. `start_export` takes the same filter and cap to deliver a file. ### The colleagues of a contact Brief: I have one contact at an account: find the other leaders there who would also buy. `count_matches` (`POST /contacts/count`) ```json { "filter": { "domains": [ "acme.example.com" ], "seniority": [ "c_level", "vp", "director" ], "functions": [ "sales_and_bizdev", "marketing_and_advertising" ] } } ``` The number of leaders at the contact's `company_domain` in the functions that buy the offer, free. `find_contacts` (`POST /contacts/search`) ```json { "filter": { "domains": [ "acme.example.com" ], "seniority": [ "c_level", "vp", "director" ], "functions": [ "sales_and_bizdev", "marketing_and_advertising" ] }, "exclude_ids": [ "per_01J9Z3K7Q8M2N4P6R8T0V2X4Y6" ], "columns": [ "id", "name", "title", "company_domain", "seniority", "function" ], "limit": 10 } ``` The leaders at that domain without the contact you started from. `exclude_ids` sits beside the filter and works on count, search and export. ### Exclude a competitor and an industry Brief: US directors and managers, but not at our competitor and not in banking. `get_values` (`GET /contacts/schema/company_industries/values?q=bank&limit=5`) The matching industry slugs with their labels, so the agent sends a slug the contract knows. Free. `count_matches` (`POST /contacts/count`) ```json { "filter": { "countries": [ "us" ], "seniority": [ "director", "manager" ], "exclude_domains": [ "competitor.example.com" ], "exclude_industries": [ "banking_and_lending" ], "exclude_functions": [ "human_resources" ] } } ``` The count after the three exclusions. Each `exclude_` key is the NOT of its twin. `find_contacts` (`POST /contacts/search`) ```json { "filter": { "countries": [ "us" ], "seniority": [ "director", "manager" ], "exclude_domains": [ "competitor.example.com" ], "exclude_industries": [ "banking_and_lending" ], "exclude_functions": [ "human_resources" ] }, "columns": [ "id", "name", "title", "company_domain", "seniority" ], "limit": 10 } ``` A page with no row from the excluded domain, industry or function. ### A where clause with not_in Brief: Engineering leaders in the United States without either of two named skills. `count_matches` (`POST /contacts/count`) ```json { "filter": { "countries": [ "us" ], "functions": [ "engineering" ], "seniority": [ "c_level", "vp", "director" ], "where": { "field": "skills", "op": "not_in", "value": [ "recruiting", "accounting" ] } } } ``` The count with the tree applied. There is no `exclude_skills` key: a negation outside the six flat ones goes through `where` with `not_in`. `find_contacts` (`POST /contacts/search`) ```json { "filter": { "countries": [ "us" ], "functions": [ "engineering" ], "seniority": [ "c_level", "vp", "director" ], "where": { "field": "skills", "op": "not_in", "value": [ "recruiting", "accounting" ] } }, "columns": [ "id", "name", "title", "company_domain", "seniority" ], "limit": 10 } ``` Rows whose skills contain neither value. A tree over 32 predicates answers 422 `filter.rejected`. ### A headcount band and a funding round Brief: C-level people at US companies of 201 to 500 staff that raised a Series B. `get_values` (`GET /contacts/schema/company_size/values?q=201&limit=10`) The headcount bands that match, so the agent sends a band the contract knows rather than two numbers. The funding round slugs are a frozen list on the Filters page. Free. `count_matches` (`POST /contacts/count`) ```json { "filter": { "countries": [ "us" ], "seniority": [ "c_level" ], "company_size": [ "201-500" ], "last_round_types": [ "series_b" ] } } ``` The count for the band and the round together. A company with no funding on record is null, never zero, so it does not match. `find_contacts` (`POST /contacts/search`) ```json { "filter": { "countries": [ "us" ], "seniority": [ "c_level" ], "company_size": [ "201-500" ], "last_round_types": [ "series_b" ] }, "columns": [ "id", "name", "title", "company", "company_domain" ], "limit": 10 } ``` A page, or an empty `data` with a `no_rows` warning that lists the four keys and asks for one to be removed. ### A signal: a recent job change Brief: US sales leaders who started a new job recently, because they buy in their first quarter. `count_matches` (`POST /contacts/count`) ```json { "filter": { "countries": [ "us" ], "functions": [ "sales_and_bizdev" ], "where": { "field": "signals", "op": "in", "value": [ "recent_job_change" ] } } } ``` The count for the signal. Signals have no flat key: they are filtered with `in` or `not_in` on `signals` inside `where`. `find_contacts` (`POST /contacts/search`) ```json { "filter": { "countries": [ "us" ], "functions": [ "sales_and_bizdev" ], "where": { "field": "signals", "op": "in", "value": [ "recent_job_change" ] } }, "columns": [ "id", "name", "title", "company_domain", "signals", "role_since" ], "limit": 10 } ``` Rows that carry the signal, with `signals` and `role_since` on the row so the agent can read the date itself. ### At most two people per company Brief: A software list that is not all from one account: at most two people per company. `count_matches` (`POST /contacts/count`) ```json { "filter": { "industries": [ "software_development" ], "seniority": [ "c_level", "vp", "director" ], "has_work_email": true } } ``` The count of everyone who matches. The count does not apply the per-company cap: the cap belongs to the page. `find_contacts` (`POST /contacts/search`) ```json { "filter": { "industries": [ "software_development" ], "seniority": [ "c_level", "vp", "director" ], "has_work_email": true }, "columns": [ "id", "name", "title", "company", "company_domain" ], "max_rows_per_company": 2, "limit": 25 } ``` A page holding at most two rows for each `company_domain`. The cap applies to each page, not to the whole search. ### The second page, by cursor Brief: Read a US audience page by page, and never show me the same person twice. `find_contacts` (`POST /contacts/search`) ```json { "filter": { "countries": [ "us" ], "has_work_email": true }, "columns": [ "id", "name", "title", "company_domain", "country" ], "limit": 5 } ``` Five rows and a `page.next_cursor`. The cursor is opaque and it pins the filter, the sort tuple and the build. `find_contacts` (`POST /contacts/search`) ```json { "filter": { "countries": [ "us" ], "has_work_email": true }, "columns": [ "id", "name", "title", "company_domain", "country" ], "limit": 5 } ``` The next five rows, with no id from the first page. Read pages until `next_cursor` is null. A cursor from an older build answers 400 `cursor.expired`. ### An export that gets truncated Brief: Write me a file of US contacts with a work email, but cap this job at 25 rows. `count_matches` (`POST /contacts/count`) ```json { "filter": { "countries": [ "us" ], "has_work_email": true } } ``` The size of the audience, free, before any row is spent. `start_export` (`POST /exports`) ```json { "filter": { "countries": [ "us" ], "has_work_email": true }, "columns": [ "id", "name", "work_email", "company_domain", "country", "seniority" ], "format": "csv", "max_rows": 25 } ``` 202 with `rows_available`, `rows_to_export: 25`, `truncated: true` and `truncation_reason: "max_rows"`, plus an `export_truncated` warning. The contact credits are held, not charged. `get_export` (`GET /exports/{export_id}`) The job status. Poll until it is `completed`, then read `url`: a signed link on our own domain that lives 7 days from completion. An expired or gone file answers 410 with the code `export.expired`. The contact credits are charged on completion. ### Enrichment by LinkedIn URL Brief: Fill in the record for a LinkedIn profile I already have. `enrich_contact` (`POST /enrichments`) ```json { "inputs": [ "https://www.linkedin.com/in/sample-dana-reyes-1ifdfs" ], "columns": [ "id", "name", "title", "company", "company_domain", "work_email", "seniority" ] } ``` One result for each input, in the order they were sent, each with `input`, `matched`, `off_domain` and `contact`. A hit costs one contact credit and a miss costs nothing. v1 answers from our own store; `refresh` answers 400 `enrich.mode_unavailable`. `enrich_contact` (`POST /enrichments`) ```json { "inputs": [ "https://www.linkedin.com/in/sample-dana-reyes-1ifdfs", "dana.reyes@example.com" ], "company_domain": "example.com", "columns": [ "id", "name", "title", "company", "company_domain", "work_email" ] } ``` The same call scoped to the account the people came from. A person we hold at another employer, or with no employer domain, answers `matched: false` with `off_domain: true`, costs nothing, and is listed on one `off_domain` warning. So a list sourced from one account never comes back with an email at a previous employer, an agency, or a school. ## Authentication Send the key on every request. Page: https://docs.leadmcp.ai/authentication An API key on the Authorization header: `Authorization: Bearer lm_live_...`. A key is shown once and stored hashed. There is no credential in a path and none in a query string. Web connectors register dynamically and run the OAuth 2.1 authorization code flow with PKCE. Command line clients run the device authorization grant. Both send the resulting access token on the same Authorization header. ### Headers | Header | Value | Notes | | --- | --- | --- | | `Authorization` | `Bearer lm_live_YOUR_API_KEY` | An API key with the prefix lm_live_, or an OAuth 2.1 access token. | | `LeadMCP-Version` | `2026-09-19` | The contract date the caller was written against. The response echoes the value. The current value is 2026-09-19. | ## The envelope Every success answer has the same shape. Page: https://docs.leadmcp.ai/envelope The success envelope. Every success response carries it. ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": [], "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": [] } ``` ### Envelope fields - `ok` | boolean | required. True on every success response. - `request_id` | string | required. The id of this request. Send the request_id to support@leadmcp.ai. - `release` | string | required. The build id that served this response. - `data` | any | optional. The payload of the route. The shape depends on the route. - `page` | Page | optional. The paging block. Present on a paged route. - `returned` | integer | required. The number of rows in this page. - `limit` | integer | required. The page size the request asked for. - `total` | integer or null | required. The total number of matching rows, or null when the count did not finish inside its budget. - `next_cursor` | string or null | required. The cursor for the next page, or null on the last page. - `quota` | Quota | required. The quota block. Every response carries it. It names the plan and the usage; read the plan limits themselves from `GET /account`. Field names are frozen. - `plan` | string | required. The plan slug this account is on. It names the caps; read the numbers themselves from `GET /account`. - `contact_credits_used` | integer | required. The contact credits this request spent. A count, a schema read or a miss spends zero. - `month_remaining` | integer | required. The contact credits left in the current billing month. A paid plan's month starts on its billing date each month; without a subscription it is the UTC calendar month. On the free plan, the contact credits left in the lifetime pool; the pool has no window. - `today_remaining` | integer | required. The contact credits left in the current UTC day. On the free plan, the same number as `month_remaining`. - `binding` | enum | required. The window that runs out first at the current pace. On the free plan, always `month`. Values: `month`, `day`. - `resets_at` | string | required. The RFC 3339 UTC time at which the binding window resets. On the free plan the pool never resets, and the field is `9999-12-31T23:59:59.000Z`. - `warnings` | array of Warning | required. Non fatal notes about this response. Empty on a clean call. - `code` | enum | required. The warning code. Values: `count_unavailable`, `no_rows`, `export_truncated`, `duplicate_work_email`, `duplicate_linkedin`, `off_domain`, `deprecated`, `max_followers_missing`. - `message` | string | required. One sentence a developer can read. - `hint` | string | optional. The suggested next action, for example to remove one key and count again. - `keys_applied` | array of string | optional. On no_rows, the filter keys that were applied. On off_domain, the inputs we hold at another employer. - `resets_at` | string | optional. On export_truncated, present only when a contact credit cap cut the job: the RFC 3339 UTC time at which the cap that cut the job resets. On the free plan the pool never resets, and the field is `9999-12-31T23:59:59.000Z`; the hint then says the pool is spent and points to the upgrade. get_export can also return export_truncated, with no resets_at, when max_rows_per_company kept fewer rows than rows_to_export; only exported rows are charged. ### page The paging block. Present on a paged route. - `returned` | integer | required. The number of rows in this page. - `limit` | integer | required. The page size the request asked for. - `total` | integer or null | required. The total number of matching rows, or null when the count did not finish inside its budget. - `next_cursor` | string or null | required. The cursor for the next page, or null on the last page. ### quota The quota block. Every response carries it. It names the plan and the usage; read the plan limits themselves from `GET /account`. Field names are frozen. - `plan` | string | required. The plan slug this account is on. It names the caps; read the numbers themselves from `GET /account`. - `contact_credits_used` | integer | required. The contact credits this request spent. A count, a schema read or a miss spends zero. - `month_remaining` | integer | required. The contact credits left in the current billing month. A paid plan's month starts on its billing date each month; without a subscription it is the UTC calendar month. On the free plan, the contact credits left in the lifetime pool; the pool has no window. - `today_remaining` | integer | required. The contact credits left in the current UTC day. On the free plan, the same number as `month_remaining`. - `binding` | enum | required. The window that runs out first at the current pace. On the free plan, always `month`. Values: `month`, `day`. - `resets_at` | string | required. The RFC 3339 UTC time at which the binding window resets. On the free plan the pool never resets, and the field is `9999-12-31T23:59:59.000Z`. ### warnings One non fatal note on a successful response. - `code` | enum | required. The warning code. Values: `count_unavailable`, `no_rows`, `export_truncated`, `duplicate_work_email`, `duplicate_linkedin`, `off_domain`, `deprecated`, `max_followers_missing`. - `message` | string | required. One sentence a developer can read. - `hint` | string | optional. The suggested next action, for example to remove one key and count again. - `keys_applied` | array of string | optional. On no_rows, the filter keys that were applied. On off_domain, the inputs we hold at another employer. - `resets_at` | string | optional. On export_truncated, present only when a contact credit cap cut the job: the RFC 3339 UTC time at which the cap that cut the job resets. On the free plan the pool never resets, and the field is `9999-12-31T23:59:59.000Z`; the hint then says the pool is spent and points to the upgrade. get_export can also return export_truncated, with no resets_at, when max_rows_per_company kept fewer rows than rows_to_export; only exported rows are charged. ## Filters Describe the audience you want. Page: https://docs.leadmcp.ai/filters The named filter keys, listed in our column order rather than in include and exclude pairs. A list key is OR inside and AND across keys. An `exclude_*` key is the NOT of its twin. A range key comes as a `_min` / `_max` (inclusive) or `_after` / `_before` (exclusive) pair. A null column never matches a value key. Flat keys cover includes, ranges and the six common negations. Everything else goes through `where` with `not_in`. ### Named keys | Key | Type | Description | | --- | --- | --- | | `countries` | array of Country | Contact countries to include. Send ISO-2 codes such as us, or English names such as Germany. | | `exclude_countries` | array of Country | Contact countries to exclude. Send ISO-2 codes such as us, or English names such as Germany. | | `has_linkedin` | boolean | True keeps only contacts that carry a LinkedIn URL. | | `has_work_email` | boolean | True keeps only contacts that carry a work email. | | `has_phone` | boolean | True matches contacts with a mobile or direct phone. False matches contacts without either. | | `include_secondary_roles` | boolean | 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. | | `title_query` | string or array of string | 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 | Job functions to include. | | `exclude_functions` | array of JobFunction | Job functions to exclude. | | `seniority` | array of Seniority | Seniority rungs to include. | | `exclude_seniority` | array of Seniority | Seniority rungs to exclude. | | `domains` | array of string | Current employer domains. A full URL reduces to a bare domain on input. | | `exclude_domains` | array of string | Current employer domains to exclude. | | `company_names` | array of string | 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 | 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 | Company employee bands to include. | | `company_revenue` | array of CompanyRevenue | Company revenue ranges to include. Only companies with a known revenue range match; most small companies have none, so this key leaves them out. | | `industries` | array of Industry | 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 | 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 | 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 | 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 | The smallest company employee count, inclusive. | | `employee_count_max` | integer | The largest company employee count, inclusive. | | `founded_year_min` | integer | The earliest company founding year, inclusive. | | `founded_year_max` | integer | The latest company founding year, inclusive. | | `company_hq_countries` | array of Country | Company headquarters countries to include. Send ISO-2 codes such as us, or English names such as Germany. | | `last_round_after` | YearMonth | The latest financing event is after this month, exclusive. | | `last_round_before` | YearMonth | The latest financing event is before this month, exclusive. | | `last_round_types` | array of FundingRoundType | Types of the latest financing event to include. | | `total_funding_min` | integer | The smallest total funding in US dollars, inclusive. Missing funding is null and never matches. | | `total_funding_max` | integer | The largest total funding in US dollars, inclusive. | | `company_ids` | array of CompanyId | 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. | ### Closed lists - **Seniority** (frozen): `c_level`, `vp`, `director`, `manager`, `senior_ic`, `entry`, `founder_or_partner`, `unknown`. The seniority rung of the contact. The list is frozen. A C-level person whose title names a founder, owner or partner role is founder_or_partner; add it to reach founder CEOs. - **JobFunction** (frozen): `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`. The job function of the contact's main job. The list holds 21 values. `operations`: running the business day to day: operations, facilities, administration. `general_management`: business leadership, general management, strategy and consulting. `sales_and_bizdev`: selling, account management, partnerships and business development. `information_technology`: IT, systems, infrastructure, data and support for technology. `education`: teaching, instruction, academic and school roles. `healthcare_and_social_services`: clinical, nursing, care and social service roles. `finance_and_accounting`: finance, accounting, treasury and audit. `manufacturing_and_production`: making things: production, plant and manufacturing roles. `engineering`: software, hardware and other engineering roles. `customer_service`: customer service, support and customer success. `government_and_public_safety`: public administration, government, police, fire and military. `marketing_and_advertising`: marketing, advertising, brand and growth. `human_resources`: recruiting, people operations, compensation and training. `arts_and_creative`: design, art, culture and other creative work. `construction_and_trades`: construction, building and skilled trades. `legal`: lawyers, legal counsel, compliance and regulatory work. `supply_chain_and_logistics`: supply chain, logistics, transport and warehousing. `procurement`: purchasing and procurement. `writing_and_editorial`: writing, editing, journalism and publishing. `research_and_science`: research and development, laboratory and scientific roles. `other`: a function the source does not name. - **EmailStatus** (frozen): `unknown`, `valid`, `invalid`, `catch_all`. The work email status comes from our data source; verified_at is the month the source last verified the address. The list is frozen. - **CompanySize** (frozen): `1-10`, `11-50`, `51-200`, `201-500`, `501-1K`, `1K-5K`, `5K-10K`, `10K+`. The employee band of the company. The list is frozen. - **FundingRoundType** (frozen): `pre_seed`, `seed`, `series_a`, `series_b`, `series_c_plus`, `private_equity`, `debt`, `other`. The eight funding categories. Series C and later rounds use `series_c_plus`. - **Signal** (frozen): `one_year_at_company`, `new_in_role_90d`, `recent_job_change`, `founder`, `at_public_company`. The five contact signals. One year at the current company, a new role within 90 days, a recent employer change, a founder role, or employment at a public company. Filter on `signals` with `in` or `not_in` inside `where`. - **Country** (frozen): an open list. A country as a lowercase ISO-2 slug, for example `us` or `de`. Aliases normalize on input: `US`, `USA` and `United States` all become `us`. Unknown values return 400 filter.unknown_value with allowed values and a hint to GET /contacts/schema/country/values. - **Industry**: read the values from `/contacts/schema/industries/values`. A company industry slug. Read the allowed values from `GET /contacts/schema/industries/values`. ### The where tree 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`. One node of the condition tree: a group (`and`, `or`, `not`) or a leaf. The tree is at most 4 deep, holds at most 25 leaves, and takes at most 50 values in one `in`. An `and` or `or` group needs at least one child. A tree over 32 predicates, or a leaf with a leading wildcard, is rejected with 422 filter.rejected. A refused operator, value type or shape answers 422 filter.rejected and names what the column takes. Fields resolve against `GET /contacts/schema`, case-insensitively, and an unknown field returns 400 filter.unknown_key. A closed-list value in a where leaf folds its aliases, as a named key does, and an unknown value answers 400 filter.unknown_value. Operators: `eq`, `neq`, `in`, `not_in`, `gt`, `gte`, `lt`, `lte`, `contains`, `exists`. ```json { "where": { "and": [ { "field": "country", "op": "in", "value": [ "us", "ca" ] }, { "or": [ { "field": "title", "op": "contains", "value": "demand generation" }, { "field": "seniority", "op": "eq", "value": "vp" } ] }, { "not": { "field": "company_domain", "op": "eq", "value": "competitor.example" } } ] } } ``` Skills carry no flat negation. Exclude them with `not_in` inside `where`. ```json { "where": { "field": "skills", "op": "not_in", "value": [ "cold calling" ] } } ``` ### Paging a search Search answers with one page. Pass `page.next_cursor` back unchanged on the next call, and stop when it is null. ```javascript const filter = { countries: ["us"], seniority: ["vp"] }; const rows = []; let cursor = null; do { const response = await fetch("https://api.leadmcp.ai/v1/contacts/search", { method: "POST", headers: { Authorization: "Bearer lm_live_YOUR_API_KEY", "Content-Type": "application/json" }, body: JSON.stringify({ filter, limit: 50, cursor }), }); const body = await response.json(); rows.push(...body.data); cursor = body.page.next_cursor; } while (cursor !== null); ``` To deliver a list, start an export instead of paging search. ### Aliases - A full URL reduces to a bare domain on input. - Aliases normalize on input: `US`, `USA` and `United States` all become `us`. - An empty normalized name returns 400 request.invalid. - Matching is exact: an email is lowercased and a LinkedIn URL reduces to its slug. - The match is exact on the stored value, which is lowercase, and the input is lowercased. ### Stated limits - A history key matches any entry on the record. The schools key matches any school. The skills key matches any skill. - Past employers are not in the data: experience lists current roles only, main role first. Filter titles with title_query. - A null column never matches a value key. Use has_work_email, has_phone and has_linkedin to ask for presence. - Signals have no flat key. Filter them with `in` or `not_in` on `signals` inside `where`. ## Errors and warnings Read a failure and a partial answer. Page: https://docs.leadmcp.ai/errors ### The error envelope The error envelope. Every failure carries it, and the shape is frozen. Every failure also carries a `request_id`. Send the request_id to support@leadmcp.ai. ```json { "ok": false, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "error": { "code": "filter.unknown_key", "message": "Unknown filter key 'country'.", "hint": "Use countries. Call GET /contacts/schema for every key.", "field": "filter.country" } } ``` - `ok` | boolean | required. False on every failure response. - `request_id` | string | required. The id of this request. Send the request_id to support@leadmcp.ai. - `release` | string | optional. The build id that served this response, when one served it. - `error` | object | required. The failure itself. - `code` | ErrorCode (slug) | required. The closed, published error code list. Codes are dotted `noun.reason`. We add codes and never repurpose one. `account.suspended` (403): an admin suspended the account, or a payment failed. After an admin suspension, contact support@leadmcp.ai. After a failed payment, update the card in Billing; spending calls are refused until a payment succeeds. Values: `request.invalid`, `filter.unknown_key`, `filter.unknown_value`, `filter.rejected`, `cursor.expired`, `enrich.mode_unavailable`, `enrich.too_many_inputs`, `auth.invalid_key`, `account.suspended`, `contact_credits.exhausted`, `premium_credits.exhausted`, `column.not_found`, `export.not_found`, `alert.not_found`, `alert.busy`, `collect.not_found`, `plan.required`, `export.expired`, `export.running`, `export.no_matches`, `rate.limited`, `data.unavailable`, `query.timeout`, `signals.below_minimum`, `alerts.limit_reached`, `auth.oauth_required`, `key.limit_reached`, `key.not_found`. - `message` | string | required. One sentence that states what failed. - `hint` | string | optional. The fix, named. On a closed list value it prints every allowed slug. - `field` | string | optional. The dotted path of the offending field, for example `filter.country`. - `details` | ContactCreditsExhaustedDetails or PremiumCreditsExhaustedDetails or SignalsBelowMinimumDetails | optional. Structured error details: contact_credits.exhausted carries ContactCreditsExhaustedDetails, premium_credits.exhausted carries PremiumCreditsExhaustedDetails, and signals.below_minimum carries SignalsBelowMinimumDetails; hint and field stay beside details on error. - `ContactCreditsExhaustedDetails` | ContactCreditsExhaustedDetails | optional. Details for contact_credits.exhausted. - `binding` | enum | required. The contact credit window whose cap is spent. Values: `day`, `month`. - `resets_at` | string | required. The RFC 3339 reset time, or 9999-12-31T23:59:59.000Z for the free plan's lifetime pool. - `PremiumCreditsExhaustedDetails` | PremiumCreditsExhaustedDetails | optional. Details for premium_credits.exhausted. - `required` | integer | required. The premium credits required for this request. - `available` | integer | required. The premium credits still available this month. - `allowance` | integer | required. The monthly premium credit allowance on the account. - `resets_at` | string | required. The RFC 3339 time when the premium credit allowance resets. - `upgrade_url` | string | required. The billing URL where the account owner can raise the premium credit tier. - `SignalsBelowMinimumDetails` | SignalsBelowMinimumDetails | optional. Details for signals.below_minimum. - `followers` | integer | required. The number of followers on the requested company page. - `quota` | Quota | optional. present when the account is known; absent for failures before the account is identified (for example a missing or invalid key) - `plan` | string | required. The plan slug this account is on. It names the caps; read the numbers themselves from `GET /account`. - `contact_credits_used` | integer | required. The contact credits this request spent. A count, a schema read or a miss spends zero. - `month_remaining` | integer | required. The contact credits left in the current billing month. A paid plan's month starts on its billing date each month; without a subscription it is the UTC calendar month. On the free plan, the contact credits left in the lifetime pool; the pool has no window. - `today_remaining` | integer | required. The contact credits left in the current UTC day. On the free plan, the same number as `month_remaining`. - `binding` | enum | required. The window that runs out first at the current pace. On the free plan, always `month`. Values: `month`, `day`. - `resets_at` | string | required. The RFC 3339 UTC time at which the binding window resets. On the free plan the pool never resets, and the field is `9999-12-31T23:59:59.000Z`. ### Error codes | Code | Status | Meaning | | --- | --- | --- | | `request.invalid` | 400 | An empty normalized name returns 400 request.invalid. | | `filter.unknown_key` | 400 | Fields resolve against `GET /contacts/schema`, case-insensitively, and an unknown field returns 400 filter.unknown_key. | | `filter.unknown_value` | 400 | Unknown values return 400 filter.unknown_value with allowed values and a hint to GET /contacts/schema/country/values. | | `filter.rejected` | 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. | | `cursor.expired` | 400 | A cursor minted on an earlier build returns 400 cursor.expired, and the caller starts the search again. | | `enrich.mode_unavailable` | 400 | Sending true returns 400 enrich.mode_unavailable. | | `enrich.too_many_inputs` | 400 | More than 100 enrichment inputs return 400 enrich.too_many_inputs with the hint: Send at most 100 inputs per call; split larger lists into batches. | | `auth.invalid_key` | 401 | The credential is missing, unknown or revoked. | | `account.suspended` | 403 | `account.suspended` (403): an admin suspended the account, or a payment failed. | | `contact_credits.exhausted` | 402 | The daily or the monthly contact credit cap is spent, or the free plan's lifetime pool is. | | `premium_credits.exhausted` | 402 | Details for premium_credits.exhausted. | | `column.not_found` | 404 | The id or the column does not exist, or the row was opted out. | | `export.not_found` | 404 | The id or the column does not exist, or the row was opted out. | | `alert.not_found` | 404 | Unknown alert id or an alert belonging to another account. | | `alert.busy` | 409 | The alert already has a queued or running job. | | `collect.not_found` | 404 | Unknown collect job id, an alert run, or a job of another account. | | `plan.required` | 403 | Creating alerts requires a paid plan (plan.required). | | `export.expired` | 410 | The export file has expired or is no longer available. | | `export.running` | 409 | One export already runs on this account. | | `export.no_matches` | 400 | A filter that matches nothing is refused with 400 export.no_matches, free, and no job is created. | | `rate.limited` | 429 | The key passed 300 requests a minute or 2 concurrent requests. | | `data.unavailable` | 503 | The data is unavailable for a short time. | | `query.timeout` | 504 | The query passed its time budget and was stopped. | | `signals.below_minimum` | 400 | Details for signals.below_minimum. | | `alerts.limit_reached` | 403 | Accounts may have at most 100 active alerts (alerts.limit_reached). | | `auth.oauth_required` | 403 | The request is signed with an API key. | | `key.limit_reached` | 409 | The account already has its one active API key. | | `key.not_found` | 404 | No active API key with this id on this account. | ### Warning codes A warning rides on a success. The status stays 200. | Code | Meaning | | --- | --- | | `count_unavailable` | A null count carries a count_unavailable warning. | | `no_rows` | On zero rows the response carries a `no_rows` warning that names the keys applied. | | `export_truncated` | The export_truncated warning names the reason and the reset time. | | `duplicate_work_email` | When two contacts share a work email, the later `last_updated` wins and a `duplicate_work_email` warning says so. | | `duplicate_linkedin` | When two contacts share a LinkedIn slug, the later `last_updated` wins and a `duplicate_linkedin` warning says so. | | `off_domain` | A person we hold at another employer, or with no employer domain, answers `matched: false` with `off_domain: true`, costs nothing, and is listed on an `off_domain` warning. | | `deprecated` | A field, a key or a code is on its way out. It is removed no earlier than six months after this warning first appears. | | `max_followers_missing` | Without max_followers, a company job asks for at most the premium credits available and adds a max_followers_missing warning. | ## Exports Deliver a list as a file. Page: https://docs.leadmcp.ai/exports Starts one file job for a filter and reserves the quota without charging it. The job counts the filter itself. `POST /contacts/count` is free and tells you the price before you pay it. One export runs per account: a second call while one runs returns 409 export.running with the running id. A job delivers at most 50,000 rows. Near a cap the job is truncated to the contact credits left under the cap that runs out first, and a warning gives the reason and the reset time. On the free plan the cap is the lifetime pool: the reason is `monthly_cap`, `resets_at` is `9999-12-31T23:59:59.000Z`, and the hint says the pool does not reset. When the count passes its budget, the server checks for one matching row. A filter that matches nothing still answers 400 export.no_matches, because that check finds no row, and no job starts. A started job then has rows_available null and a count_unavailable warning. ### The file One export job. The file is UTF-8. A CSV file is RFC 4180: the header row equals the API column names, `id` comes first, a null is empty, a date is RFC 3339 UTC, and the name is `{export_id}.csv.gz`. CSV keeps arrays and objects as JSON text in one cell. A JSONL file uses the same names, one object a line. A JSONL line holds native JSON values, so an array or an object stays an array or an object. Formats: `csv` or `jsonl`, gzipped. One job writes at most 50,000 rows. A signed link lives 24 hours. ### The job Statuses: `queued`, `running`, `completed`, `failed`. Truncation reasons: `daily_cap`, `monthly_cap`, `job_row_limit`, `max_rows`. True when max_rows, the 50,000 row job limit or a contact credit cap made rows_to_export smaller than the rows the filter gives. A per-company cap alone does not set it. The export_truncated warning names the reason and the reset time. ## Enrichment Match identifiers to stored rows. Page: https://docs.leadmcp.ai/enrichment Takes up to 100 work emails, phone numbers or LinkedIn URLs and returns the matching stored rows. The result keeps the input order, one result per input. You pay one contact credit for each match, and a miss costs nothing. Matching is exact: an email is lowercased and a LinkedIn URL reduces to its slug. When two contacts share a work email, the later `last_updated` wins and a `duplicate_work_email` warning says so. When two contacts share a LinkedIn slug, the later `last_updated` wins and a `duplicate_linkedin` warning says so. Both duplicate warnings include `keys_applied` and a `hint`. Phones match mobile_phone or direct_phone. A number shared by multiple contacts returns matched false and reason ambiguous. It costs zero contact credits. An input that is no email, phone or LinkedIn profile URL returns reason invalid and costs zero contact credits. The forms `mailto:` and `Name ` are accepted. Send the full profile URL. To find a person by name, use `POST /contacts/search` with `domains` and a `where` leaf on `name` with `contains`. Each matched input costs one contact credit, including repeated inputs. ### Modes - `stored`: `stored` reads our own store and is the only working value in v1. - `refresh`: `refresh` is reserved and returns 400 enrich.mode_unavailable until v2. Sending mode: refresh returns 400 enrich.mode_unavailable. The value is reserved for v2. One call takes at most 100 inputs. ## Limits The caps on contact credits, requests and time. Page: https://docs.leadmcp.ai/limits | Limit | Value | | --- | --- | | Contact credits on the paid plan, under the Fair Usage Policy | Unlimited | | Contact credits in total on the free plan, for the life of the account | 1,000 | | Rows a page on search (50 is the default) | 500 | | Concurrent requests per key | 2 | | Requests a minute per key | 300 | | Count time budget | 3 s | | Search page time budget | 10 s | | Export time budget | 120 s | | Rows per export job | 50,000 | | An export download URL is on our own domain and lives 7 days | | | A filter tree has depth 4, 25 leaves, and 50 values in one `in` | | | An enrichment call takes at most 100 inputs | | | A company lookup takes at most 100 inputs | | | Feedback reports a day per key, and 100 a day per account | 20 | | Active API key per account | 1 | | Unused device code per IP address at a time, and 10 device codes an hour per IP address | 1 | ## All routes Every REST route, with what it does and what it costs. Page: https://docs.leadmcp.ai/routes The base URL is `https://api.leadmcp.ai/v1`. Every route answers with the same envelope. | Route | Purpose | Cost | | --- | --- | --- | | [GET /account](https://docs.leadmcp.ai/account/get-account) | Get account | Free. | | [GET /credits](https://docs.leadmcp.ai/credits/get-credits) | Get credit balances | Free. | | [GET /health](https://docs.leadmcp.ai/health/get-health) | Health check | Free. | | [GET /contacts/schema](https://docs.leadmcp.ai/contacts/get-contacts-schema) | List columns | Free. | | [GET /contacts/schema/{column}/values](https://docs.leadmcp.ai/contacts/get-column-values) | List column values | Free. | | [POST /contacts/count](https://docs.leadmcp.ai/contacts/count-contacts) | Count matches | Free. | | [POST /contacts/search](https://docs.leadmcp.ai/contacts/search-contacts) | Find contacts | Each returned contact costs one contact credit. | | [POST /exports](https://docs.leadmcp.ai/exports/start-export) | Start an export | One contact credit per exported contact, charged on completion. | | [GET /exports/{id}](https://docs.leadmcp.ai/exports/get-export) | Get an export | Free. | | [POST /enrichments](https://docs.leadmcp.ai/enrichments/enrich-contacts) | Enrich contacts | One contact credit per stored match. A miss costs nothing. | | [GET /signals](https://docs.leadmcp.ai/signals/list-signals) | list signals | Free. | | [POST /signals/collect](https://docs.leadmcp.ai/signals/collect-signals) | collect signals | One premium credit per lead; holds twice the expected engagers, or exactly the requested company followers (without max_followers, at most the premium credits available). Premium credits are held before work starts, and a dry run is free. | | [GET /signals/collect/{id}](https://docs.leadmcp.ai/signals/get-collect) | get collect status | Free. | | [POST /signals/alerts](https://docs.leadmcp.ai/signals/create-alert) | create alert | One premium credit per new lead, held before each run. Premium credits are held before work starts, and a dry run is free. | | [GET /signals/alerts](https://docs.leadmcp.ai/signals/list-alerts) | list alerts | Free. | | [GET /signals/alerts/{id}](https://docs.leadmcp.ai/signals/get-alert) | get alert | Free. | | [PATCH /signals/alerts/{id}](https://docs.leadmcp.ai/signals/update-alert) | update alert | Free. | | [DELETE /signals/alerts/{id}](https://docs.leadmcp.ai/signals/stop-alert) | stop alert | Free. | | [POST /signals/alerts/{id}/poll](https://docs.leadmcp.ai/signals/poll-alert) | poll alert | One premium credit per new lead. Premium credits are held before work starts, and a dry run is free. | | [POST /feedback](https://docs.leadmcp.ai/feedback/send-feedback) | Send feedback | Free. | | [GET /companies/schema](https://docs.leadmcp.ai/companies/get-companies-schema) | Get the company schema | Free. | | [GET /companies/schema/{column}/values](https://docs.leadmcp.ai/companies/get-company-column-values) | List company column values | Free. | | [POST /companies/count](https://docs.leadmcp.ai/companies/count-companies) | Count companies | Free. | | [POST /companies/search](https://docs.leadmcp.ai/companies/search-companies) | Search companies | Each returned company costs one contact credit. | | [POST /companies/lookup](https://docs.leadmcp.ai/companies/lookup-companies) | Look up companies | One contact credit per match. A miss costs nothing. | | [GET /keys](https://docs.leadmcp.ai/keys/list-api-keys) | List API keys | Free. | | [POST /keys](https://docs.leadmcp.ai/keys/create-api-key) | Create an API key | Free. | | [DELETE /keys/{id}](https://docs.leadmcp.ai/keys/revoke-api-key) | Revoke an API key | Free. | ## Get account `GET /account` Returns the account, the plan and its row limits, the last four characters of the active key and the quota block. This route is the source of truth for the plan and its limits. Free. Page: https://docs.leadmcp.ai/account/get-account Returns the account, the plan and its row limits, the last four characters of the active key and the quota block. This route is the source of truth for the plan and its limits. Free. Cost: Free. ### Response - `account_id` | string | required. The id of the account. - `plan` | string | required. The plan name of the account. This route is the source of truth for the plan. - `contact_credits_per_month` | integer | required. The monthly contact credit cap of the plan. On the paid plan, the cap the Fair Usage Policy sets. On the free plan, the lifetime pool: 1,000 contact credits in total, for the life of the account. - `contact_credits_per_day` | integer | required. The daily contact credit cap of the plan. On the paid plan, the cap the Fair Usage Policy sets. On the free plan, the same lifetime pool as `contact_credits_per_month`. - `key_prefix` | enum | required. The prefix of the active key. Only `lm_live_` exists. Values: `lm_live_`. - `key_last_four` | string | required. The last four characters of the active key. The key itself is shown once, at creation. - `created_at` | string | optional. The RFC 3339 UTC time at which the account was created. ### Failures | Status | Meaning | | --- | --- | | `401` | The credential is missing, unknown or revoked. | | `429` | The key passed 300 requests a minute or 2 concurrent requests. | | `503` | The data is unavailable for a short time. | ### Example ```bash curl -sS "https://api.leadmcp.ai/v1/account" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "LeadMCP-Version: 2026-09-19" ``` Response `200`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": { "account_id": "acc_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "plan": "free", "contact_credits_per_month": 1000, "contact_credits_per_day": 1000, "key_prefix": "lm_live_", "key_last_four": "7f2a", "created_at": "2026-09-19T09:15:00Z" }, "quota": { "plan": "free", "contact_credits_used": 50, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "warnings": [] } ``` ## Get credit balances `GET /credits` Returns both meters, contact credits and premium credits, as separate objects. Premium credits pay for engagers and verified emails. Stored contact delivery uses contact credits. When a premium unit finds no room, the API answers `402 premium_credits.exhausted` with the reset time and an `upgrade_url` for the account owner. Free. Page: https://docs.leadmcp.ai/credits/get-credits Returns both meters, contact credits and premium credits, as separate objects. Premium credits pay for engagers and verified emails. Stored contact delivery uses contact credits. When a premium unit finds no room, the API answers `402 premium_credits.exhausted` with the reset time and an `upgrade_url` for the account owner. Free. Cost: Free. ### Response - `contact_credits` | ContactCreditBalance | required. - `contact_credits_per_month` | integer | required. The monthly contact credit cap of the plan. On the paid plan, the cap the Fair Usage Policy sets. On the free plan, the lifetime pool: 1,000 contact credits in total, for the life of the account. - `contact_credits_per_day` | integer | required. The daily contact credit cap of the plan. On the paid plan, the cap the Fair Usage Policy sets. On the free plan, the same lifetime pool as `contact_credits_per_month`. - `month_remaining` | integer | required. The contact credits left in the current billing month. A paid plan's month starts on its billing date each month; without a subscription it is the UTC calendar month. On the free plan, the contact credits left in the lifetime pool; the pool has no window. - `today_remaining` | integer | required. The contact credits left in the current UTC day. On the free plan, the same number as `month_remaining`. - `binding` | enum | required. The window that runs out first at the current pace. On the free plan, always `month`. Values: `month`, `day`. - `resets_at` | string | required. The RFC 3339 UTC time at which the binding window resets. On the free plan the pool never resets, and the field is `9999-12-31T23:59:59.000Z`. - `premium_credits` | PremiumCreditBalance | required. This month's premium credits. Premium credits pay for engagers and verified emails, and reset on the billing date each month. - `allowance` | integer | required. The premium credits the plan grants this month, plus any grant that arrived this month. - `used` | integer | required. The premium credits spent this month on delivered premium units. - `held` | integer | required. The premium credits running work holds before it delivers. A hold settles to what was delivered or is released. - `available` | integer | required. The premium credits a new premium unit can take right now: the allowance less used and held, never below zero. - `resets_at` | string | required. The RFC 3339 UTC time at which the allowance resets: the next billing date, or the start of the next UTC month without a subscription. ### Failures | Status | Meaning | | --- | --- | | `401` | The credential is missing, unknown or revoked. | | `429` | The key passed 300 requests a minute or 2 concurrent requests. | | `503` | The data is unavailable for a short time. | ### Example ```bash curl -sS "https://api.leadmcp.ai/v1/credits" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "LeadMCP-Version: 2026-09-19" ``` Response `200`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": { "contact_credits": { "contact_credits_per_month": 1000, "contact_credits_per_day": 1000, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "premium_credits": { "allowance": 0, "used": 0, "held": 0, "available": 0, "resets_at": "2026-10-01T00:00:00Z" } }, "quota": { "plan": "free", "contact_credits_used": 50, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "warnings": [] } ``` ## Health check `GET /health` Returns the service state, the build id and the month the current data was loaded. No row totals. Free. Page: https://docs.leadmcp.ai/health/get-health Returns the service state, the build id and the month the current data was loaded. No row totals. Free. Cost: Free. ### Response - `status` | enum | required. The state of the service. The list is frozen. Values: `ok`, `degraded`, `unavailable`. - `release` | string | required. The build id currently serving. - `data_loaded` | YearMonth | required. The month of the current data load. ### Failures | Status | Meaning | | --- | --- | | `401` | The credential is missing, unknown or revoked. | | `429` | The key passed 300 requests a minute or 2 concurrent requests. | | `503` | The data is unavailable for a short time. | ### Example ```bash curl -sS "https://api.leadmcp.ai/v1/health" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "LeadMCP-Version: 2026-09-19" ``` Response `200`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": { "status": "ok", "release": "b_2026_09_15", "data_loaded": "2026-08" }, "quota": { "plan": "free", "contact_credits_used": 50, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "warnings": [] } ``` ## List columns `GET /contacts/schema` Returns every public column with its format, and every filter key with its type and its value route. Top-level job fields (title, company, company_domain, function, seniority, role_since) describe the main role. experience lists the person's current roles only, main role first; the source has no past roles. A housekeeping date (`last_updated`, `last_refreshed`) carries the month. A role start date (`role_since`) carries the day, because a job-change signal is only worth acting on when you know the day it happened. Free. Page: https://docs.leadmcp.ai/contacts/get-contacts-schema Returns every public column with its format, and every filter key with its type and its value route. Top-level job fields (title, company, company_domain, function, seniority, role_since) describe the main role. experience lists the person's current roles only, main role first; the source has no past roles. A housekeeping date (`last_updated`, `last_refreshed`) carries the month. A role start date (`role_since`) carries the day, because a job-change signal is only worth acting on when you know the day it happened. Free. Cost: Free. ### Response - `columns` | array of SchemaColumn | required. The public columns, in the default order. - `name` | string | required. The column name used in requests, files and rows. - `type` | enum | required. The value type of the column. The list is frozen. Values: `string`, `integer`, `number`, `boolean`, `date`, `month`, `array`, `object`. - `description` | string | required. One sentence about what the column holds. - `format` | string | optional. The value format, for example `YYYY-MM` or an ISO-2 slug. - `nullable` | boolean | optional. True when the column can be null. A null never matches a value key. - `closed_list` | boolean | optional. True when the column takes values from a closed slug list. - `values_route` | string or null | optional. The route that lists the allowed values, or null. - `filter_keys` | array of SchemaFilterKey | required. The named filter keys a request may send. - `key` | string | required. The filter key name. - `type` | enum | required. The shape the key takes. The list is frozen. Values: `list`, `string`, `boolean`, `integer`, `month`. - `description` | string | required. One sentence about what the key matches. - `column` | string or null | optional. The column the key reads, or null for a derived key. - `excludes` | string or null | optional. The `exclude_*` twin of this key, or null when it has none. - `values_route` | string or null | optional. The route that lists the allowed values, or null. - `aliases` | array of string | optional. Input spellings that normalize to this key or its values. - `notes` | array of string | optional. Stated limits of the filter language, for example that past employers are not in the data. ### Failures | Status | Meaning | | --- | --- | | `401` | The credential is missing, unknown or revoked. | | `429` | The key passed 300 requests a minute or 2 concurrent requests. | | `503` | The data is unavailable for a short time. | ### Example ```bash curl -sS "https://api.leadmcp.ai/v1/contacts/schema" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "LeadMCP-Version: 2026-09-19" ``` Response `200`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": { "columns": [ { "name": "work_email", "type": "string", "description": "The work email, lowercased.", "nullable": true, "closed_list": false, "values_route": null }, { "name": "seniority", "type": "string", "description": "The seniority rung of the current role.", "nullable": true, "closed_list": true, "values_route": "/contacts/schema/seniority/values" } ], "filter_keys": [ { "key": "seniority", "type": "list", "description": "Seniority rungs to include.", "column": "seniority", "excludes": "exclude_seniority", "values_route": "/contacts/schema/seniority/values", "aliases": [ "Vice President" ] } ], "notes": [ "Past employers are not in the data: experience lists current roles only." ] }, "quota": { "plan": "free", "contact_credits_used": 50, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "warnings": [] } ``` ## List column values `GET /contacts/schema/{column}/values` Returns the allowed slugs of one closed-list column, or the observed values of an open list column (city, region, skills). A column that identifies a person has no values route and is answered as not found. Free. Page: https://docs.leadmcp.ai/contacts/get-column-values Returns the allowed slugs of one closed-list column, or the observed values of an open list column (city, region, skills). A column that identifies a person has no values route and is answered as not found. Free. Cost: Free. ### Parameters | Name | In | Type | Required | Default | Description | | --- | --- | --- | --- | --- | --- | | `column` | path | string | yes | | The column name, as `GET /contacts/schema` prints it, for example `seniority`. | | `q` | query | string | no | | An optional search string that filters the values by slug or by label. | | `limit` | query | integer | no | 100 | The maximum number of values to return. The default is 100. | ### Response The values of the column. An open list comes most common first. A closed list comes in its published order, with count null. - `value` | string | required. The canonical slug. Slugs never change. - `label` | string | required. The display label. A label may change, so never store it. - `count` | integer or null | optional. How many rows carry the value, or null when it was not computed. ### 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. | | `404` | The id or the column does not exist, or the row was opted out. | | `429` | The key passed 300 requests a minute or 2 concurrent requests. | | `503` | The data is unavailable for a short time. | ### Example ```bash curl -sS "https://api.leadmcp.ai/v1/contacts/schema/seniority/values" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "LeadMCP-Version: 2026-09-19" ``` Response `200`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": [ { "value": "vp", "label": "Vice President", "count": 1842000 }, { "value": "director", "label": "Director", "count": 2910000 } ], "quota": { "plan": "free", "contact_credits_used": 50, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "warnings": [] } ``` ## Count matches `POST /contacts/count` Counts the matching contacts. Free, and exact when it completes inside the 3 s budget. On zero rows the response carries a `no_rows` warning that names the keys applied. Page: https://docs.leadmcp.ai/contacts/count-contacts Counts the matching contacts. Free, and exact when it completes inside the 3 s budget. On zero rows the response carries a `no_rows` warning that names the keys applied. Cost: Free. ### Request body - `filter` | Filter | required. The audience to count. - `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. - `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 - `count` | integer or null | required. The matching rows, or null when the count passed its 3 s budget. A null count carries a count_unavailable warning. ### 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. | | `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/count" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "LeadMCP-Version: 2026-09-19" \ -H "Content-Type: application/json" \ -d '{"filter":{"countries":["us"],"seniority":["vp"],"functions":["marketing_and_advertising"],"has_work_email":true}}' ``` Response `200`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": { "count": 4210 }, "quota": { "plan": "free", "contact_credits_used": 50, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "warnings": [] } ``` ## 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. Page: https://docs.leadmcp.ai/contacts/search-contacts 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/`. 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/`, 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": [] } ``` ## Start an export `POST /exports` Starts one file job for a filter and reserves the quota without charging it. The job counts the filter itself. `POST /contacts/count` is free and tells you the price before you pay it. One export runs per account: a second call while one runs returns 409 export.running with the running id. A job delivers at most 50,000 rows. Near a cap the job is truncated to the contact credits left under the cap that runs out first, and a warning gives the reason and the reset time. On the free plan the cap is the lifetime pool: the reason is `monthly_cap`, `resets_at` is `9999-12-31T23:59:59.000Z`, and the hint says the pool does not reset. When the count passes its budget, the server checks for one matching row. A filter that matches nothing still answers 400 export.no_matches, because that check finds no row, and no job starts. A started job then has rows_available null and a count_unavailable warning. Page: https://docs.leadmcp.ai/exports/start-export Starts one file job for a filter and reserves the quota without charging it. The job counts the filter itself. `POST /contacts/count` is free and tells you the price before you pay it. One export runs per account: a second call while one runs returns 409 export.running with the running id. A job delivers at most 50,000 rows. Near a cap the job is truncated to the contact credits left under the cap that runs out first, and a warning gives the reason and the reset time. On the free plan the cap is the lifetime pool: the reason is `monthly_cap`, `resets_at` is `9999-12-31T23:59:59.000Z`, and the hint says the pool does not reset. When the count passes its budget, the server checks for one matching row. A filter that matches nothing still answers 400 export.no_matches, because that check finds no row, and no job starts. A started job then has rows_available null and a count_unavailable warning. Cost: One contact credit per exported contact, charged on completion. ### Request body - `filter` | Filter | required. The audience to write to the file. - `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 write, in this order. Send `["*"]` for every column. - `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. - `max_rows` | integer | optional. The ceiling for this job. One job writes at most 50,000 rows, so a bigger audience needs several jobs. Default `50000`. - `format` | enum | optional. The file format. Both are gzipped. The list is frozen. Default `csv`. Values: `csv`, `jsonl`. - `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. ### 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. | | `409` | One export already runs on this account. | | `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. | ### Example ```bash curl -sS -X POST "https://api.leadmcp.ai/v1/exports" \ -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","director"]},"columns":["*"],"format":"csv","max_rows":50000}' ``` Response `202`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": { "id": "exp_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "status": "queued", "rows_available": 4210, "rows_to_export": 4210, "truncated": false, "truncation_reason": null, "resets_at": null, "format": "csv" }, "quota": { "plan": "free", "contact_credits_used": 50, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "warnings": [] } ``` ## Get an export `GET /exports/{id}` Returns the status of one export job, then the download URL and the rows exported. Poll every 3 to 5 seconds until the status is completed. The URL is on our own domain and needs no key. The file is gzip-compressed, `{export_id}.csv.gz` or `{export_id}.jsonl.gz`: download it and gunzip it. The file and the URL live 7 days from the moment the job completes. After that we delete the file, and the download link answers 410 with the code `export.expired`. When max_rows_per_company keeps fewer rows than rows_to_export, the job returns an export_truncated warning, and only exported rows are charged. A job runs to completion: there is no cancel. Page: https://docs.leadmcp.ai/exports/get-export Returns the status of one export job, then the download URL and the rows exported. Poll every 3 to 5 seconds until the status is completed. The URL is on our own domain and needs no key. The file is gzip-compressed, `{export_id}.csv.gz` or `{export_id}.jsonl.gz`: download it and gunzip it. The file and the URL live 7 days from the moment the job completes. After that we delete the file, and the download link answers 410 with the code `export.expired`. When max_rows_per_company keeps fewer rows than rows_to_export, the job returns an export_truncated warning, and only exported rows are charged. A job runs to completion: there is no cancel. Cost: Free. ### Parameters | Name | In | Type | Required | Default | Description | | --- | --- | --- | --- | --- | --- | | `id` | path | ExportId | yes | | The export id, a ULID behind the prefix `exp_`. | ### Response - `id` | ExportId | required. An export id: the prefix `exp_` and 26 Crockford base32 characters. - `status` | ExportStatus (slug) | required. The state of an export job. A job runs to completion: there is no cancel. The list is frozen. Values: `queued`, `running`, `completed`, `failed`. - `rows_to_export` | integer | optional. The rows this job reserved at acceptance. - `rows_exported` | integer or null | optional. The rows written, once the job completed. Each exported contact costs one contact credit on completion. - `url` | string or null | optional. The download URL, present once the job completed. It is on our own domain, it carries a signature rather than a credential, and it lives 7 days from the moment the job completes. The bytes are read by us and streamed back, so the address of the host that keeps the file is never published. After 7 days we delete the file, the field is null, and the link answers 410 with the code `export.expired`. - `expires_at` | string or null | optional. The RFC 3339 UTC time at which the download URL stops working. - `filename` | string or null | optional. The file name, `{export_id}.csv.gz` or `{export_id}.jsonl.gz`. - `format` | enum | required. The file format of this job. Values: `csv`, `jsonl`. - `error` | string or null | optional. Why the job failed, when it failed. A failed job releases its quota reservation. - `created_at` | string | required. The RFC 3339 UTC time at which the job was accepted. - `completed_at` | string or null | optional. The RFC 3339 UTC time at which the job finished. ### 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. | | `404` | The id or the column does not exist, or the row was opted out. | | `410` | The export file has expired or is no longer available. | | `429` | The key passed 300 requests a minute or 2 concurrent requests. | | `503` | The data is unavailable for a short time. | ### Example ```bash curl -sS "https://api.leadmcp.ai/v1/exports/exp_01JBQ2W8K9V6M3T0X5Y7Z4A1BC" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "LeadMCP-Version: 2026-09-19" ``` Response `200`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": { "id": "exp_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "status": "completed", "rows_to_export": 4210, "rows_exported": 4210, "url": "https://leadmcp.ai/exports/exp_01JBQ2W8K9V6M3T0X5Y7Z4A1BC.csv.gz", "expires_at": "2026-09-20T09:15:00Z", "filename": "exp_01JBQ2W8K9V6M3T0X5Y7Z4A1BC.csv.gz", "format": "csv", "error": null, "created_at": "2026-09-19T09:15:00Z", "completed_at": "2026-09-19T09:16:24Z" }, "quota": { "plan": "free", "contact_credits_used": 50, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "warnings": [] } ``` ## Enrich contacts `POST /enrichments` Takes up to 100 work emails, phone numbers or LinkedIn URLs and returns the matching stored rows. The result keeps the input order, one result per input. You pay one contact credit for each match, and a miss costs nothing. Matching is exact: an email is lowercased and a LinkedIn URL reduces to its slug. When two contacts share a work email, the later `last_updated` wins and a `duplicate_work_email` warning says so. When two contacts share a LinkedIn slug, the later `last_updated` wins and a `duplicate_linkedin` warning says so. Both duplicate warnings include `keys_applied` and a `hint`. Phones match mobile_phone or direct_phone. A number shared by multiple contacts returns matched false and reason ambiguous. It costs zero contact credits. An input that is no email, phone or LinkedIn profile URL returns reason invalid and costs zero contact credits. The forms `mailto:` and `Name ` are accepted. Send the full profile URL. To find a person by name, use `POST /contacts/search` with `domains` and a `where` leaf on `name` with `contains`. Each matched input costs one contact credit, including repeated inputs. Page: https://docs.leadmcp.ai/enrichments/enrich-contacts Takes up to 100 work emails, phone numbers or LinkedIn URLs and returns the matching stored rows. The result keeps the input order, one result per input. You pay one contact credit for each match, and a miss costs nothing. Matching is exact: an email is lowercased and a LinkedIn URL reduces to its slug. When two contacts share a work email, the later `last_updated` wins and a `duplicate_work_email` warning says so. When two contacts share a LinkedIn slug, the later `last_updated` wins and a `duplicate_linkedin` warning says so. Both duplicate warnings include `keys_applied` and a `hint`. Phones match mobile_phone or direct_phone. A number shared by multiple contacts returns matched false and reason ambiguous. It costs zero contact credits. An input that is no email, phone or LinkedIn profile URL returns reason invalid and costs zero contact credits. The forms `mailto:` and `Name ` are accepted. Send the full profile URL. To find a person by name, use `POST /contacts/search` with `domains` and a `where` leaf on `name` with `contains`. Each matched input costs one contact credit, including repeated inputs. Cost: One contact credit per stored match. A miss costs nothing. ### Request body - `inputs` | array of string | required. Up to 100 work emails, phone numbers or LinkedIn URLs. One input matches at most one contact. - `company_domain` | string | optional. The domain of the account you sourced these people from, for example `acme.com`. When set, a match counts only if the contact's current employer is on this domain. A person we hold at another employer, or with no employer domain, answers `matched: false` with `off_domain: true`, costs nothing, and is listed on an `off_domain` warning. Without it, every match is returned and charged, whatever the employer. A value with no dot, such as a company name, returns 400 request.invalid. To find the domain of a named company, use `POST /companies/search` with `company_names`. - `mode` | enum | optional. How to answer. `stored` reads our own store and is the only working value in v1. `refresh` is reserved and returns 400 enrich.mode_unavailable until v2. Default `stored`. Values: `stored`, `refresh`. - `columns` | array of string | optional. The columns to return on a hit. Every hit also carries `id`, always first. The default is the enrichment set: name, title, company, company_domain, work_email, country, seniority, last_updated, mobile_phone, direct_phone, has_phone, function, location, email_status, company_size, company_industries, company_linkedin, linkedin. A signal row that matches a stored contact carries the same set. Send `["*"]` for every column. ### Response One result per input, in the same order as the request. - `input` | string | required. The identifier exactly as the request sent it. - `matched` | boolean | required. True when a stored row matched and, if `company_domain` was sent, the contact works there. You pay one contact credit for each match, and a miss costs nothing. - `off_domain` | boolean | required. True when we hold this person but their current employer is not on the `company_domain` you sent, or has no domain. Such a result is a miss: no row, no charge. Always false when no `company_domain` was sent. - `contact` | Contact or null | required. The matched contact row, or null on a miss. - `reason` | enum | optional. Present for invalid inputs or phones shared by multiple contacts. Such inputs return no contact and cost zero contact credits. Ambiguity is checked before company_domain. Values: `invalid`, `ambiguous`. ### 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. | | `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/enrichments" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "LeadMCP-Version: 2026-09-19" \ -H "Content-Type: application/json" \ -d '{"inputs":["dana.reyes@example.com","https://www.linkedin.com/in/dana-reyes"],"mode":"stored"}' ``` Response `200`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": [ { "input": "dana.reyes@example.com", "matched": true, "contact": { "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 } }, { "input": "https://www.linkedin.com/in/dana-reyes", "matched": false, "contact": null } ], "quota": { "plan": "free", "contact_credits_used": 50, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "warnings": [] } ``` ## list signals `GET /signals` Returns signals newest first, with a cursor for the next page. The page size is at most 500. source_type and source_id read one source; a cursor from a filtered page keeps its filter. Reading is free: a row's contact was charged once, when the row was collected. Page: https://docs.leadmcp.ai/signals/list-signals Returns signals newest first, with a cursor for the next page. The page size is at most 500. source_type and source_id read one source; a cursor from a filtered page keeps its filter. Reading is free: a row's contact was charged once, when the row was collected. Cost: Free. ### Parameters | Name | In | Type | Required | Default | Description | | --- | --- | --- | --- | --- | --- | | `kind` | query | string | no | | | | `alert_id` | query | string | no | | | | `job_id` | query | string | no | | | | `source_type` | query | enum | no | | Reads one kind of source: alert or collect. | | `source_id` | query | string | no | | One alert id or collect job id. Send it only with source_type; alone it answers 400 request.invalid with field source_id. | | `since` | query | string | no | | | | `limit` | query | integer | no | 50 | | | `cursor` | query | string | no | | | ### Response - `items` | array of object | required. - `id` | string | required. - `kind` | enum | required. Values: `linkedin_engagement`, `company_follow`. - `action` | enum | required. Values: `reaction`, `comment`, `follow`. - `subject` | string | required. - `seen_at` | string | required. - `source` | object | required. - `type` | enum | required. Values: `alert`, `collect`. - `id` | string | required. - `linkedin_url` | string or null | required. The LinkedIn profile URL of the person who engaged or followed, as the source gave it. It is the key of the row and of the contact lookup. - `name` | string or null | required. The name the source shows for the person. - `headline` | string or null | required. The headline the source shows for the person. - `details` | any | required. - `contact` | any | required. - `contact_status` | enum | required. delivered: the LinkedIn URL matched a stored contact, one contact credit was charged, and contact holds it. not_found: no stored contact has this URL, or the URL is not a public profile URL; nothing was charged. credits_exhausted: a contact matched but the contact credit caps had no room; nothing was charged. account_blocked: a contact matched but the account could not be charged; nothing was charged. pending: the lookup has not run yet; nothing was charged. The lookup runs once, when the row is collected, so reading the feed again never charges again. Values: `delivered`, `not_found`, `credits_exhausted`, `account_blocked`, `pending`. - `next_cursor` | string or null | required. ### 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. | | `429` | The key passed 300 requests a minute or 2 concurrent requests. | | `503` | The data is unavailable for a short time. | ### Example ```bash curl -sS "https://api.leadmcp.ai/v1/signals" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "LeadMCP-Version: 2026-09-19" ``` Response `200`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": { "items": [ { "id": "01ARZ3NDEKTSV4RRFFQ69G5FAX", "kind": "linkedin_engagement", "action": "comment", "subject": "https://www.linkedin.com/feed/update/urn:li:activity:1234567890", "seen_at": "2026-09-20T00:00:00Z", "source": { "type": "collect", "id": "01ARZ3NDEKTSV4RRFFQ69G5FAW" }, "linkedin_url": "https://www.linkedin.com/in/example", "name": "Alex Morgan", "headline": "Engineering leader", "details": { "text": "Thanks for sharing.", "reaction_type": null }, "contact": { "id": "per_01ARZ3NDEKTSV4RRFFQ69G5FAY", "name": "Alex Morgan", "title": "Engineering Director", "company": "Example", "company_domain": "example.com", "work_email": "alex@example.com", "country": "us", "seniority": "director", "last_updated": "2026-08", "mobile_phone": null, "direct_phone": null, "has_phone": false, "function": "engineering", "location": "Austin, Texas, United States", "email_status": "valid", "company_size": "201-500", "company_industries": [ "software_development" ], "company_linkedin": "https://www.linkedin.com/company/example", "linkedin": "https://www.linkedin.com/in/example" }, "contact_status": "delivered" }, { "id": "01ARZ3NDEKTSV4RRFFQ69G5FAZ", "kind": "linkedin_engagement", "action": "reaction", "subject": "https://www.linkedin.com/feed/update/urn:li:activity:1234567890", "seen_at": "2026-09-20T00:00:00Z", "source": { "type": "collect", "id": "01ARZ3NDEKTSV4RRFFQ69G5FAW" }, "linkedin_url": "https://www.linkedin.com/in/someone-else", "name": "Sam Lee", "headline": null, "details": { "text": null, "reaction_type": "LIKE" }, "contact": null, "contact_status": "not_found" } ], "next_cursor": null }, "quota": { "plan": "free", "contact_credits_used": 50, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "warnings": [] } ``` ## collect signals `POST /signals/collect` Collects one LinkedIn profile, post or company page once. Company followers have a minimum order of 100, accept max_followers, reserve exactly the requested count and always answer 202. Without max_followers, a company job asks for at most the premium credits available and adds a max_followers_missing warning. One credit per unique lead settles when results arrive. A page below the minimum answers `signals.below_minimum` (HTTP 400) with the follower count in error.details. With dry_run true, returns a free estimate without admission. Engagement collects support wait for estimates at most 500. The 202 answer carries job.id. Poll get_collect (GET /signals/collect/{id}) every 30 to 60 seconds until status is completed or failed. Then read the rows with list_signals (GET /signals): source_type collect and source_id the job id. Page: https://docs.leadmcp.ai/signals/collect-signals Collects one LinkedIn profile, post or company page once. Company followers have a minimum order of 100, accept max_followers, reserve exactly the requested count and always answer 202. Without max_followers, a company job asks for at most the premium credits available and adds a max_followers_missing warning. One credit per unique lead settles when results arrive. A page below the minimum answers `signals.below_minimum` (HTTP 400) with the follower count in error.details. With dry_run true, returns a free estimate without admission. Engagement collects support wait for estimates at most 500. The 202 answer carries job.id. Poll get_collect (GET /signals/collect/{id}) every 30 to 60 seconds until status is completed or failed. Then read the rows with list_signals (GET /signals): source_type collect and source_id the job id. Cost: One premium credit per lead; holds twice the expected engagers, or exactly the requested company followers (without max_followers, at most the premium credits available). Premium credits are held before work starts, and a dry run is free. ### Request body - `kind` | enum | required. What to collect. linkedin_profile: the people who react to or comment on the 10 latest posts of one profile. linkedin_post: the people who react to or comment on one post. linkedin_company: the followers of one company page. Values: `linkedin_profile`, `linkedin_post`, `linkedin_company`. - `target` | string | required. The LinkedIn URL of the target. A profile URL, such as https://www.linkedin.com/in/. A post URL of the form https://www.linkedin.com/posts/ or https://www.linkedin.com/feed/update/urn:li:activity:. A company URL, such as https://www.linkedin.com/company/. - `max_followers` | integer | optional. For linkedin_company only: the most followers to collect, at least 100 and at most one million. Without it, a company job asks for at most the premium credits available, with a minimum of 100, and adds a max_followers_missing warning. - `wait` | boolean | optional. True keeps the request open until the rows are ready, for at most about 45 seconds, when the estimate is 500 engagers or fewer. A company page never waits. Otherwise the job runs in the background; read its rows with list_signals. - `dry_run` | boolean | optional. True returns only the estimate: units, premium_credits, available and sufficient. It holds no credits, starts no job and costs nothing. ### Response - `job` | object | required. - `kind` | enum | required. Values: `linkedin_profile`, `linkedin_post`, `linkedin_company`. - `target` | string | required. - `id` | string | required. - `alert_id` | any | required. - `status` | enum | required. Values: `queued`, `running`, `completed`, `partial`, `no_matches`, `failed`, `cancelled`. - `hold` | integer | required. - `signals` | integer | required. - `leads` | integer | required. - `settled_at` | any | required. - `error` | string or null | required. - `scrape_outcome` | any | required. - `created_at` | string | required. - `started_at` | any | required. - `completed_at` | any | required. ### 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. | | `429` | The key passed 300 requests a minute or 2 concurrent requests. | | `503` | The data is unavailable for a short time. | ### Example ```bash curl -sS -X POST "https://api.leadmcp.ai/v1/signals/collect" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "LeadMCP-Version: 2026-09-19" \ -H "Content-Type: application/json" \ -d '{"kind":"linkedin_profile","target":"https://www.linkedin.com/in/example"}' ``` Response `202`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": { "job": { "kind": "linkedin_profile", "target": "https://www.linkedin.com/in/example", "id": "01ARZ3NDEKTSV4RRFFQ69G5FAW", "alert_id": null, "status": "queued", "hold": 20, "signals": 0, "leads": 0, "settled_at": null, "error": null, "scrape_outcome": null, "created_at": "2026-09-19T09:15:00Z", "started_at": null, "completed_at": null } }, "quota": { "plan": "free", "contact_credits_used": 50, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "warnings": [] } ``` ## get collect status `GET /signals/collect/{id}` Returns the status of one collect job: queued, running, completed or failed. It also returns the leads and contacts so far, the premium credits held and charged, and the list_signals arguments that read the rows. After collect_signals, call it every 30 to 60 seconds until status is completed or failed. Then read the rows with GET /signals and the query in read.arguments. Free. Page: https://docs.leadmcp.ai/signals/get-collect Returns the status of one collect job: queued, running, completed or failed. It also returns the leads and contacts so far, the premium credits held and charged, and the list_signals arguments that read the rows. After collect_signals, call it every 30 to 60 seconds until status is completed or failed. Then read the rows with GET /signals and the query in read.arguments. Free. Cost: Free. ### Parameters | Name | In | Type | Required | Default | Description | | --- | --- | --- | --- | --- | --- | | `id` | path | string | yes | | The collect job id: job.id in the collect_signals answer. | ### Response - `id` | string | required. - `kind` | enum | required. Values: `linkedin_profile`, `linkedin_post`, `linkedin_company`. - `target` | string | required. - `status` | enum | required. queued: no runner has started the job. running: the job collects or matches rows; call get_collect again in 30 to 60 seconds. completed: all rows are written; read them with list_signals. failed: the job stopped; the rows it wrote stay readable, and error says what to do. Values: `queued`, `running`, `completed`, `failed`. - `requested` | integer | required. The leads the job asks for: the requested company followers, or the expected engagers of a profile or post. - `signals` | integer | required. The rows the job wrote so far. - `leads` | integer | required. The unique new leads so far. Each one costs one premium credit when the job settles. - `contacts_matched` | integer | required. The rows that carry a stored contact so far. Each one cost one contact credit. - `premium_credits` | object | required. - `held` | integer | required. The premium credits held for this job now. The hold returns when the job settles. - `charged` | integer | required. The premium credits this job charged. It is 0 until the job settles. - `error` | string or null | required. Present only when status is failed: what went wrong and what to do. - `created_at` | string | required. - `finished_at` | any | required. When the job completed or failed. Null while it is queued or running. - `read` | object | required. The list_signals arguments that read this job's rows. On REST, send them as the query of GET /signals. - `tool` | string | required. - `arguments` | object | required. - `source_type` | string | required. - `source_id` | string | required. ### 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. | | `404` | Unknown collect job id, an alert run, or a job of another account. | | `429` | The key passed 300 requests a minute or 2 concurrent requests. | | `503` | The data is unavailable for a short time. | ### Example ```bash curl -sS "https://api.leadmcp.ai/v1/signals/collect/01ARZ3NDEKTSV4RRFFQ69G5FAV" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "LeadMCP-Version: 2026-09-19" ``` Response `200`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": { "id": "01ARZ3NDEKTSV4RRFFQ69G5FAW", "kind": "linkedin_profile", "target": "https://www.linkedin.com/in/example", "status": "running", "requested": 10, "signals": 6, "leads": 5, "contacts_matched": 2, "premium_credits": { "held": 20, "charged": 0 }, "error": null, "created_at": "2026-09-20T00:00:00Z", "finished_at": null, "read": { "tool": "list_signals", "arguments": { "source_type": "collect", "source_id": "01ARZ3NDEKTSV4RRFFQ69G5FAW" } } }, "quota": { "plan": "free", "contact_credits_used": 50, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "warnings": [] } ``` ## create alert `POST /signals/alerts` An alert is a source of signals. Read them with `list_signals`. Starts a premium source for one premium credit per lead. Holds premium credits for the first job. With dry_run true, estimates without writing a row. Page: https://docs.leadmcp.ai/signals/create-alert An alert is a source of signals. Read them with `list_signals`. Starts a premium source for one premium credit per lead. Holds premium credits for the first job. With dry_run true, estimates without writing a row. Cost: One premium credit per new lead, held before each run. Premium credits are held before work starts, and a dry run is free. ### Request body - `kind` | enum | required. What to watch. linkedin_profile: the people who react to or comment on the 10 latest posts of one profile. linkedin_post: the people who react to or comment on one post. Values: `linkedin_profile`, `linkedin_post`. - `target` | string | required. The LinkedIn URL of the target. A profile URL, such as https://www.linkedin.com/in/. A post URL of the form https://www.linkedin.com/posts/ or https://www.linkedin.com/feed/update/urn:li:activity:. - `cadence` | enum | optional. daily runs the alert once a day. manual never runs on its own: it runs only when you call poll_alert. The default is daily. Values: `daily`, `manual`. - `billing_choice` | enum | optional. new_only: the first run sets a baseline and charges nothing for the backlog; later runs collect and charge only new engagers. backfill_and_new: the first run also collects and charges the existing engagers. The default is backfill_and_new. Values: `backfill_and_new`, `new_only`. - `dry_run` | boolean | optional. True returns only the estimate: units, premium_credits, available and sufficient. It holds no credits, starts no job and costs nothing. ### Response - `alert` | object | required. - `kind` | enum | required. Values: `linkedin_profile`, `linkedin_post`. - `target` | string | required. - `id` | string | required. - `cadence` | enum | required. Values: `daily`, `manual`. - `billing_choice` | enum | required. Values: `backfill_and_new`, `new_only`. - `active` | boolean | required. - `last_polled_at` | any | required. - `removed_at` | any | required. - `created_at` | string | required. - `job` | object | required. - `kind` | enum | required. Values: `linkedin_profile`, `linkedin_post`, `linkedin_company`. - `target` | string | required. - `id` | string | required. - `alert_id` | any | required. - `status` | enum | required. Values: `queued`, `running`, `completed`, `partial`, `no_matches`, `failed`, `cancelled`. - `hold` | integer | required. - `signals` | integer | required. - `leads` | integer | required. - `settled_at` | any | required. - `error` | string or null | required. - `scrape_outcome` | any | required. - `created_at` | string | required. - `started_at` | any | required. - `completed_at` | any | required. ### 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. | | `403` | Creating alerts requires a paid plan (plan.required). | | `429` | The key passed 300 requests a minute or 2 concurrent requests. | | `503` | The data is unavailable for a short time. | ### Example ```bash curl -sS -X POST "https://api.leadmcp.ai/v1/signals/alerts" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "LeadMCP-Version: 2026-09-19" \ -H "Content-Type: application/json" \ -d '{"kind":"linkedin_profile","target":"https://www.linkedin.com/in/example"}' ``` Response `202`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": { "alert": { "kind": "linkedin_profile", "target": "https://www.linkedin.com/in/example", "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", "cadence": "daily", "billing_choice": "backfill_and_new", "active": true, "last_polled_at": null, "removed_at": null, "created_at": "2026-09-19T09:15:00Z" }, "job": { "kind": "linkedin_profile", "target": "https://www.linkedin.com/in/example", "id": "01ARZ3NDEKTSV4RRFFQ69G5FAW", "alert_id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", "status": "queued", "hold": 20, "signals": 0, "leads": 0, "settled_at": null, "error": null, "scrape_outcome": null, "created_at": "2026-09-19T09:15:00Z", "started_at": null, "completed_at": null } }, "quota": { "plan": "free", "contact_credits_used": 50, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "warnings": [] } ``` ## list alerts `GET /signals/alerts` An alert is a source of signals. Read them with `list_signals`. Lists sources with signal counts, lead counts and last job state. Free. Page: https://docs.leadmcp.ai/signals/list-alerts An alert is a source of signals. Read them with `list_signals`. Lists sources with signal counts, lead counts and last job state. Free. Cost: Free. ### Response - `kind` | enum | required. Values: `linkedin_profile`, `linkedin_post`. - `target` | string | required. - `id` | string | required. - `cadence` | enum | required. Values: `daily`, `manual`. - `billing_choice` | enum | required. Values: `backfill_and_new`, `new_only`. - `active` | boolean | required. - `last_polled_at` | any | required. - `removed_at` | any | required. - `created_at` | string | required. - `signals` | integer | required. - `leads` | integer | required. - `last_job_status` | any | required. ### 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. | | `429` | The key passed 300 requests a minute or 2 concurrent requests. | | `503` | The data is unavailable for a short time. | ### Example ```bash curl -sS "https://api.leadmcp.ai/v1/signals/alerts" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "LeadMCP-Version: 2026-09-19" ``` Response `200`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": [ { "kind": "linkedin_profile", "target": "https://www.linkedin.com/in/example", "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", "cadence": "daily", "billing_choice": "backfill_and_new", "active": true, "last_polled_at": null, "removed_at": null, "created_at": "2026-09-19T09:15:00Z", "signals": 10, "leads": 8, "last_job_status": "completed" } ], "quota": { "plan": "free", "contact_credits_used": 50, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "warnings": [] } ``` ## get alert `GET /signals/alerts/{id}` An alert is a source of signals. Read them with `list_signals`. Returns one source and its last job. Free. Page: https://docs.leadmcp.ai/signals/get-alert An alert is a source of signals. Read them with `list_signals`. Returns one source and its last job. Free. Cost: Free. ### Parameters | Name | In | Type | Required | Default | Description | | --- | --- | --- | --- | --- | --- | | `id` | path | string | yes | | The alert id. | ### Response - `alert` | object | required. - `kind` | enum | required. Values: `linkedin_profile`, `linkedin_post`. - `target` | string | required. - `id` | string | required. - `cadence` | enum | required. Values: `daily`, `manual`. - `billing_choice` | enum | required. Values: `backfill_and_new`, `new_only`. - `active` | boolean | required. - `last_polled_at` | any | required. - `removed_at` | any | required. - `created_at` | string | required. - `last_job` | object or null | required. - `kind` | enum | required. Values: `linkedin_profile`, `linkedin_post`, `linkedin_company`. - `target` | string | required. - `id` | string | required. - `alert_id` | string or null | required. - `status` | enum | required. Values: `queued`, `running`, `completed`, `partial`, `no_matches`, `failed`, `cancelled`. - `hold` | integer | required. - `signals` | integer | required. - `leads` | integer | required. - `settled_at` | string or null | required. - `error` | string or null | required. - `scrape_outcome` | enum | required. Values: `completed`, `partial`, `no_matches`, `failed`. - `created_at` | string | required. - `started_at` | string or null | required. - `completed_at` | string or null | required. ### 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. | | `404` | Unknown alert id or an alert belonging to another account. | | `429` | The key passed 300 requests a minute or 2 concurrent requests. | | `503` | The data is unavailable for a short time. | ### Example ```bash curl -sS "https://api.leadmcp.ai/v1/signals/alerts/01ARZ3NDEKTSV4RRFFQ69G5FAV" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "LeadMCP-Version: 2026-09-19" ``` Response `200`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": { "alert": { "kind": "linkedin_profile", "target": "https://www.linkedin.com/in/example", "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", "cadence": "daily", "billing_choice": "backfill_and_new", "active": true, "last_polled_at": null, "removed_at": null, "created_at": "2026-09-19T09:15:00Z" }, "last_job": null }, "quota": { "plan": "free", "contact_credits_used": 50, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "warnings": [] } ``` ## update alert `PATCH /signals/alerts/{id}` An alert is a source of signals. Read them with `list_signals`. Changes whether a source is active and its cadence. Free. Page: https://docs.leadmcp.ai/signals/update-alert An alert is a source of signals. Read them with `list_signals`. Changes whether a source is active and its cadence. Free. Cost: Free. ### Parameters | Name | In | Type | Required | Default | Description | | --- | --- | --- | --- | --- | --- | | `id` | path | string | yes | | The alert id. | ### Request body - `active` | boolean | optional. False pauses the alert, and true resumes it. A paused alert does not run on its daily cadence. - `cadence` | enum | optional. daily runs the alert once a day. manual never runs on its own: it runs only when you call poll_alert. The default is daily. Values: `daily`, `manual`. ### Response - `alert` | object | required. - `kind` | enum | required. Values: `linkedin_profile`, `linkedin_post`. - `target` | string | required. - `id` | string | required. - `cadence` | enum | required. Values: `daily`, `manual`. - `billing_choice` | enum | required. Values: `backfill_and_new`, `new_only`. - `active` | boolean | required. - `last_polled_at` | any | required. - `removed_at` | any | required. - `created_at` | string | required. ### 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. | | `404` | Unknown alert id or an alert belonging to another account. | | `429` | The key passed 300 requests a minute or 2 concurrent requests. | | `503` | The data is unavailable for a short time. | ### Example ```bash curl -sS -X PATCH "https://api.leadmcp.ai/v1/signals/alerts/01ARZ3NDEKTSV4RRFFQ69G5FAV" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "LeadMCP-Version: 2026-09-19" \ -H "Content-Type: application/json" \ -d '{"active":false}' ``` Response `200`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": { "alert": { "kind": "linkedin_profile", "target": "https://www.linkedin.com/in/example", "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", "cadence": "daily", "billing_choice": "backfill_and_new", "active": false, "last_polled_at": null, "removed_at": null, "created_at": "2026-09-19T09:15:00Z" } }, "quota": { "plan": "free", "contact_credits_used": 50, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "warnings": [] } ``` ## stop alert `DELETE /signals/alerts/{id}` An alert is a source of signals. Read them with `list_signals`. Removes a source. Its signals stay available. Free. Page: https://docs.leadmcp.ai/signals/stop-alert An alert is a source of signals. Read them with `list_signals`. Removes a source. Its signals stay available. Free. Cost: Free. ### Parameters | Name | In | Type | Required | Default | Description | | --- | --- | --- | --- | --- | --- | | `id` | path | string | yes | | The alert id. | ### Response - `alert` | object | required. - `kind` | enum | required. Values: `linkedin_profile`, `linkedin_post`. - `target` | string | required. - `id` | string | required. - `cadence` | enum | required. Values: `daily`, `manual`. - `billing_choice` | enum | required. Values: `backfill_and_new`, `new_only`. - `active` | boolean | required. - `last_polled_at` | any | required. - `removed_at` | any | required. - `created_at` | string | required. ### 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. | | `404` | Unknown alert id or an alert belonging to another account. | | `429` | The key passed 300 requests a minute or 2 concurrent requests. | | `503` | The data is unavailable for a short time. | ### Example ```bash curl -sS -X DELETE "https://api.leadmcp.ai/v1/signals/alerts/01ARZ3NDEKTSV4RRFFQ69G5FAV" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "LeadMCP-Version: 2026-09-19" ``` Response `200`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": { "alert": { "kind": "linkedin_profile", "target": "https://www.linkedin.com/in/example", "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", "cadence": "daily", "billing_choice": "backfill_and_new", "active": false, "last_polled_at": null, "removed_at": "2026-09-20T00:00:00Z", "created_at": "2026-09-19T09:15:00Z" } }, "quota": { "plan": "free", "contact_credits_used": 50, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "warnings": [] } ``` ## poll alert `POST /signals/alerts/{id}/poll` An alert is a source of signals. Read them with `list_signals`. Runs a poll now, holding premium credits for new leads. One credit per lead for premium signals. Page: https://docs.leadmcp.ai/signals/poll-alert An alert is a source of signals. Read them with `list_signals`. Runs a poll now, holding premium credits for new leads. One credit per lead for premium signals. Cost: One premium credit per new lead. Premium credits are held before work starts, and a dry run is free. ### Parameters | Name | In | Type | Required | Default | Description | | --- | --- | --- | --- | --- | --- | | `id` | path | string | yes | | The alert id. | ### Response - `job` | object | required. - `kind` | enum | required. Values: `linkedin_profile`, `linkedin_post`, `linkedin_company`. - `target` | string | required. - `id` | string | required. - `alert_id` | any | required. - `status` | enum | required. Values: `queued`, `running`, `completed`, `partial`, `no_matches`, `failed`, `cancelled`. - `hold` | integer | required. - `signals` | integer | required. - `leads` | integer | required. - `settled_at` | any | required. - `error` | string or null | required. - `scrape_outcome` | any | required. - `created_at` | string | required. - `started_at` | any | required. - `completed_at` | any | required. ### 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. | | `404` | Unknown alert id or an alert belonging to another account. | | `409` | The alert already has a queued or running job. | | `429` | The key passed 300 requests a minute or 2 concurrent requests. | | `503` | The data is unavailable for a short time. | ### Example ```bash curl -sS -X POST "https://api.leadmcp.ai/v1/signals/alerts/01ARZ3NDEKTSV4RRFFQ69G5FAV/poll" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "LeadMCP-Version: 2026-09-19" ``` Response `202`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": { "job": { "kind": "linkedin_profile", "target": "https://www.linkedin.com/in/example", "id": "01ARZ3NDEKTSV4RRFFQ69G5FAW", "alert_id": null, "status": "queued", "hold": 20, "signals": 0, "leads": 0, "settled_at": null, "error": null, "scrape_outcome": null, "created_at": "2026-09-19T09:15:00Z", "started_at": null, "completed_at": null } }, "quota": { "plan": "free", "contact_credits_used": 50, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "warnings": [] } ``` ## Send feedback `POST /feedback` Reports a problem with one call to the LeadMCP team: a missing feature or filter, a bug, wrong data in a row, or an unclear error. Send the request_id of that call. The request_id must be from the last 7 days. The same request_id and category sent again returns the first feedback_id and does not count again. A key can send 20 reports a day, and an account can send 100 a day. A day is the last 24 hours. Free. Page: https://docs.leadmcp.ai/feedback/send-feedback Reports a problem with one call to the LeadMCP team: a missing feature or filter, a bug, wrong data in a row, or an unclear error. Send the request_id of that call. The request_id must be from the last 7 days. The same request_id and category sent again returns the first feedback_id and does not count again. A key can send 20 reports a day, and an account can send 100 a day. A day is the last 24 hours. Free. Cost: Free. ### Request body - `request_id` | string | required. The request_id from the response of the call that went wrong. It must be from the last 7 days. - `category` | enum | required. The kind of problem: a missing feature or filter, a bug, wrong data in a row, or an unclear error. Values: `missing_feature`, `bug`, `wrong_data`, `unclear_error`. - `tool` | string | required. The tool or route the agent called or wanted. Free text: a tool that does not exist is accepted. - `row_id` | string | optional. The id of the row with wrong data, when there is one. - `message` | string | required. What went wrong, or what the agent needed. ### Response - `feedback_id` | string | required. The id of the report. A repeat of one report returns the same id. - `status` | string | required. Always `received`. ### 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. | | `429` | The key sent 20 feedback reports in the last 24 hours, or the account sent 100, or the key passed its request limits. | | `503` | The data is unavailable for a short time. | ### Example ```bash curl -sS -X POST "https://api.leadmcp.ai/v1/feedback" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "LeadMCP-Version: 2026-09-19" \ -H "Content-Type: application/json" \ -d '{"request_id":"req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC","category":"missing_feature","tool":"find_contacts","message":"I need a filter for company revenue."}' ``` Response `200`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": { "feedback_id": "fbk_01JBQ2W8K9V6M3T0X5Y7Z4A1BD", "status": "received" }, "quota": { "plan": "free", "contact_credits_used": 50, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "warnings": [] } ``` ## Get the company schema `GET /companies/schema` Returns every public company column and every company filter key. Free. Page: https://docs.leadmcp.ai/companies/get-companies-schema Returns every public company column and every company filter key. Free. Cost: Free. ### Response - `columns` | array of SchemaColumn | required. The public columns, in the default order. - `name` | string | required. The column name used in requests, files and rows. - `type` | enum | required. The value type of the column. The list is frozen. Values: `string`, `integer`, `number`, `boolean`, `date`, `month`, `array`, `object`. - `description` | string | required. One sentence about what the column holds. - `format` | string | optional. The value format, for example `YYYY-MM` or an ISO-2 slug. - `nullable` | boolean | optional. True when the column can be null. A null never matches a value key. - `closed_list` | boolean | optional. True when the column takes values from a closed slug list. - `values_route` | string or null | optional. The route that lists the allowed values, or null. - `filter_keys` | array of SchemaFilterKey | required. The named filter keys a request may send. - `key` | string | required. The filter key name. - `type` | enum | required. The shape the key takes. The list is frozen. Values: `list`, `string`, `boolean`, `integer`, `month`. - `description` | string | required. One sentence about what the key matches. - `column` | string or null | optional. The column the key reads, or null for a derived key. - `excludes` | string or null | optional. The `exclude_*` twin of this key, or null when it has none. - `values_route` | string or null | optional. The route that lists the allowed values, or null. - `aliases` | array of string | optional. Input spellings that normalize to this key or its values. - `notes` | array of string | optional. Stated limits of the filter language, for example that past employers are not in the data. ### Failures | Status | Meaning | | --- | --- | | `401` | The credential is missing, unknown or revoked. | | `429` | The key passed 300 requests a minute or 2 concurrent requests. | ### Example ```bash curl -sS "https://api.leadmcp.ai/v1/companies/schema" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "LeadMCP-Version: 2026-09-19" ``` Response `200`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": { "columns": [ { "name": "headcount_band", "type": "string", "description": "The company employee band.", "nullable": true, "closed_list": true, "values_route": "/companies/schema/headcount_band/values" } ], "filter_keys": [ { "key": "company_size", "type": "list", "description": "Matches the 'headcount_band' column.", "column": "headcount_band", "excludes": null, "values_route": "/companies/schema/headcount_band/values" } ], "notes": [ "To find the people at these companies, send their ids to POST /contacts/search in the company_ids filter key." ] }, "quota": { "plan": "free", "contact_credits_used": 50, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "warnings": [] } ``` ## List company column values `GET /companies/schema/{column}/values` Returns the allowed values of one closed-list company column: hq_country, headcount_band, revenue_range, last_round_type or industries. Company names and domains have no values route and are answered as not found. Free. Page: https://docs.leadmcp.ai/companies/get-company-column-values Returns the allowed values of one closed-list company column: hq_country, headcount_band, revenue_range, last_round_type or industries. Company names and domains have no values route and are answered as not found. Free. Cost: Free. ### Parameters | Name | In | Type | Required | Default | Description | | --- | --- | --- | --- | --- | --- | | `column` | path | string | yes | | The column name, as `GET /companies/schema` prints it, for example `headcount_band`. | | `q` | query | string | no | | An optional search string that filters the values by slug or by label. | | `limit` | query | integer | no | 100 | The maximum number of values to return. The default is 100. | ### Response The values of the column. - `value` | string | required. The canonical slug. Slugs never change. - `label` | string | required. The display label. A label may change, so never store it. - `count` | integer or null | optional. How many rows carry the value, or null when it was not computed. ### 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. | | `404` | The id or the column does not exist, or the row was opted out. | | `429` | The key passed 300 requests a minute or 2 concurrent requests. | | `503` | The data is unavailable for a short time. | ### Example ```bash curl -sS "https://api.leadmcp.ai/v1/companies/schema/headcount_band/values" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "LeadMCP-Version: 2026-09-19" ``` Response `200`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": [ { "value": "51-200", "label": "51-200", "count": null }, { "value": "201-500", "label": "201-500", "count": null } ], "quota": { "plan": "free", "contact_credits_used": 50, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "warnings": [] } ``` ## Count companies `POST /companies/count` Counts the matching companies. Free, and exact when it completes inside the 3 s budget. On zero rows the response carries a `no_rows` warning that names the keys applied. Page: https://docs.leadmcp.ai/companies/count-companies Counts the matching companies. Free, and exact when it completes inside the 3 s budget. On zero rows the response carries a `no_rows` warning that names the keys applied. Cost: Free. ### Request body - `filter` | CompanyFilter | required. The companies to count. - `company_ids` | array of CompanyId | optional. Company ids (`com_`) to include. Takes up to 1,000 ids. - `domains` | array of string | optional. Company domains. A company matches when any of its domains is in the list. A full URL reduces to a bare domain on input. - `exclude_domains` | array of string | optional. Company domains to exclude. A company is left out when any of its domains is in the list. - `company_names` | array of string | optional. Company 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 /companies/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 /companies/schema/industries/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. ### Response - `count` | integer or null | required. The matching rows, or null when the count passed its 3 s budget. A null count carries a count_unavailable warning. ### 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. | | `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/companies/count" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "LeadMCP-Version: 2026-09-19" \ -H "Content-Type: application/json" \ -d '{"filter":{"company_hq_countries":["us"],"company_size":["51-200","201-500"],"industries":["software_development"]}}' ``` Response `200`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": { "count": 1840 }, "quota": { "plan": "free", "contact_credits_used": 50, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "warnings": [] } ``` ## Search companies `POST /companies/search` Returns one page of company records for a company filter, in a fixed order. Each returned company costs one contact credit, through the same plan caps as contacts. Page with `cursor` until `page.next_cursor` is null. To find the people at these companies, send their ids to `POST /contacts/search` in the `company_ids` filter key. Page: https://docs.leadmcp.ai/companies/search-companies Returns one page of company records for a company filter, in a fixed order. Each returned company costs one contact credit, through the same plan caps as contacts. Page with `cursor` until `page.next_cursor` is null. To find the people at these companies, send their ids to `POST /contacts/search` in the `company_ids` filter key. Cost: Each returned company costs one contact credit. ### Request body - `filter` | CompanyFilter | required. The companies to read. - `company_ids` | array of CompanyId | optional. Company ids (`com_`) to include. Takes up to 1,000 ids. - `domains` | array of string | optional. Company domains. A company matches when any of its domains is in the list. A full URL reduces to a bare domain on input. - `exclude_domains` | array of string | optional. Company domains to exclude. A company is left out when any of its domains is in the list. - `company_names` | array of string | optional. Company 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 /companies/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 /companies/schema/industries/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. - `limit` | integer | optional. The page size. The default is 50 and the maximum is 500, the same as contact search. 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. ### Response One page of company records. - `id` | CompanyId | optional. Our company id. Send it to the contact filter in company_ids. - `name` | string or null | optional. The company name. - `description` | string or null | optional. The company description. - `domain` | string or null | optional. The company domain. - `domains` | array of string | optional. The company domains. - `linkedin` | string or null | optional. The company linkedin. - `linkedin_urls` | array of string | optional. The company linkedin urls. - `social` | array of object | optional. The company social. - `network` | string or null | optional. The network. - `url` | string or null | optional. The url. - `username` | string or null | optional. The username. - `followers` | integer or null | optional. The followers. - `phone` | string or null | optional. The company phone. - `employee_count` | integer or null | optional. The company employee count. - `headcount_band` | CompanySize (slug) or null | optional. The company employee band. - `founded_year` | integer or null | optional. The company founded year. - `revenue_range` | CompanyRevenue (slug) or null | optional. The yearly revenue range of the company. Null when it is not known, which is the case for most small companies. - `employee_growth_pct` | integer or null | optional. The change in the employee count over the latest month, in percent. Negative when the company shrank. Null when unknown. - `hq_city` | string or null | optional. The company hq city. - `hq_country` | string or null | optional. The company hq country. - `locations` | array of object | optional. The company locations. - `name` | string or null | optional. The name. - `country` | string or null | optional. The country. - `is_primary` | boolean or null | optional. The is primary. - `street` | string or null | optional. The street address. Null when the location is not the address on record. - `postcode` | string or null | optional. The postal code. Null when the location is not the address on record. - `industries` | array of string | optional. The company industries. - `naics` | array of string | optional. The NAICS industry codes. - `specialties` | array of string | optional. What the company says it specializes in, in its own words. - `is_nonprofit` | boolean or null | optional. Whether the company is nonprofit. Null when unknown. - `is_government` | boolean or null | optional. Whether the company is government. Null when unknown. - `is_public` | boolean or null | optional. Whether the company is public. Null when unknown. - `total_funding_usd` | integer or null | optional. The company total funding usd. - `last_round_type` | FundingRoundType (slug) or null | optional. The latest funding round category. - `last_round_usd` | integer or null | optional. The company last round usd. - `last_round_date` | CalendarDate or null | optional. The latest funding date. - `investors` | array of string | optional. The lead investors of the company's funding rounds. - `investor_count` | integer or null | optional. The number of investors in the latest funding round. Null when unknown. - `monthly_visits` | integer or null | optional. The company monthly visits. - `sic` | array of string | optional. The SIC industry codes. - `last_updated` | YearMonth or null | optional. The month the company record changed. ### 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/companies/search" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "LeadMCP-Version: 2026-09-19" \ -H "Content-Type: application/json" \ -d '{"filter":{"company_hq_countries":["us"],"company_size":["51-200","201-500"],"industries":["software_development"]},"limit":25}' ``` Response `200`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": [ { "id": "com_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "name": "Dana Reyes", "description": "description", "domain": "domain", "domains": [ "domains" ], "linkedin": "https://www.linkedin.com/in/dana-reyes", "linkedin_urls": [ "linkedin urls" ], "social": [ { "network": "network", "url": "url", "username": "username", "followers": 1 } ], "phone": "phone", "employee_count": 320, "headcount_band": "1-10", "founded_year": 0, "revenue_range": "0-1M", "employee_growth_pct": 1, "hq_city": "hq city", "hq_country": "hq country", "locations": [ { "name": "Dana Reyes", "country": "us", "is_primary": true, "street": "street", "postcode": "postcode" } ], "industries": [ "industries" ], "naics": [ "naics" ], "specialties": [ "specialties" ], "is_nonprofit": true, "is_government": true, "is_public": true, "total_funding_usd": 0, "last_round_type": "pre_seed", "last_round_usd": 0, "last_round_date": "2026-09-19", "investors": [ "investors" ], "investor_count": 0, "monthly_visits": 0, "sic": [ "sic" ], "last_updated": "2026-08" } ], "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": [] } ``` ## Look up companies `POST /companies/lookup` Takes up to 100 company ids, domains or company LinkedIn URLs and returns the matching company records. The result keeps the input order, one result per input. You pay one contact credit for each match, and a miss costs nothing. Each matched input costs one contact credit, including repeated inputs. A domain matches a company that carries it among its domains. A LinkedIn URL reduces to its company page. Page: https://docs.leadmcp.ai/companies/lookup-companies Takes up to 100 company ids, domains or company LinkedIn URLs and returns the matching company records. The result keeps the input order, one result per input. You pay one contact credit for each match, and a miss costs nothing. Each matched input costs one contact credit, including repeated inputs. A domain matches a company that carries it among its domains. A LinkedIn URL reduces to its company page. Cost: One contact credit per match. A miss costs nothing. ### Request body - `inputs` | array of string | required. Up to 100 company ids, domains or company LinkedIn URLs. One input matches at most one company. ### Response One result per input, in the same order as the request. - `input` | string | required. The identifier exactly as the request sent it. - `matched` | boolean | required. True when we hold the company. You pay one contact credit for each match, and a miss costs nothing. - `company` | Company or null | required. The matched company, or null on a miss. - `reason` | enum | optional. Present when the input is not a company id, a domain or a company LinkedIn URL. Such an input returns no company and costs nothing. Values: `invalid`. ### 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. | | `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/companies/lookup" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "LeadMCP-Version: 2026-09-19" \ -H "Content-Type: application/json" \ -d '{"inputs":["example.com","https://www.linkedin.com/company/example"]}' ``` Response `200`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": [ { "input": "example.com", "matched": true, "company": { "id": "com_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "name": "Dana Reyes", "description": "description", "domain": "domain", "domains": [ "domains" ], "linkedin": "https://www.linkedin.com/in/dana-reyes", "linkedin_urls": [ "linkedin urls" ], "social": [ { "network": "network", "url": "url", "username": "username", "followers": 1 } ], "phone": "phone", "employee_count": 320, "headcount_band": "1-10", "founded_year": 0, "revenue_range": "0-1M", "employee_growth_pct": 1, "hq_city": "hq city", "hq_country": "hq country", "locations": [ { "name": "Dana Reyes", "country": "us", "is_primary": true, "street": "street", "postcode": "postcode" } ], "industries": [ "industries" ], "naics": [ "naics" ], "specialties": [ "specialties" ], "is_nonprofit": true, "is_government": true, "is_public": true, "total_funding_usd": 0, "last_round_type": "pre_seed", "last_round_usd": 0, "last_round_date": "2026-09-19", "investors": [ "investors" ], "investor_count": 0, "monthly_visits": 0, "sic": [ "sic" ], "last_updated": "2026-08" } }, { "input": "https://www.linkedin.com/company/unknown-example", "matched": false, "company": null } ], "quota": { "plan": "free", "contact_credits_used": 50, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "warnings": [] } ``` ## List API keys `GET /keys` Lists the account's active API key with its id, name, created_at and last_used_at. Never returns the key or its hash. Only an OAuth session may call it; a request signed with an API key answers 403 auth.oauth_required. Free. Page: https://docs.leadmcp.ai/keys/list-api-keys Lists the account's active API key with its id, name, created_at and last_used_at. Never returns the key or its hash. Only an OAuth session may call it; a request signed with an API key answers 403 auth.oauth_required. Free. Cost: Free. ### Response - `keys` | array of ApiKeySummary | required. - `id` | string | required. The id of the key. It is not a secret. - `name` | string | required. The label of the key. - `created_at` | string | required. When the key was created, RFC 3339 UTC. - `last_used_at` | string or null | required. The last call made with the key, RFC 3339 UTC. Null until the first call. ### Failures | Status | Meaning | | --- | --- | | `401` | The credential is missing, unknown or revoked. | | `403` | The request is signed with an API key. | | `429` | The key passed 300 requests a minute or 2 concurrent requests. | | `503` | The data is unavailable for a short time. | ### Example ```bash curl -sS "https://api.leadmcp.ai/v1/keys" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "LeadMCP-Version: 2026-09-19" ``` Response `200`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": {}, "quota": { "plan": "free", "contact_credits_used": 50, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "warnings": [] } ``` ## Create an API key `POST /keys` Creates the account's one API key and returns it once. While a key is active the answer is 409 key.limit_reached: revoke it first. Only an OAuth session may call it; a request signed with an API key answers 403 auth.oauth_required. Free. Page: https://docs.leadmcp.ai/keys/create-api-key Creates the account's one API key and returns it once. While a key is active the answer is 409 key.limit_reached: revoke it first. Only an OAuth session may call it; a request signed with an API key answers 403 auth.oauth_required. Free. Cost: Free. ### Request body - `name` | string | required. A label for the key, for example the project that uses it. ### Response - `id` | string | required. The id of the key. It is not a secret. - `name` | string | required. The label of the key. - `created_at` | string | required. When the key was created, RFC 3339 UTC. - `key` | string | required. The key, with the prefix `lm_live_`. Send it as `Authorization: Bearer `. It cannot be shown again. - `instructions` | string | required. What to do with the key now. ### 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. | | `403` | The request is signed with an API key. | | `409` | The account already has its one active API key. | | `429` | The key passed 300 requests a minute or 2 concurrent requests. | | `503` | The data is unavailable for a short time. | ### Example ```bash curl -sS -X POST "https://api.leadmcp.ai/v1/keys" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "LeadMCP-Version: 2026-09-19" \ -H "Content-Type: application/json" \ -d '{"name":"My project"}' ``` Response `200`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": {}, "quota": { "plan": "free", "contact_credits_used": 50, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "warnings": [] } ``` ## Revoke an API key `DELETE /keys/{id}` Revokes one API key at once: every call with it fails from then on. An unknown id, or the id of another account's key, answers 404 key.not_found. Only an OAuth session may call it; a request signed with an API key answers 403 auth.oauth_required. Free. Page: https://docs.leadmcp.ai/keys/revoke-api-key Revokes one API key at once: every call with it fails from then on. An unknown id, or the id of another account's key, answers 404 key.not_found. Only an OAuth session may call it; a request signed with an API key answers 403 auth.oauth_required. Free. Cost: Free. ### Response - `id` | string | required. The id of the key. It is not a secret. - `revoked` | boolean | required. Always true. Calls with the key fail from now on. ### 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. | | `403` | The request is signed with an API key. | | `404` | No active API key with this id on this account. | | `429` | The key passed 300 requests a minute or 2 concurrent requests. | | `503` | The data is unavailable for a short time. | ### Example ```bash curl -sS -X DELETE "https://api.leadmcp.ai/v1/keys/exp_01JBQ2W8K9V6M3T0X5Y7Z4A1BC" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "LeadMCP-Version: 2026-09-19" ``` Response `200`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": {}, "quota": { "plan": "free", "contact_credits_used": 50, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "warnings": [] } ``` ## Tools What your agent can do with the MCP server. Page: https://docs.leadmcp.ai/mcp/tools The server URL is `https://mcp.leadmcp.ai`. Every description states its cost before the model acts. ### The tools | Tool | When to use it | Cost | | --- | --- | --- | | `guide` | Explains the workflow, the filter keys and the cost of each tool | Free. | | `count_matches` | Counts the contacts that match a filter | Free. | | `find_contacts` | Returns a page of contact rows for a filter | Each returned contact costs one contact credit. | | `get_values` | Lists the allowed values of one closed-list column, with an optional search string | Free. | | `start_export` | Starts a file job for a filter and answers with the job size (rows_available and rows_to_export) | One contact credit per exported contact, charged on completion. | | `get_export` | Polls one export job and returns its status. Poll every 3 to 5 seconds until status is completed. Then it returns a download URL on our own domain that needs no key and is valid 7 days from completion. The file is gzip-compressed (.csv.gz or .jsonl.gz): download it and gunzip it, or give the link to the person when you have no shell. An expired or gone file answers 410 with the code `export.expired` | Free. | | `enrich_contact` | Matches up to 100 work emails, phone numbers or LinkedIn profile URLs to stored rows | One contact credit per stored match. A miss costs nothing. | | `get_credit_balance` | Both meters, as separate objects. contact_credits: the plan caps, what is left this month and today, and the reset time. premium_credits: this month's allowance, used, held, available and the reset time. Premium credits pay for engagers and verified emails | Free. | | `list_signals` | Every row has the same columns | Free. | | `collect_signals` | Collects one profile, post or company page once. Company followers accept max_followers (minimum 100) and finish asynchronously. One premium credit per lead | One premium credit per lead; holds twice the expected engagers, or exactly the requested company followers (without max_followers, at most the premium credits available). Premium credits are held before work starts, and a dry run is free. | | `get_collect` | Returns the status of one collect job: queued, running, completed or failed. It also returns the leads and contacts so far, the premium credits held and charged, and the list_signals arguments that read the rows. After collect_signals, call it every 30 to 60 seconds until status is completed or failed. Then read the rows with list_signals. Free | Free. | | `estimate_signals` | Returns units (expected engagers or company followers), premium_credits (premium credits reserved), available and sufficient. Includes upgrade_url only when insufficient. Company pages accept max_followers | Free. | | `create_alert` | An alert is a source of signals. Read them with `list_signals`. Watches one profile or post daily. Paid plans only, at most 100 active alerts. Answers 403 plan.required for free plans or 403 alerts.limit_reached when the cap is reached. One premium credit per new lead, held before each run | One premium credit per new lead, held before each run. Premium credits are held before work starts, and a dry run is free. | | `list_alerts` | An alert is a source of signals. Read them with `list_signals`. Lists the account's alerts with signal and lead counts and the last job state. Free | Free. | | `get_alert` | An alert is a source of signals. Read them with `list_signals`. Returns one alert and its last job. Free | Free. | | `update_alert` | An alert is a source of signals. Read them with `list_signals`. Pauses, resumes or changes the cadence of one alert. Free | Free. | | `stop_alert` | An alert is a source of signals. Read them with `list_signals`. Removes one alert. Its signals stay. Free | Free. | | `poll_alert` | An alert is a source of signals. Read them with `list_signals`. Runs one alert now. One premium credit per new lead. Answers 409 while a run is in progress | One premium credit per new lead. Premium credits are held before work starts, and a dry run is free. | | `send_feedback` | Report a problem to the LeadMCP team: a missing feature or filter, a bug, wrong data in a row, or an unclear error. Send the request_id from the response of the call that went wrong. Call it once for each problem, not after every call. It is free | Free. | | `count_companies` | Counts the companies that match a company filter | Free. | | `find_companies` | Returns a page of company records for a company filter | Each returned company costs one contact credit. | | `get_companies` | Matches up to 100 company ids, domains or company LinkedIn URLs to company records | One contact credit per match. A miss costs nothing. | | `create_api_key` | Creates the account's one API key for scripts and tools without OAuth, and returns the key once. An account has one active key: while one exists the answer is 409 key.limit_reached, so call revoke_api_key first. Works only in an OAuth session. Free | Free. | | `list_api_keys` | Lists the account's active API key with its id, name, created_at and last_used_at. Never returns the key itself. Works only in an OAuth session. Free | Free. | | `revoke_api_key` | Revokes one API key by id at once | Free. | Inputs of `count_matches`: - `filter` | Filter | required. The audience to count. - `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. - `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. Inputs of `find_contacts`: - `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. Inputs of `get_values`: - `column` | string | required. The column name, as `GET /contacts/schema` prints it, for example `seniority`. - `q` | string | optional. An optional search string that filters the values by slug or by label. - `limit` | integer | optional. The maximum number of values to return. The default is 100. Default `100`. Inputs of `start_export`: - `filter` | Filter | required. The audience to write to the file. - `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 write, in this order. Send `["*"]` for every column. - `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. - `max_rows` | integer | optional. The ceiling for this job. One job writes at most 50,000 rows, so a bigger audience needs several jobs. Default `50000`. - `format` | enum | optional. The file format. Both are gzipped. The list is frozen. Default `csv`. Values: `csv`, `jsonl`. - `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. Inputs of `get_export`: - `id` | ExportId | required. The export id, a ULID behind the prefix `exp_`. Inputs of `enrich_contact`: - `inputs` | array of string | required. Up to 100 work emails, phone numbers or LinkedIn URLs. One input matches at most one contact. - `company_domain` | string | optional. The domain of the account you sourced these people from, for example `acme.com`. When set, a match counts only if the contact's current employer is on this domain. A person we hold at another employer, or with no employer domain, answers `matched: false` with `off_domain: true`, costs nothing, and is listed on an `off_domain` warning. Without it, every match is returned and charged, whatever the employer. A value with no dot, such as a company name, returns 400 request.invalid. To find the domain of a named company, use `POST /companies/search` with `company_names`. - `mode` | enum | optional. How to answer. `stored` reads our own store and is the only working value in v1. `refresh` is reserved and returns 400 enrich.mode_unavailable until v2. Default `stored`. Values: `stored`, `refresh`. - `columns` | array of string | optional. The columns to return on a hit. Every hit also carries `id`, always first. The default is the enrichment set: name, title, company, company_domain, work_email, country, seniority, last_updated, mobile_phone, direct_phone, has_phone, function, location, email_status, company_size, company_industries, company_linkedin, linkedin. A signal row that matches a stored contact carries the same set. Send `["*"]` for every column. Inputs of `list_signals`: - `kind` | string | optional. - `alert_id` | string | optional. - `job_id` | string | optional. - `source_type` | enum | optional. Reads one kind of source: alert or collect. - `source_id` | string | optional. One alert id or collect job id. Send it only with source_type; alone it answers 400 request.invalid with field source_id. - `since` | string | optional. - `limit` | integer | optional. Default `50`. - `cursor` | string | optional. Inputs of `collect_signals`: - `kind` | enum | required. What to collect. linkedin_profile: the people who react to or comment on the 10 latest posts of one profile. linkedin_post: the people who react to or comment on one post. linkedin_company: the followers of one company page. Values: `linkedin_profile`, `linkedin_post`, `linkedin_company`. - `target` | string | required. The LinkedIn URL of the target. A profile URL, such as https://www.linkedin.com/in/. A post URL of the form https://www.linkedin.com/posts/ or https://www.linkedin.com/feed/update/urn:li:activity:. A company URL, such as https://www.linkedin.com/company/. - `max_followers` | integer | optional. For linkedin_company only: the most followers to collect, at least 100 and at most one million. Without it, a company job asks for at most the premium credits available, with a minimum of 100, and adds a max_followers_missing warning. - `wait` | boolean | optional. True keeps the request open until the rows are ready, for at most about 45 seconds, when the estimate is 500 engagers or fewer. A company page never waits. Otherwise the job runs in the background; read its rows with list_signals. - `dry_run` | boolean | optional. True returns only the estimate: units, premium_credits, available and sufficient. It holds no credits, starts no job and costs nothing. Inputs of `get_collect`: - `id` | string | required. The collect job id: job.id in the collect_signals answer. Inputs of `estimate_signals`: - `kind` | enum | required. What to collect. linkedin_profile: the people who react to or comment on the 10 latest posts of one profile. linkedin_post: the people who react to or comment on one post. linkedin_company: the followers of one company page. Values: `linkedin_profile`, `linkedin_post`, `linkedin_company`. - `target` | string | required. The LinkedIn URL of the target. A profile URL, such as https://www.linkedin.com/in/. A post URL of the form https://www.linkedin.com/posts/ or https://www.linkedin.com/feed/update/urn:li:activity:. A company URL, such as https://www.linkedin.com/company/. Inputs of `create_alert`: - `kind` | enum | required. What to watch. linkedin_profile: the people who react to or comment on the 10 latest posts of one profile. linkedin_post: the people who react to or comment on one post. Values: `linkedin_profile`, `linkedin_post`. - `target` | string | required. The LinkedIn URL of the target. A profile URL, such as https://www.linkedin.com/in/. A post URL of the form https://www.linkedin.com/posts/ or https://www.linkedin.com/feed/update/urn:li:activity:. - `cadence` | enum | optional. daily runs the alert once a day. manual never runs on its own: it runs only when you call poll_alert. The default is daily. Values: `daily`, `manual`. - `billing_choice` | enum | optional. new_only: the first run sets a baseline and charges nothing for the backlog; later runs collect and charge only new engagers. backfill_and_new: the first run also collects and charges the existing engagers. The default is backfill_and_new. Values: `backfill_and_new`, `new_only`. - `dry_run` | boolean | optional. True returns only the estimate: units, premium_credits, available and sufficient. It holds no credits, starts no job and costs nothing. Inputs of `get_alert`: - `id` | string | required. The alert id. Inputs of `update_alert`: - `id` | string | required. The alert id. - `active` | boolean | optional. False pauses the alert, and true resumes it. A paused alert does not run on its daily cadence. - `cadence` | enum | optional. daily runs the alert once a day. manual never runs on its own: it runs only when you call poll_alert. The default is daily. Values: `daily`, `manual`. Inputs of `stop_alert`: - `id` | string | required. The alert id. Inputs of `poll_alert`: - `id` | string | required. The alert id. Inputs of `send_feedback`: - `request_id` | string | required. The request_id from the response of the call that went wrong. It must be from the last 7 days. - `category` | enum | required. The kind of problem: a missing feature or filter, a bug, wrong data in a row, or an unclear error. Values: `missing_feature`, `bug`, `wrong_data`, `unclear_error`. - `tool` | string | required. The tool or route the agent called or wanted. Free text: a tool that does not exist is accepted. - `row_id` | string | optional. The id of the row with wrong data, when there is one. - `message` | string | required. What went wrong, or what the agent needed. Inputs of `count_companies`: - `filter` | CompanyFilter | required. The companies to count. - `company_ids` | array of CompanyId | optional. Company ids (`com_`) to include. Takes up to 1,000 ids. - `domains` | array of string | optional. Company domains. A company matches when any of its domains is in the list. A full URL reduces to a bare domain on input. - `exclude_domains` | array of string | optional. Company domains to exclude. A company is left out when any of its domains is in the list. - `company_names` | array of string | optional. Company 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 /companies/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 /companies/schema/industries/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. Inputs of `find_companies`: - `filter` | CompanyFilter | required. The companies to read. - `company_ids` | array of CompanyId | optional. Company ids (`com_`) to include. Takes up to 1,000 ids. - `domains` | array of string | optional. Company domains. A company matches when any of its domains is in the list. A full URL reduces to a bare domain on input. - `exclude_domains` | array of string | optional. Company domains to exclude. A company is left out when any of its domains is in the list. - `company_names` | array of string | optional. Company 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 /companies/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 /companies/schema/industries/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. - `limit` | integer | optional. The page size. The default is 50 and the maximum is 500, the same as contact search. 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. Inputs of `get_companies`: - `inputs` | array of string | required. Up to 100 company ids, domains or company LinkedIn URLs. One input matches at most one company. Response of `estimate_signals`: ```json { "ok": true, "request_id": "req_01JBQ2W8K9V6M3T0X5Y7Z4A1BC", "release": "b_2026_09_15", "data": { "units": 10, "premium_credits": 20, "available": 100, "sufficient": true }, "quota": { "plan": "free", "contact_credits_used": 50, "month_remaining": 950, "today_remaining": 950, "binding": "month", "resets_at": "9999-12-31T23:59:59.000Z" }, "warnings": [] } ``` Each worked example gives a buyer brief and the calls an agent makes for it: https://docs.leadmcp.ai/examples. ## Connect a client Add the server to your agent. Page: https://docs.leadmcp.ai/mcp/connect ### Set up from your agent in one line Paste this line into Claude Code, Codex, Cursor or another coding agent. The agent reads the setup steps, shows you a link and a code, and you click Approve in your browser. A new user creates the account on that page. ```text set up https://leadmcp.ai/SKILL.md ``` Pick your client. Each one needs the server URL, and most of them sign in with OAuth. ### Claude app Authentication: OAuth, one click. Open Settings, then Connectors. Click Add, then Add custom connector. Name it LeadMCP and paste the server URL. Click Continue. Keep the detected options: Authentication is Sign in now, and OAuth client is Register automatically. Add no headers. Click Add, then Connect, then Approve. ```bash https://mcp.leadmcp.ai ``` ### ChatGPT Authentication: OAuth, one click. Open Settings, then Plugins. Click Add, then Add MCP server. Name it LeadMCP and pick Streamable HTTP. Paste the server URL. Leave the bearer token and the headers empty. Click Save. Open the MCPs tab, click Authenticate, then click Approve. ```bash https://mcp.leadmcp.ai ``` ### Claude Code Authentication: Device flow. Register the client and open verification_uri_complete from the reply. The page shows the user_code: check that it matches, and click Approve. A client that cannot open a link prints verification_uri and user_code instead. Then poll for the token and add the server with the token on the Authorization header. ```bash curl -s -X POST https://leadmcp.ai/api/oauth/device \ -H "Content-Type: application/json" \ -d '{"client_name":"Claude Code"}' # Open verification_uri_complete, check that the page shows user_code, click Approve, then poll: curl -s -X POST https://leadmcp.ai/api/oauth/token \ -d "grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=&client_id=" claude mcp add --transport http leadmcp https://mcp.leadmcp.ai --header "Authorization: Bearer " ``` ### Codex Authentication: Device flow. Run the same device flow as Claude Code. Then add the server to ~/.codex/config.toml with the token on the Authorization header. ```bash [mcp_servers.leadmcp] url = "https://mcp.leadmcp.ai" http_headers = { Authorization = "Bearer " } ``` ### Cursor Authentication: Bearer key. Open Settings, then MCP, then Add new global MCP server. Paste this into .cursor/mcp.json with your key in the header. Save the file and turn the leadmcp server on. ```json { "mcpServers": { "leadmcp": { "url": "https://mcp.leadmcp.ai", "headers": { "Authorization": "Bearer lm_live_YOUR_API_KEY" } } } } ``` ### REST, and any other tool Authentication: Bearer key. Send the key as a bearer token on every request. Run a count before you spend contact credits: a count costs nothing and tells you how big the audience is. ```bash curl -sS -X POST "https://api.leadmcp.ai/v1/contacts/count" \ -H "Authorization: Bearer lm_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"filter":{"countries":["us"],"seniority":["vp"],"functions":["marketing_and_advertising"]}}' ``` ## Changelog What changed in the contract. Page: https://docs.leadmcp.ai/changelog | Date | Change | | --- | --- | | 2026-10-11 | The Company API: GET /companies/schema, GET /companies/schema/{column}/values, POST /companies/count, POST /companies/search and POST /companies/lookup, with the MCP tools count_companies, find_companies and get_companies. A company count is free; each returned company costs one contact credit; a lookup miss is free. Contact count, search and export take the new filter key company_ids. | | 2026-10-11 | Contacts add company_revenue, the yearly revenue range of the employer, and the filter adds the company_revenue key. The company record adds revenue_range, employee_growth_pct, specialties, investors, investor_count, and a street and a postcode on each location. | | 2026-10-05 | Job functions replaced: 21 values that follow the source data, for example engineering, information_technology, marketing_and_advertising and research_and_science. The 19 earlier values are gone. | | 2026-09-19 | Job function slugs reworded, 19 buckets kept, and skilled_trades is now trades_and_labor; signals replaces tags, four computed signals added. | | 2026-09-19 | experience, education and social carry more of the record; social names the username; tags added; last_refreshed added; role start dates are day precision. | | 2026-09-19 | Job function values are now full names, 19 values. | | 2026-09-19 | The MCP tool that returns rows is now find_contacts, and the tool that counts them is now count_matches. | | 2026-09-19 | v1 contract published. |