Pre-Black Friday: 20% off, for as long as you stayEARLYBFon Solo and AgencyEnds Nov 28Start free

How-to guide

How to Enrich a CRM Programmatically (REST API and a Python Loop)

Size the list for free, pull the rows, enrich each person once, and write the fields back to your CRM. Under an hour, 20 records on the example.

Get your free API key →Free to start. No credit card. 1,000 records to spend whenever you like.

Programmatic CRM enrichment is a loop: find the people, enrich each one by id, write the fields back. This guide builds that loop against the LeadOcean REST API with real requests. The same pattern works with any CRM that has an update endpoint.

TL;DR

  • Four calls do the job: a free count, a search, a person enrich and your CRM's upsert.
  • It takes about 45 minutes, and the example spends about 20 of your 1,000 free records.
  • Counting is free, and an enrichment that finds nobody costs nothing, so you can size and test before you spend.

Prerequisites

  • A LeadOcean API key from app.leadocean.io. The Free plan works and needs no card.
  • The key in your shell: export LEADOCEAN_API_KEY=your_key_here.
  • Python 3.9 or later with requests, or any HTTP client.
  • Write access to your CRM's API (a token for a contact upsert). The CRM call below is a placeholder you replace.
  • Free runs at 5 requests a second. Keep the loop under that, or move to a plan with a higher limit.

Step 1: Size the list before you spend anything

Counting is free, so start there. Send count=true as a query parameter with limit=1. The JSON body rejects unknown fields, so the flag does not go in the body.

bash
curl -s -G "https://api.leadocean.io/v1/people/search" \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  --data-urlencode "count=true" \
  --data-urlencode "limit=1" \
  --data-urlencode "jobLevel=VP" \
  --data-urlencode "jobFunction=Sales & Business Development" \
  --data-urlencode "country=US" \
  --data-urlencode "employeeRange=51-200"

meta.total is the match count, capped at 100,000. The data array comes back empty with meta.countOnly true. If you work from an AI client, the MCP tool leadocean_count_leads does the same call. For company lists use GET /v1/companies/count, which is also free.

Search returns one record per person. The body is flat JSON, filters are arrays of strings, and limit runs from 1 to 100. Enum values are exact strings, so check them with leadocean_list_enum_values or GET /v1/enums first.

bash
curl -s -X POST "https://api.leadocean.io/v1/people/search" \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jobLevel": ["VP"],
    "jobFunction": ["Sales & Business Development"],
    "country": ["US"],
    "employeeRange": ["51-200"],
    "limit": 10
  }'

Each row carries an id at meta.source_ids.person_id. Keep it. Search publishes email flags, not addresses, and about a fifth of findable people have no LinkedIn URL, so the id is what lets you enrich every row.

To page, pass back meta.nextCursor unchanged as cursor. One search pages to 10,000 rows. On the last page meta.depthCapped is true and there is no cursor, so narrow the filters and run several smaller searches.

Step 3: Enrich each person by person_id

POST /v1/people/enrich takes exactly one of linkedin_url, email, phone or person_id. It costs one record. Add reveal_email to get the addresses and phones we hold, at no extra charge.

bash
curl -s -X POST "https://api.leadocean.io/v1/people/enrich" \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"person_id": "1234567890", "reveal_email": true}'

There is no name plus domain lookup. If your CRM only holds a name and a company, search by domain first, then enrich the id. If the CRM already holds an email or a LinkedIn URL, skip the search and enrich on that directly.

Since October 2026 the response also includes resume_data: past roles, education, certifications and awards. Past employers carry no company_id yet, so match them on name.

Step 4: Write the fields back to your CRM

This loop reads contacts from a search, enriches each, and upserts. Swap upsert_to_crm for your CRM's client. It stays under the 5 requests a second Free limit.

python
import os, time, requests

BASE = "https://api.leadocean.io"
H = {"x-api-key": os.environ["LEADOCEAN_API_KEY"]}

