API Docs Get API key
Developer Tutorial

How to Enrich Leads With an API

A practical, copy-paste guide to turning a thin lead list into full contact records — email, phone, LinkedIn, and firmographics — using one REST endpoint. Every call is 1 credit, with no per-result multipliers.

Lead enrichment is the process of taking the little you know about a prospect — a name, an email, a company, a LinkedIn URL — and filling in everything you're missing so your CRM records are complete and your outreach actually lands. Doing it by hand doesn't scale. Doing it with an API does.

This tutorial walks through enriching leads with the LinkFinder AI API end to end: authentication, your first call, chaining lookups into a full contact record, bulk-enriching a CSV, surviving rate limits, and pushing results into a CRM. Everything runs against a single endpoint — POST https://api.linkfinderai.com — and you select what you want with a type field. One request costs one credit, including lookups that come back empty, so cost is easy to reason about up front.

Examples are shown in cURL, Python, and Node.js. You only need the free tier (100 credits) to follow along.

1

Get your API key

Create a free account and grab your key from the dashboard under Settings → API Key. The free trial includes 100 credits, which is plenty to test the full enrichment flow below.

Treat the key like a password. Set it as an environment variable rather than hardcoding it, and never ship it in client-side JavaScript or commit it to a repo.

# Add to your shell profile or a .env file (never commit it)
export LINKFINDER_API_KEY="lf_live_your_key_here"
import os
API_KEY = os.environ["LINKFINDER_API_KEY"]
BASE_URL = "https://api.linkfinderai.com"
const API_KEY = process.env.LINKFINDER_API_KEY;
const BASE_URL = "https://api.linkfinderai.com";
2

Make your first enrichment call

Every request is a POST to the same URL with two fields: type (which enrichment you want) and input_data (what you already know). Pass your key in the Authorization: Bearer header.

Start with the most representative enrichment — pass a person's LinkedIn URL and get their full structured profile back (name, title, company, location) in one call:

curl -X POST "https://api.linkfinderai.com" \
     -H "Content-Type: application/json" \
     -H "Authorization: Bearer $LINKFINDER_API_KEY" \
     -d '{
       "type": "linkedin_profile_to_linkedin_info",
       "input_data": "https://linkedin.com/in/john-doe"
     }'
import os, requests

API_KEY = os.environ["LINKFINDER_API_KEY"]

resp = requests.post(
    "https://api.linkfinderai.com",
    headers={
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
    },
    json={
        "type": "linkedin_profile_to_linkedin_info",
        "input_data": "https://linkedin.com/in/john-doe",
    },
)

data = resp.json()
print(data["status"], data["result"])  # success {full_name, job_title, company_name, ...}
const API_KEY = process.env.LINKFINDER_API_KEY;

const resp = await fetch("https://api.linkfinderai.com", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    type: "linkedin_profile_to_linkedin_info",
    input_data: "https://linkedin.com/in/john-doe",
  }),
});

const data = await resp.json();
console.log(data.status, data.result); // success { full_name, job_title, company_name, ... }
Don't have a LinkedIn URL yet? Start from what you do have. Use lead_full_name_to_linkedin_url (name + company), email_to_linkedin_url (reverse lookup from an email), or company_name_to_website to resolve a company first.
3

Read the response shape

Single-value enrichments return a small, predictable object. Always branch on status first — a 200 response can still carry status: "error" with a null result when no match was found.

{
  "result": "[email protected]",
  "status": "success"
}
{
  "result": null,
  "status": "error",
  "message": "Email not found"
}

Richer endpoints return more. linkedin_profile_to_linkedin_info gives a full profile, and the natural-language leads_finder_ai endpoint returns an array of fully-formed lead objects in one call — handy when you want to source and enrich at the same time:

{
  "full_name": "Sarah Mitchell",
  "job_title": "VP of Sales",
  "seniority_level": "vp",
  "email": "[email protected]",
  "linkedin": "https://linkedin.com/in/sarah-mitchell-sales",
  "company_name": "CloudCore",
  "company_domain": "cloudcore.io",
  "company_size": "51-200",
  "industry": "Software",
  "city": "Austin",
  "state": "Texas",
  "country": "United States"
}
A lookup that returns null still costs 1 credit. Cache results and de-duplicate your input list before you start spending.
4

Build a full enriched lead record

Real enrichment is a chain. You rarely have everything you need from one call, so you resolve an identifier first and then append the rest. A common pattern: start with a name + company, find the LinkedIn URL, then append email, phone, and firmographics.

Here's a reusable Python function that does exactly that. It posts to the one endpoint, switches on type, and gracefully skips fields it can't find:

import os, requests

API_KEY = os.environ["LINKFINDER_API_KEY"]
BASE_URL = "https://api.linkfinderai.com"

def call(enrichment_type, input_data, **extra):
    """One request = one credit. Returns result or None."""
    resp = requests.post(
        BASE_URL,
        headers={
            "Authorization": f"Bearer {API_KEY}",
            "Content-Type": "application/json",
        },
        json={"type": enrichment_type, "input_data": input_data, **extra},
        timeout=30,
    )
    resp.raise_for_status()
    data = resp.json()
    return data.get("result") if data.get("status") == "success" else None

def enrich_lead(full_name, company):
    lead = {"full_name": full_name, "company": company}

    # 1) Resolve a LinkedIn URL from name + company
    profile_url = call("lead_full_name_to_linkedin_url", f"{full_name} {company}")
    if not profile_url:
        return lead  # nothing to chain off of

    lead["linkedin"] = profile_url

    # 2) Append contact + firmographic data off that profile
    lead["email"] = call("linkedin_profile_to_email", profile_url)
    lead["phone"] = call("linkedin_profile_to_phone", profile_url)
    lead["profile"] = call("linkedin_profile_to_linkedin_info", profile_url)
    lead["website"] = call("company_name_to_website", company)

    return lead

