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

# List Building Playbook

# List Building Playbook

> Build a fresh, enriched prospecting list across your entire ICP — from zero to CRM-ready in one workflow.

<div style={{position:'absolute',width:'1px',height:'1px',padding:0,margin:'-1px',overflow:'hidden',clipPath:'inset(50%)',whiteSpace:'nowrap',border:0}}>
  > **Agents & LLMs**: a Markdown version of this page is available by appending `.md` to the URL, and the full documentation index is at [llms.txt](https://docs.blitz-api.ai/llms.txt).
</div>

Most "list building" workflows are painful because they require **two passes**: first build a company list (Company Search), then loop over each company to fetch employees (Employee Finder), then enrich each person. **Find People collapses the first two steps into one call.**

This recipe shows how to go from a blank ICP definition to a fully-enriched prospecting list ready to push to your CRM or sequencer.

***

## What You'll Build

A repeatable pipeline that:

1. Takes a single ICP definition (industry + size + geography + persona).
2. Calls **Find People** with cursor-based pagination to collect every matching decision-maker.
3. Enriches each person with a verified work email (and optionally a US phone).
4. Outputs a clean CSV / CRM payload.

***

## Step 1 — Define the ICP

The ICP lives entirely in the Find People request body. Combine **company filters** (who you sell to) with **people filters** (who you talk to inside those companies).

```json theme={null} theme={null}
{
  "company": {
    "industry": { "include": ["IT Services and IT Consulting", "Software Development"] },
    "employee_range": ["51-200", "201-500"],
    "hq": { "sales_region": ["EMEA"] },
    "type": { "include": ["Privately Held"] }
  },
  "people": {
    "job_level": ["VP", "Director"],
    "job_function": ["Sales & Business Development", "Advertising & Marketing"],
    "min_connections": 200
  },
  "max_results": 50
}
```

<Tip>
  Iterate this JSON like you would iterate a SQL query. Loosen one filter at a time (`employee_range`, then `industry`, then `min_connections`) until your `total_results` lands in the right ballpark for your campaign volume.
</Tip>

***

## Step 2 — Paginate with the cursor

Find People uses **cursor-based** pagination, but the SDK handles it — iterate (or `collect()`) the returned page and it fetches each subsequent page for you. Each page holds up to `max_results` (max `50`).

<CodeGroup>
  ```javascript Node.js theme={null} theme={null}
  async function buildIcpList(icp, maxItems = 1000) {
    // The SDK paginates automatically; collect() gathers every match into an array.
    // max_results is only the page size — max_items is the client-side total cap that
    // actually bounds spend (on the Trial plan the API bills 1 credit per result; Unlimited plans are flat-rate).
    return client.search.people({ ...icp, max_results: 50, max_items: maxItems }).collect();
  }
  ```

  ```python Python theme={null} theme={null}
  def build_icp_list(icp, max_items=1000):
      # The SDK paginates automatically; auto_paging_iter streams every match across pages.
      # max_results is only the page size — max_items is the client-side total cap that
      # actually bounds spend (on the Trial plan the API bills 1 credit per result; Unlimited plans are flat-rate).
      return list(
          client.search.people(**icp, max_results=50).auto_paging_iter(max_items=max_items)
      )
  ```
</CodeGroup>

***

## Step 3 — Enrich emails (and phones)

Find People returns LinkedIn profile URLs but no contact points. Pass each `linkedin_url` to the enrichment endpoints.

<Steps>
  <Step title="Work email">
    `POST /v2/enrichment/email` with `person_linkedin_url`. Returns a verified email or `found: false`.
  </Step>

  <Step title="Mobile phone (US only)">
    `POST /v2/enrichment/phone` with `person_linkedin_url`. Skip people whose `location.country_code` is not `US`.
  </Step>
</Steps>

<Note>
  Emails returned by `/v2/enrichment/email` are already verified at source — they're re-tested against mail servers at least once every 30 days. You can push them straight to your sequencer without an additional validation step.
</Note>

```javascript theme={null} theme={null}
async function enrichPerson(person) {
  const email = await client.enrichment.email({
    person_linkedin_url: person.linkedin_url,
  });

  const phone =
    person.location?.country_code === "US"
      ? await client.enrichment.phone({
          person_linkedin_url: person.linkedin_url,
        })
      : null;

  return { ...person, email: email.email ?? null, phone: phone?.phone ?? null };
}
```

<Note>
  The SDK enforces the `5 RPS` per-endpoint rate limit for you (a single client instance stays under your limit on each endpoint) and retries automatically on `429`. Reuse one `client` across your enrichment calls — see [Configuration](/sdks/typescript#configuration).
</Note>

***

## Step 4 — Output for CRM / Sequencer

Map the Find People + enrichment payload to the columns your CRM expects:

| CRM Field      | Source                                                      |
| :------------- | :---------------------------------------------------------- |
| `first_name`   | `person.first_name`                                         |
| `last_name`    | `person.last_name`                                          |
| `title`        | `person.experiences[0].job_title` (current role)            |
| `company`      | `person.experiences[0].company_linkedin_url` → enrich later |
| `linkedin_url` | `person.linkedin_url`                                       |
| `email`        | `enrichment.email`                                          |
| `phone`        | `enrichment.phone` (US only)                                |
| `country`      | `person.location.country_code`                              |
| `seniority`    | derived from `experiences[0].job_title`                     |

***

## When to Re-Run

Re-run the same ICP definition on a schedule (weekly or monthly) to capture **net-new** decision-makers as people change roles. Diff against your existing CRM by `linkedin_url` to insert only new contacts.

For *cleaning* existing CRM contacts (vs. sourcing new ones), use the [CRM Hygiene Playbook](/guide/recipes/crm-hygiene) instead.

***

<CardGroup cols={2}>
  <Card title="Find People (concept)" icon="users-viewfinder" href="/guide/concepts/find-people">
    Full filter reference and response schema.
  </Card>

  <Card title="Account Breakthrough" icon="bullseye-arrow" href="/guide/recipes/enrichment-workflow">
    Need to penetrate a *named* account list instead? Use the Waterfall recipe.
  </Card>

  <Card title="CRM Hygiene" icon="broom" href="/guide/recipes/crm-hygiene">
    Clean and re-enrich your existing contacts on a schedule.
  </Card>

  <Card title="Field Normalization" icon="list-check" href="/guide/reference/normalization">
    Case-sensitive enums for industry, job level, job function, sales region.
  </Card>
</CardGroup>
