Skip to content
LeadMCPDocs

Worked examples

Buyer briefs, and the calls an agent makes for each one.

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. Count first: a count is free. Read the Filters page for every key.

A country, a seniority and a function

We sell to consulting leaders in the United States: size the audience, then show me a few of them.

count_matchesPOST /contacts/count

POST /contacts/count
{
  "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_contactsPOST /contacts/search

POST /contacts/search
{
  "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

Show me everyone we can reach by email at our three named target accounts.

count_matchesPOST /contacts/count

POST /contacts/count
{
  "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_contactsPOST /contacts/search

POST /contacts/search
{
  "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

Find the people who decide at one named account, with a work email.

count_matchesPOST /contacts/count

POST /contacts/count
{
  "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_contactsPOST /contacts/search

POST /contacts/search
{
  "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

Give me up to three leaders at each of our target accounts, in one list.

count_matchesPOST /contacts/count

POST /contacts/count
{
  "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_contactsPOST /contacts/search

POST /contacts/search
{
  "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

I have one contact at an account: find the other leaders there who would also buy.

count_matchesPOST /contacts/count

POST /contacts/count
{
  "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_contactsPOST /contacts/search

POST /contacts/search
{
  "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

US directors and managers, but not at our competitor and not in banking.

get_valuesGET /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_matchesPOST /contacts/count

POST /contacts/count
{
  "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_contactsPOST /contacts/search

POST /contacts/search
{
  "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

Engineering leaders in the United States without either of two named skills.

count_matchesPOST /contacts/count

POST /contacts/count
{
  "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_contactsPOST /contacts/search

POST /contacts/search
{
  "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

C-level people at US companies of 201 to 500 staff that raised a Series B.

get_valuesGET /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_matchesPOST /contacts/count

POST /contacts/count
{
  "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_contactsPOST /contacts/search

POST /contacts/search
{
  "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

US sales leaders who started a new job recently, because they buy in their first quarter.

count_matchesPOST /contacts/count

POST /contacts/count
{
  "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_contactsPOST /contacts/search

POST /contacts/search
{
  "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

A software list that is not all from one account: at most two people per company.

count_matchesPOST /contacts/count

POST /contacts/count
{
  "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_contactsPOST /contacts/search

POST /contacts/search
{
  "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

Read a US audience page by page, and never show me the same person twice.

find_contactsPOST /contacts/search

POST /contacts/search
{
  "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_contactsPOST /contacts/search

POST /contacts/search
{
  "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

Write me a file of US contacts with a work email, but cap this job at 25 rows.

count_matchesPOST /contacts/count

POST /contacts/count
{
  "filter": {
    "countries": [
      "us"
    ],
    "has_work_email": true
  }
}

The size of the audience, free, before any row is spent.

start_exportPOST /exports

POST /exports
{
  "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_exportGET /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

Fill in the record for a LinkedIn profile I already have.

enrich_contactPOST /enrichments

POST /enrichments
{
  "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_contactPOST /enrichments

POST /enrichments
{
  "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.