# B2B People and Company Search API - GTM Database (`nabeelbaghoor/gtm-database-search-api`) Actor

Search a go-to-market database of people and companies with a query language that reaches across from a person to their employer and back: current title, seniority, headcount, industry, revenue, funding and technologies in one expression. Also runs your own enrichment routines. Pay per result.

- **URL**: https://apify.com/nabeelbaghoor/gtm-database-search-api.md
- **Developed by:** [Nabeel Hassan](https://apify.com/nabeelbaghoor) (community)
- **Categories:** Business, Developer tools, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$10.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## B2B People and Company Search API - GTM Database

Ask one question that reaches from a person to their employer and back. Current software engineers at companies over five hundred people running a particular vendor, in one expression. One flat row per record.

### What this actor does

- **Searches across entities, not within one.** The query language lets a people search test facts about the employer, and a company search test facts about the staff. `select from people where experiences.any(is_current = true and job_title is_similar_to ("engineer") and company.technographics.any(vendor = "Salesforce"))` is a single query. A filter list cannot express that, which is why this is the default mode.
- **Passes your query through untouched.** Nested parentheses, `and`, `or`, `not`, similarity matching and cross-entity clauses all reach the provider exactly as written. Nothing here rewrites, quotes or splits a query, because the provider validates it far better than this actor could and a rewritten boolean expression is a silently different search.
- **Writes out the vocabulary rather than guessing it.** This provider serves its filter names at run time rather than publishing them, and they change. Two modes exist purely to fetch that: one writes every accepted filter for people or companies into the dataset with its type, allowed values and usage guidance, the other writes out the query language grammar. Run one, read the result, then build your search from names that actually exist.
- **Drains a search properly.** A search here is created once and then pulled from a stateful iterator: there is no page number, and the only honest signal that more remain is the flag the provider returns. The run reads that flag rather than inferring the end from a short page.
- **Tells you when the provider stopped early.** There is a difference between a search that ran out of matching records and one the provider capped at your plan's query limit, and only one of them means you have seen everything. The run says which happened.
- **Runs your own saved routines.** Point it at a provider-managed enrichment function or a custom function from your workspace, give it rows of input values, and it runs them in batches, waits for the asynchronous work, and writes one row per item with the result or the reason it failed. Your own id on a row comes back with the result so it ties to your record.
- **Treats work in progress as progress.** The routine endpoint answers with an in-progress status while it works. Reading that as a failure is the usual way an integration with this provider reports a successful empty run; this one polls until the work is actually done.
- **Reports what your allowance has left.** The provider returns the period quota alongside the data, and the run logs what remains and when it resets.
- **Never charges for a miss.** Only rows carrying a resolved record are billed.

### Input

| Field | What it does |
| --- | --- |
| `mode` | Advanced query, structured filters, filter fields, query reference, or routine. |
| `sourceType` | People or companies, for the filters and fields modes. |
| `searchQuery` | The query, in the provider's own language. |
| `filters` | A JSON filters object, for structured filters mode. |
| `jobTitleKeywords` | Shortcut for the job title filter. |
| `locationCities` | Shortcut for the city filter. |
| `companyIndustries` | Shortcut for the company industry filter. |
| `routineId` | Which enrichment function to run. |
| `routineItems` | The rows to run it over. |
| `requireLinkedinUrl` | Keep only rows with a profile URL. |
| `requireCompanyDomain` | Keep only companies with a domain. |
| `countries` | Keep only these countries. |
| `reportWorkspace` | Log which workspace the key belongs to. |
| `pageSize` | Records drawn per request, up to 500. |
| `requestsPerMinute` | Pace the run under your plan's rate limit. |
| `routineTimeoutMinutes` | Give up waiting for a routine batch. |
| `skipNotFound` | Leave misses and refusals out of the dataset. |
| `maxResults` | Hard cap on rows, and therefore on spend. |
| `apiKey` | Your own API key. Stored as a secret. |

### Example output

```json
{
  "found": true,
  "mode": "query",
  "recordType": "person",
  "personId": 42,
  "fullName": "Jane Doe",
  "firstName": "Jane",
  "lastName": "Doe",
  "linkedinUrl": "https://www.linkedin.com/in/example",
  "jobTitle": "Software Engineer",
  "companyName": "Example Corp",
  "jobStartDate": "2024-01-01",
  "jobEndDate": null,
  "jobLocation": "New York, New York, United States",
  "locationName": "New York, New York, United States",
  "city": "New York",
  "state": "New York",
  "country": null,
  "matchedExperiences": [
    {
      "company": "Example Corp",
      "title": "Software Engineer",
      "location": "New York, New York, United States",
      "startDate": "2024-01-01",
      "endDate": null
    }
  ],
  "error": null,
  "raw": { }
}
```

### Frequently asked questions

#### What can the query language do that a filter list cannot?

Reach across entities and nest logic. A filter list can say "job title contains engineer" and "company has over 500 employees" as two independent conditions. The query language can say "has a current experience whose title is similar to engineer at a company that has over 500 employees and uses this vendor", which is one condition about one experience rather than three conditions that happen to be true of the same person. It also takes parentheses, so alternatives can be grouped inside a requirement: two acceptable titles, but only at companies of a certain size.

#### How do I learn the fields I can query?

Run the query reference mode. It fetches the provider's own grammar and field reference with your key and writes it into the dataset, so you can read exactly what is queryable rather than guessing. For the older structured filters mode, run the filter fields mode instead: it writes out every accepted filter name for people or for companies, with its type, its allowed values and the provider's guidance on how it behaves.

#### Why does the actor not just list the filters in its own input form?

Because this provider does not publish them. The filter vocabulary is served at run time, per record type, and it changes. Hard-coding a list of plausible filter names would produce a search that quietly ignored half of them, which is worse than asking you to look them up: an ignored filter widens your search and you pay for the extra rows without ever being told. Three filter names appear in the provider's own documented examples and are offered as convenience fields; everything else goes through the filters object.

#### What is the difference between the two search modes?

The advanced query is newer, in beta, and far more expressive. Structured filters are the older JSON form, still supported, and the right choice if you already have filter objects saved from somewhere else or you want a simple keyword-and-location search. They hit different endpoints and both are here.

#### What does has\_more actually mean, and why does it matter?

The search endpoint is an iterator with state at the provider. Each page comes back with a flag saying whether more remain. That flag is the only reliable stopping condition: a short page does not mean the end, and there is no total to count against. This actor reads the flag, and when the provider stops for its own reasons rather than because the results ran out, it says so, because "your plan will not return more this period" and "there are no more" look identical in an empty page.

#### What is a routine and why would I run one here?

A routine is saved logic in your own workspace: a provider-managed enrichment function, or a custom function you built. Running it through this actor means you can point it at a list of rows, have the batching, the waiting and the retrying handled, and get one dataset row per item with either the result or the reason it failed. Your own id on an input row comes back attached to its result, so the output joins straight back to your records.

#### Do the search results include email addresses?

No. This provider's search returns profile and firmographic data; contact details come from its enrichment routines, which are a separate call with their own cost. That separation is the provider's own and this actor keeps it, so a search is never billed as an enrichment. Use the routine mode with an enrichment function when you want contact details for the rows you kept.

#### Are the routes in this actor verified against the live service?

They come from the provider's own published API document rather than from probing, and that is worth saying plainly. This provider's gateway checks the credential before it decides whether a path exists, so a wrong key and an invented route both answer the same way and probing proves nothing about either. Everywhere that probing can distinguish them, it is used; here it cannot, so the published document is the source.

#### Do I need my own API key?

Yes. This actor does not include data access. You use your own key from Clay, the provider whose go-to-market database API this actor calls, created under Settings, then Account, then API keys in your workspace, and pasted into the `apiKey` field, where it is stored as an Apify secret. It travels in a request header and is never written to the log. Your own plan, allowance and terms apply, and some parts of this API are limited to particular plans.

#### How much does a run cost?

Pricing is pay per result: you are charged for each record resolved into the dataset, and never for items the provider returned nothing for, for inputs refused before they were sent, for rows dropped by the filters, or for duplicates. Apify platform usage is included in the per-result price. Your own provider allowance is separate and billed by them.

### Keyword map

B2B people search API, company search API, GTM database, go to market data, cross entity search, boolean search query language, prospect search API, account search API, firmographic data, technographic search, employee count filter, annual revenue data, total funding data, job title search, seniority search, enrichment routine, waterfall enrichment automation, sales intelligence API, lead list building, ICP account list, CRM enrichment pipeline, contact data enrichment, agent accessible data API, headless GTM workflow

# Actor input Schema

## `mode` (type: `string`):

The advanced query is the expressive one: a query language with nested boolean logic and criteria that reach across from a person to their employer and back. Structured filters are the older JSON form. The two reference modes write out the vocabulary this provider accepts, which is served at run time rather than published, so run one of them first if you are building a search by hand.

## `sourceType` (type: `string`):

Which records to search or describe, for the structured filters and filter fields modes. The advanced query works this out for itself from the query you write, so this is ignored there.

## `searchQuery` (type: `string`):

In advanced query mode, the query to run, written in the provider own language. It reads like a sentence: select from people where experiences.any(is\_current = true and job\_title is\_similar\_to ("head of sales") and company.estimated\_employee\_count >= 500). It supports parentheses, and, or, not, and criteria that cross from a person to their employer with experiences.any(...) or from a company to its staff with people.exists(...). Passed through exactly as written.

## `filters` (type: `object`):

In structured filters mode, the filters to apply, as a JSON object of filter name to value. The filter names this provider accepts are not published: run the filter fields mode with your own key to write the whole vocabulary into a dataset, then use the names it gives you. Anything set here wins over the three convenience fields below.

## `jobTitleKeywords` (type: `array`):

A shortcut for the job title filter in structured filters mode, when searching people. One phrase per line. Ignored if you set the same filter by hand in the filters object above.

## `locationCities` (type: `array`):

A shortcut for the city filter in structured filters mode, when searching people. One city per line.

## `companyIndustries` (type: `array`):

A shortcut for the industry filter in structured filters mode, when searching companies. One industry per line.

## `routineId` (type: `string`):

In routine mode, the id of the enrichment function to run. This is one of the provider own managed functions, or a custom function you have saved in your workspace. Take the id from the provider interface or its command line tool.

## `routineItems` (type: `array`):

In routine mode, the rows to run the function over, as a list of JSON objects of input values, for example {"domain": "example.com"}. Up to 100 per batch, and larger lists are split into batches for you. Give an object an "id" of your own to tie the result back to your record; one is generated from the row position otherwise.

## `requireLinkedinUrl` (type: `boolean`):

Drop rows carrying no professional network profile URL.

## `requireCompanyDomain` (type: `boolean`):

Drop company rows where the website domain is unknown. A domain is what most downstream enrichment needs.

## `countries` (type: `array`):

Keep only rows whose country matches one of these, compared without regard to case. Leave empty to keep every country.

## `reportWorkspace` (type: `boolean`):

Log which workspace this key belongs to before the run starts. Useful when you hold keys for more than one. Informational only.

## `pageSize` (type: `integer`):

How many records to draw from the provider at a time, up to its ceiling of 500. Larger pages mean fewer requests; smaller pages mean the run stops closer to your result cap.

## `requestsPerMinute` (type: `integer`):

Pace the run so it stays under whatever rate your plan allows.

## `routineTimeoutMinutes` (type: `integer`):

Give up waiting for one routine batch after this long. The batch keeps running at the provider, so nothing is lost. Enrichment functions call out to other services, so a full batch takes minutes rather than seconds.

## `skipNotFound` (type: `boolean`):

Leave items the provider returned nothing for, and inputs refused before they were sent, out of the dataset. Off by default, because knowing which of your rows failed is usually the point. Misses are never charged for either way.

## `maxResults` (type: `integer`):

Stop after this many rows. This is the cap on both spend and run time.

## `apiKey` (type: `string`):

Your own API key for the go-to-market database API, created under Settings, then Account, then API keys in your provider workspace. Sent in a request header and never written to the log. Stored as a secret.

## Actor input object example

```json
{
  "mode": "query",
  "sourceType": "people",
  "searchQuery": "select from people where experiences.any(is_current = true and job_title is_similar_to (\"head of sales\"))",
  "filters": {},
  "jobTitleKeywords": [],
  "locationCities": [],
  "companyIndustries": [],
  "routineItems": [],
  "requireLinkedinUrl": false,
  "requireCompanyDomain": false,
  "countries": [],
  "reportWorkspace": false,
  "pageSize": 100,
  "requestsPerMinute": 120,
  "routineTimeoutMinutes": 20,
  "skipNotFound": false,
  "maxResults": 100
}
```

# Actor output Schema

## `results` (type: `string`):

One row per record. People carry the name, profile URL, current or matched role, employer and structured location, with every experience the query matched. Companies carry the name, domain, size, type, industry, revenue band, total funding range, location and profile URL. The reference modes carry the provider's own filter vocabulary and query grammar.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchQuery": "select from people where experiences.any(is_current = true and job_title is_similar_to (\"head of sales\"))",
    "filters": {},
    "jobTitleKeywords": [],
    "locationCities": [],
    "companyIndustries": [],
    "routineItems": [],
    "countries": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("nabeelbaghoor/gtm-database-search-api").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "searchQuery": "select from people where experiences.any(is_current = true and job_title is_similar_to (\"head of sales\"))",
    "filters": {},
    "jobTitleKeywords": [],
    "locationCities": [],
    "companyIndustries": [],
    "routineItems": [],
    "countries": [],
}

# Run the Actor and wait for it to finish
run = client.actor("nabeelbaghoor/gtm-database-search-api").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchQuery": "select from people where experiences.any(is_current = true and job_title is_similar_to (\\"head of sales\\"))",
  "filters": {},
  "jobTitleKeywords": [],
  "locationCities": [],
  "companyIndustries": [],
  "routineItems": [],
  "countries": []
}' |
apify call nabeelbaghoor/gtm-database-search-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nabeelbaghoor/gtm-database-search-api"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/UN6OrHzTS3eKrTmaT/builds/SUZ6Q8S0VBu8kFcK8/openapi.json
