> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://gtm-tools.sh/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://gtm-tools.sh/_mcp/server.

# Lead Enrichment

> Take a CSV of name + company_domain rows and enrich each with the LinkedIn profile URL and verified professional email.

A worked example: take a CSV of `name,company_domain` rows and enrich each with the LinkedIn profile URL and verified professional email.

## What this example does

1. Reads a list of leads.
2. For each lead, calls `get_linkedin_profile_url` (5 credits) and `get_email` (5 credits) in parallel.
3. Writes the enriched output as JSON.

Cost: **10 credits per lead** (\~\$0.10).

## Code

```typescript
import { readFile, writeFile } from "node:fs/promises";

const KEY = process.env.GTM_TOOLS_API_KEY!;

interface Lead {
  name: string;
  company_domain: string;
  lead_id?: string;
}

interface EnrichedLead extends Lead {
  linkedin_profile_url?: string;
  email?: string;
  email_status?: string;
  email_reason?: string;
}

async function call<T>(tool: string, body: unknown): Promise<T | null> {
  const res = await fetch(`https://api.gtm-tools.sh/api/v0/${tool}`, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  if (!res.ok) {
    console.error(`${tool} ${res.status}: ${await res.text()}`);
    return null;
  }
  return res.json();
}

async function enrich(lead: Lead): Promise<EnrichedLead> {
  const [profile, email] = await Promise.all([
    call<{ url?: string }>("get_linkedin_profile_url", {
      name: lead.name,
      domain: lead.company_domain,
    }),
    call<{ email?: string; status?: string; reason?: string }>("get_email", {
      name: lead.name,
      domain: lead.company_domain,
      input_parameters: { lead_id: lead.lead_id },
    }),
  ]);

  return {
    ...lead,
    linkedin_profile_url: profile?.url,
    email: email?.email,
    email_status: email?.status,
    email_reason: email?.reason,
  };
}

async function batch(leads: Lead[], concurrency = 5): Promise<EnrichedLead[]> {
  const out: EnrichedLead[] = [];
  for (let i = 0; i < leads.length; i += concurrency) {
    const slice = leads.slice(i, i + concurrency);
    const enriched = await Promise.all(slice.map(enrich));
    out.push(...enriched);
    console.log(`enriched ${out.length}/${leads.length}`);
  }
  return out;
}

const leads: Lead[] = [
  { lead_id: "1", name: "Justin Mares", company_domain: "kettleandfire.com" },
  { lead_id: "2", name: "Sara Blakely", company_domain: "spanx.com" },
  { lead_id: "3", name: "Brian Chesky", company_domain: "airbnb.com" },
];

const enriched = await batch(leads);
await writeFile("./out.json", JSON.stringify(enriched, null, 2));
```

## Sample output

```json
[
  {
    "lead_id": "1",
    "name": "Justin Mares",
    "company_domain": "kettleandfire.com",
    "linkedin_profile_url": "https://linkedin.com/in/justinmares",
    "email": "justin@kettleandfire.com",
    "email_status": "ok"
  },
  {
    "lead_id": "2",
    "name": "Sara Blakely",
    "company_domain": "spanx.com",
    "linkedin_profile_url": "https://linkedin.com/in/sarablakely",
    "email": "sara@spanx.com",
    "email_status": "ok"
  }
]
```

## Notes

* **Concurrency**: 5 is safe. Going above 10 risks `429`s on the email endpoint.
* **Non-ok results**: `email` is `null` whenever `status` isn't `ok`, and the `reason` says why (`catch_all`, `no_mx`, `unverifiable_provider`, `no_pattern_verified`). Branch on those rather than treating a missing address as a transient failure. See [Why is get_email returning not_found?](/knowledge-base/troubleshooting/email-not-found).
* **Failed calls still cost**: the credit cost is debited before the tool reaches its provider, so an upstream `5xx` or a `try_again_later` is charged. Honor `retry_after_seconds` instead of retrying tightly.
* **Cost**: 10 credits per lead × 100 leads = 1,000 credits = **\$10**.

## Next steps

#### [Outbound](/outbound)

Add signal qualification before enrichment to cut unqualified spend.

#### [Signal Pipeline Example](/signal-pipeline)

Daily detector sweep across a target list.