print(enrich_lead("Sarah Mitchell", "CloudCore"))
const API_KEY = process.env.LINKFINDER_API_KEY;
const BASE_URL = "https://api.linkfinderai.com";

async function call(type, input_data, extra = {}) {
  const resp = await fetch(BASE_URL, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ type, input_data, ...extra }),
  });
  const data = await resp.json();
  return data.status === "success" ? data.result : null;
}

async function enrichLead(fullName, company) {
  const lead = { fullName, company };

  const profileUrl = await call("lead_full_name_to_linkedin_url", `${fullName} ${company}`);
  if (!profileUrl) return lead;

  lead.linkedin = profileUrl;
  lead.email   = await call("linkedin_profile_to_email", profileUrl);
  lead.phone   = await call("linkedin_profile_to_phone", profileUrl);
  lead.profile = await call("linkedin_profile_to_linkedin_info", profileUrl);
  lead.website = await call("company_name_to_website", company);

  return lead;
}

console.log(await enrichLead("Sarah Mitchell", "CloudCore"));

That single lead used five credits (one per call). If a step returns null, the chain still continues — you just store what you have. To enrich companies in bulk instead of people, swap in company_domain_to_employees with department and seniority filters.

5

Bulk-enrich a CSV of leads

The single-lead function scales straight to a list. Read a CSV, enrich each row, and write the result back out. The only thing to respect is your plan's requests-per-second limit, so add a small delay (or a token-bucket) between calls.

import csv, time, json

INPUT = "leads.csv"     # columns: full_name, company
OUTPUT = "leads_enriched.csv"
REQS_PER_SEC = 5        # match your plan (Starter = 5/s)
DELAY = 1.0 / REQS_PER_SEC

with open(INPUT, newline="") as f_in, open(OUTPUT, "w", newline="") as f_out:
    reader = csv.DictReader(f_in)
    fields = ["full_name", "company", "linkedin", "email", "phone", "website"]
    writer = csv.DictWriter(f_out, fieldnames=fields, extrasaction="ignore")
    writer.writeheader()

    for row in reader:
        lead = enrich_lead(row["full_name"], row["company"])
        writer.writerow(lead)
        print(f"enriched: {lead['full_name']} -> {lead.get('email')}")
        time.sleep(DELAY)   # stay under the rate limit

print("Done.")
De-duplicate leads.csv first and skip rows you've already enriched. Since empty lookups still cost a credit, a clean input list is the single biggest lever on your bill.
6

Handle errors & rate limits

Production enrichment runs unattended, so handle the failure cases explicitly. These are the status codes you'll actually hit:

CodeMeaningWhat to do
200SuccessCheck result — it may be null (still 1 credit)
401UnauthorizedMissing/invalid key — check the Authorization header
402Insufficient creditsTop up or wait for the next billing cycle
422Invalid requestVerify the type value and input_data format
429Rate limit exceededBack off exponentially: 1s, 2s, 4s
500Server errorRetry after ~30s; contact support if it persists

Wrap your request helper with retry-and-backoff so a burst of 429s or a transient 500 doesn't kill a long bulk run:

import time, requests

def call(enrichment_type, input_data, max_retries=4, **extra):
    delay = 1.0
    for attempt in range(max_retries):
        resp = requests.post(
            BASE_URL,
            headers={"Authorization": f"Bearer {API_KEY}",
                     "Content-Type": "application/json"},
            json={"type": enrichment_type, "input_data": input_data, **extra},
            timeout=30,
        )

        if resp.status_code in (429, 500):
            time.sleep(delay)       # 1s -> 2s -> 4s -> 8s
            delay *= 2
            continue

        if resp.status_code == 402:
            raise RuntimeError("Out of credits — top up to continue.")

        resp.raise_for_status()
        data = resp.json()
        return data.get("result") if data.get("status") == "success" else None

    raise RuntimeError("Exhausted retries after repeated rate limiting.")
PlanCredits / moRequests / secBatch size
Starter5,0005 req/sUp to 500
Professional20,00010 req/sUp to 500
Enterprise50,00020 req/sUp to 500
HyperGrowth250,00050 req/sUp to 500
7

Sync enriched leads to your CRM

Once you have an enriched record, the last step is getting it where your team works. Two paths:

Option A — Direct from code

Post the enriched object straight to your CRM's API right after the enrichment chain. Map email, phone, and linkedin onto your contact fields and upsert.

Option B — No-code via Zapier or Make

If you'd rather not maintain a per-CRM integration, call the endpoint from a generic HTTP step. One Zapier connection then fans out to HubSpot, Salesforce, Pipedrive, Airtable, Google Sheets, and thousands of other apps.

# Zapier: "Webhooks by Zapier" -> POST action
URL:     https://api.linkfinderai.com
Method:  POST
Headers: Authorization: Bearer YOUR_API_KEY
         Content-Type: application/json
Body:    {"type": "lead_full_name_to_linkedin_url",
          "input_data": "{{full_name}} {{company}}"}
# Make: HTTP -> "Make a request" module
URL:     https://api.linkfinderai.com
Method:  POST
Headers: Authorization: Bearer YOUR_API_KEY
Body:    JSON -> {"type": "linkedin_profile_to_email",
                 "input_data": "{{linkedin_url}}"}
Re-enrich on a schedule. People change jobs and companies grow, so a record that was complete six months ago is already drifting. A monthly job that re-runs your highest-value accounts keeps the CRM honest.

Start enriching leads in 2 minutes

Spin up the flow above on the free tier — 100 credits, an API on every plan, and flat 1-credit-per-request pricing with no annual contract.

Get your API key

No credit card required • API on every plan • Flat pricing • Cancel anytime