def search(cursor=None):
    body = {"jobLevel": ["VP"], "jobFunction": ["Sales & Business Development"],
            "country": ["US"], "employeeRange": ["51-200"], "limit": 10}
    if cursor:
        body["cursor"] = cursor
    r = requests.post(f"{BASE}/v1/people/search", headers=H, json=body)
    r.raise_for_status()
    return r.json()

def enrich(person_id):
    r = requests.post(f"{BASE}/v1/people/enrich", headers=H,
                      json={"person_id": person_id, "reveal_email": True})
    if r.status_code == 404:      # nobody matched, costs nothing
        return None
    r.raise_for_status()
    return r.json()

def upsert_to_crm(person):
    ...  # your CRM client here, keyed on email or LinkedIn URL

page = search()
for row in page["data"]:
    person = enrich(row["meta"]["source_ids"]["person_id"])
    if person:
        upsert_to_crm(person)
    time.sleep(0.25)              # 4 a second, under the Free limit

Map contact_data.email_status to a CRM field and filter on it before any send. Bounces are not refunded, so read the status first. For a few thousand rows or more, skip the loop and use POST /v1/exports: up to 50,000 rows a request and 500,000 a day, with bulk CSV on paid plans.

What you get

Step 1 on 21 October 2026, run with the free leadocean_count_leads tool for US VPs of Sales at companies with 51 to 200 employees:

json
{
  "total": 18567,
  "totalIsExact": true,
  "totalAll": 60456,
  "distinctCompanies": 12192,
  "credits": 0
}

total counts people whose best email is verified or catch-all. totalAll is the same filters with no email condition. It is a mailable-email count, not a count of everyone.

Response shape from the API reference, placeholder values, for the enrich call:

json
{
  "success": true,
  "data": {
    "profile_data": { "profile_full_name": "Jane Doe" },
    "contact_data": {
      "has_email": true,
      "has_phone": true,
      "email_status": "verified",
      "email_type": "work",
      "contact_emails": [{ "email": "jane.doe@acme.com", "type": "work", "status": "verified", "priority": 1 }],
      "contact_phones": [{ "phone": "+14155550133", "type": "direct", "priority": 1 }],
      "contact_current_experiences": [{ "job_title": "VP Sales", "company_name": "Acme" }]
    },
    "resume_data": { "experiences": [], "educations": [] }
  }
}

In the loop, enrich() returns this whole object, so the person sits under data.

Troubleshooting

ErrorCauseFix
401Missing or wrong x-api-key headerCheck $LEADOCEAN_API_KEY is exported and sent as x-api-key
429Above your plan's rate limit (5 a second on Free, 200 on Agency)Honour Retry-After, or add a sleep to the loop
402Records are spent (Free is 1,000 for life)Check GET /v1/account, or move to a paid plan
Empty data on a searchA filter value that is not an exact enum string, such as VP of Sales for jobLevelLook up the value with GET /v1/enums and resend
404 on enrichNobody matched that identifierNot an error to retry. It costs nothing, so skip the row
No nextCursor before you expectedThe 10,000 row depth cap (meta.depthCapped true)Split the list into narrower searches

FAQ

How many records does CRM enrichment cost?

One record per person you receive. A search page of 10 is 10 records, and each enrichment is one more. Counts, 404s and empty pages are free.

Can I enrich by name and company domain?

Not directly. Search by domain to get person ids, then enrich by person_id. If the CRM already holds an email or LinkedIn URL, enrich on that.

Does it work with Salesforce, HubSpot or a spreadsheet?

Yes, if it has an update API or an import. LeadOcean returns JSON, and the CRM side is your upsert_to_crm. For no-code flows see the Clay integration or n8n.

How fresh is the data?

The dataset is refreshed monthly, and each record carries its own fetched_at date. Re-enrich on a schedule, not on every page view.

Is there a CRM inside LeadOcean?

No. LeadOcean is the data layer: API, MCP server and an exports page in the app. Your CRM stays where it is.

More on this: CRM enrichment tools compared, the CRM enrichment use case, the company enrichment API and pricing. Back to the blog hub.

Enrich your CRM from one API call

Free to start. No credit card. 1,000 records to spend whenever you like.

Get your free API key →