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

How-to guide

How to Personalize Emails From Data (With the LeadOcean API)

Personalization is a data problem first. Pull the fields, check they exist, then write one line per person.

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

Personalizing emails means putting one true fact about the reader in the opening line. You get that fact from data, so fetch the data before you write.

This guide pulls a small segment, enriches a few people, and turns the fields into merge values. Each step is a request you can run.

TL;DR

  • Search a segment, enrich five people for career history, then fill a merge template. Skip any person whose field is empty.
  • It takes about 20 minutes and spends about 30 records, inside the free 1,000.
  • Sizing the list first is free, so count before you pull.

Prerequisites

  • A free LeadOcean key in $LEADOCEAN_API_KEY (start free, no card: pricing).
  • curl and jq.
  • Any sending tool. LeadOcean returns data only. It has no sequencer or inbox.
  • Background: email personalization and email deduplication if you merge lists.

Step 1: Size the segment for free

Count first. A count costs no records, so you can tune filters as often as you like.

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

The count=true flag goes in the query string, not the JSON body. Enum values are exact strings: check them with leadocean_list_enum_values or GET /v1/enums.

Step 2: Pull one page of people

Search with the same body and a real limit. One page of 25 spends 25 records.

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": ["GB"],
    "employeeRange": ["51-200"],
    "limit": 25
  }' > page1.json

Filters are arrays and the body is flat. For the next page, send the cursor from the response.

Step 3: Enrich a few people for a personal fact

Pick five people and enrich each by person_id, found on each search row at meta.source_ids.person_id. Since 2026-10-17 the response carries career history, so you can reference a past role, a school or a certification.

bash
for id in $(jq -r '.data[:5][].meta.source_ids.person_id' page1.json); do
  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\": \"$id\"}"
done > enriched.jsonl

An enrichment that finds nothing costs nothing. Past employers carry no company_id yet, so match them by name.

Step 4: Build merge values and skip gaps

A merge value is only safe when the field exists. Pick the best one per person and fall back to a plain opener.

bash
jq -r '
  .data as $p
  | def past: ($p.resume_data.experiences // [])[0];
  {
    name: $p.profile_data.profile_full_name,
    company: ($p.contact_data.contact_current_experiences[0].company_name // null),
    hook: (if past then "your time at " + past.company_name else null end)
  }
  | select(.hook != null)
  | "Hi \(.name), saw \(.hook). Quick question about \(.company)."
' enriched.jsonl

The API reference does not list the inner keys of resume_data or of the experience objects. Print one record and confirm experiences and company_name before you run a batch. Rule of thumb: one fact, one line, no stacking. If hook is null, send the plain version or drop the person.

What you get

Step 1 on 2026-10-21, run with the free leadocean_count_leads tool, for VP-level Sales & Business Development people in GB at companies of 51-200 employees:

json
{
  "total": 442,
  "totalIsExact": true,
  "totalAll": 1349,
  "distinctCompanies": 352,
  "credits": 0
}

The 442 counts only people whose email is verified, catch_all_valid or catch_all. The 1,349 is the same filters with no email condition. Do not call 442 everyone you can reach by other means.

A person record from enrich has this shape (response shape from the API reference, placeholder values):

json
{
  "success": true,
  "data": {
    "profile_data": { "profile_full_name": "Jane Doe" },
    "contact_data": {
      "has_email": true,
      "email_status": "verified",
      "contact_current_experiences": [{ "company_name": "Acme" }]
    },
    "resume_data": {
      "experiences": [{ "company_name": "Example Ltd" }],
      "educations": []
    },
    "meta": { "source_ids": { "person_id": "1234567890" } }
  }
}

Troubleshooting

ErrorCauseFix
401Missing or wrong x-api-keyCheck $LEADOCEAN_API_KEY is exported and sent as the x-api-key header.
429Above the plan's rate limit (5 requests a second on Free, 200 on Agency)Honor Retry-After, or add a short sleep in the loop.
402Out of recordsThe free 1,000 are spent. Pick a paid plan on pricing.
Empty resultsA filter value is not an exact enum stringList values with leadocean_list_enum_values, for example Sales & Business Development.
Unknown field rejectedcount=true placed in the JSON bodyMove it to the query string.
Search stops pagingOne search pages to 10,000 rows (meta.depthCapped)Narrow the filters and run a second search.

FAQ

How many fields should one email use?

One. A single accurate fact reads as research. Three facts read as a mail merge.

What if the personal field is empty?

Skip that line, not the email. Use a plain opener or leave the person out of the batch.

Does LeadOcean send the emails?

No. It returns people and company data. Send with any tool you already use.

How do I avoid emailing the same person twice?

Dedupe on email address before you send. See email deduplication.

Can I guess addresses from a pattern instead?

You can, but verify them first. See what is an email pattern for the risk.

Get the fields that make your first line true

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

Get your free API key →