Lead Enrichment

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 tokens) and get_email (5 tokens) in parallel.
  3. Writes the enriched output as JSON.

Cost: 10 tokens per lead (~$0.10).

Code

1import { readFile, writeFile } from "node:fs/promises";
2
3const KEY = process.env.GTM_ENGINE_API_KEY!;
4
5interface Lead {
6 name: string;
7 company_domain: string;
8 lead_id?: string;
9}
10
11interface EnrichedLead extends Lead {
12 linkedin_profile_url?: string;
13 email?: string;
14 is_catch_all?: boolean;
15}
16
17async function call<T>(server: "socials" | "data", tool: string, body: unknown): Promise<T | null> {
18 const res = await fetch(`https://${server}.gtm-engine.sh/api/v0/${tool}`, {
19 method: "POST",
20 headers: {
21 "Authorization": `Bearer ${KEY}`,
22 "Content-Type": "application/json",
23 },
24 body: JSON.stringify(body),
25 });
26 if (!res.ok) {
27 console.error(`${tool} ${res.status}: ${await res.text()}`);
28 return null;
29 }
30 return res.json();
31}
32
33async function enrich(lead: Lead): Promise<EnrichedLead> {
34 const [profile, email] = await Promise.all([
35 call<{ url?: string }>("socials", "get_linkedin_profile_url", {
36 name: lead.name,
37 domain: lead.company_domain,
38 }),
39 call<{ email?: string; is_catch_all?: boolean }>("data", "get_email", {
40 name: lead.name,
41 domain: lead.company_domain,
42 input_parameters: { lead_id: lead.lead_id },
43 }),
44 ]);
45
46 return {
47 ...lead,
48 linkedin_profile_url: profile?.url,
49 email: email?.email,
50 is_catch_all: email?.is_catch_all,
51 };
52}
53
54async function batch(leads: Lead[], concurrency = 5): Promise<EnrichedLead[]> {
55 const out: EnrichedLead[] = [];
56 for (let i = 0; i < leads.length; i += concurrency) {
57 const slice = leads.slice(i, i + concurrency);
58 const enriched = await Promise.all(slice.map(enrich));
59 out.push(...enriched);
60 console.log(`enriched ${out.length}/${leads.length}`);
61 }
62 return out;
63}
64
65const leads: Lead[] = [
66 { lead_id: "1", name: "Justin Mares", company_domain: "kettleandfire.com" },
67 { lead_id: "2", name: "Sara Blakely", company_domain: "spanx.com" },
68 { lead_id: "3", name: "Brian Chesky", company_domain: "airbnb.com" },
69];
70
71const enriched = await batch(leads);
72await writeFile("./out.json", JSON.stringify(enriched, null, 2));

Sample output

1[
2 {
3 "lead_id": "1",
4 "name": "Justin Mares",
5 "company_domain": "kettleandfire.com",
6 "linkedin_profile_url": "https://linkedin.com/in/justinmares",
7 "email": "justin@kettleandfire.com",
8 "is_catch_all": false
9 },
10 {
11 "lead_id": "2",
12 "name": "Sara Blakely",
13 "company_domain": "spanx.com",
14 "linkedin_profile_url": "https://linkedin.com/in/sarablakely",
15 "email": "sara@spanx.com",
16 "is_catch_all": false
17 }
18]

Notes

  • Concurrency: 5 is safe. Going above 10 risks 429s on the email endpoint.
  • Catch-all handling: When is_catch_all is true, downstream code should treat the email as a hypothesis. See Email Finding.
  • Refunds: If the upstream provider 5xx’s, the token reservation is automatically refunded.
  • Cost: 10 tokens per lead × 100 leads = 1,000 tokens = $10.

Next steps