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

How-to guide

How to Enrich a CSV of Contacts in Claude Code (2026)

Add one MCP URL, test on five rows, then let Claude Code write and run the script for the full file. Each matched row costs 1 record.

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

You enrich a CSV in Claude Code by connecting the LeadOcean MCP server, testing five rows in the session, then asking Claude Code to run a short script over the full file. Claude Code reads and writes the files. LeadOcean returns the email, status and phone it holds.

The setup is on the Claude Code setup page. This guide covers the CSV run end to end.

TL;DR

  • It takes about 20 minutes: 5 to connect, 5 to test on five rows, 10 to run the script on the full file.
  • It spends 1 record per matched row. A 25-row file costs up to 25 of your 1,000 free records, plus 5 for the test. A row we hold nothing for costs 0.
  • Run the full file as a script, not as chat calls. Claude Code warns when MCP tool output passes 10,000 tokens and caps it at 25,000 by default, October 2026.

Prerequisites

  • A LeadOcean account and API key from app.leadocean.io. The free plan has no card and 1,000 records, one-off.
  • The key in your shell as $LEADOCEAN_API_KEY. Only the script step needs it.
  • Claude Code installed, plus curl and jq.
  • A CSV with a linkedin_url column. An email or phone column also works as the lookup key.
bash
export LEADOCEAN_API_KEY="paste-your-key-here"

A sample contacts.csv with placeholder rows:

csv
name,company,linkedin_url
Jane Doe,Acme,https://www.linkedin.com/in/janedoe-example
John Roe,Acme,https://www.linkedin.com/in/johnroe-example

Step 1: Connect the LeadOcean MCP server

Add the hosted server as an HTTP MCP server, then sign in. Claude Code's docs say to run /mcp inside a session to authenticate a remote server with OAuth. No key gets pasted into the chat.

bash
claude mcp add --transport http leadocean https://api.leadocean.io/mcp
claude mcp list
text
/mcp

Pick leadocean in the panel and approve the sign-in. From your shell, claude mcp login leadocean runs the same sign-in. The server exposes 13 tools. Seven never spend records: the two counts, filter list, enum list, account, get export and list exports.

Step 2: Check the connection for free

Confirm Claude Code can reach the server before you spend a record. Ask for a lookup that costs nothing.

text
Use the leadocean MCP server to list the valid values for email_status.
Do not search for or look up any person.

Claude Code calls leadocean_list_enum_values. If it answers with 13 values, the connection works.

Step 3: Enrich five rows in the session

Cap the first run at five rows so the spend stays visible. Put contacts.csv in your working directory and ask for it by name.

text
Read contacts.csv. Take the first 5 rows. For each linkedin_url, call
leadocean_get_lead and include the email and phone numbers if LeadOcean holds them.
Stop after 5 rows. Return a table with name, company, email, email_status, phone.
If a row is not found, write "not found" and move on. Do not guess an address.

A found row costs 1 record. A get_lead that finds nobody costs nothing, the same as a REST 404. Read email_status before you send, because LeadOcean gives no refund or credit for bounced emails.

Check the five rows by eye. Does the title match the person you expected? Is the email column filled where has_email is true? If a row looks wrong, fix the LinkedIn URL in the CSV now, while a mistake still costs five records and not five hundred.

Step 4: Run the full file with a script

Past about 25 rows, ask Claude Code to write the loop. It calls POST /v1/people/enrich once per row. Setting reveal_email to true returns the addresses at no extra record.

text
Write enrich.sh. For every row in contacts.csv, POST the linkedin_url to
https://api.leadocean.io/v1/people/enrich with header x-api-key from
$LEADOCEAN_API_KEY and reveal_email true. Write linkedin_url, email, email_status
and phone to enriched.csv. Skip rows that return 404. Run it and tell me how many
rows matched.

The script Claude Code writes should look like this one:

bash
echo "linkedin_url,email,email_status,phone" > enriched.csv
tr -d '\r' < contacts.csv | tail -n +2 | while IFS=, read -r name company linkedin; 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 "{\"linkedin_url\":\"$linkedin\",\"reveal_email\":true}" |
  jq -r --arg u "$linkedin" '[$u,
    (.data.contact_data.contact_emails[0].email // ""),
    (.data.contact_data.email_status // ""),
    (.data.contact_data.contact_phones[0].phone // "")] | @csv' >> enriched.csv
done

Ask Claude Code to show you the script before it runs. Two things to check: the key comes from $LEADOCEAN_API_KEY and not a literal string, and the loop sends one request at a time. One at a time stays under the rate limit on every plan: 5 requests a second on Free, 50 on Solo and 200 on Agency.

Each request takes exactly one of linkedin_url, email, phone or person_id. There is no name plus domain lookup. For a list of company domains, search by domain first, then enrich by person_id.

Step 5: Keep only the mailable rows

Enrichment gives you addresses. It does not say which are safe to mail. Filter on email_status before you send anything.

text
From enriched.csv, keep only rows where email_status is verified, catch_all_valid
or catch_all. Write them to mailable.csv and say how many rows you dropped and why.

Do this in the same session so Claude Code can show you the dropped rows. Add a count at the end: rows in, rows matched, rows mailable. Those three numbers are your match rate and your mailable rate for this list.

Drop invalid, spam_trap, abuse and disposable. Treat risky, role and derived with care. untested means nobody has checked the address yet.

What you get

Step 2 returns the exact strings the email_status filter accepts. We ran it on 2026-10-21 and show the value list.

json
{"name":"email_status","total":13,"matched":13,"values":["verified","catch_all_valid","catch_all","risky","unknown","untested","invalid","role","disposable","spam_trap","abuse","derived","none"],"truncated":false}

Steps 3 and 4 return one record per matched row. This is the response shape from the API reference, with placeholder values:

json
{
  "success": true,
  "data": {
    "profile_data": { "...": "name, title, company" },
    "contact_data": {
      "has_email": true,
      "has_phone": true,
      "email_status": "verified",
      "contact_emails": [{ "email": "jane.doe@acme.com", "type": "work" }],
      "contact_phones": [{ "phone": "+14155550133", "type": "mobile" }]
    }
  }
}

Without reveal_email, contact_emails and contact_phones are absent. has_email and has_phone still tell you whether a value exists.

For a 25-row file, the record cost is 5 for the test and up to 25 for the run. Compare the row count of enriched.csv with contacts.csv to get the match rate for your own list.

Troubleshooting

ErrorCauseFix
401The x-api-key header is missing, or the key is wrong or revoked.Copy a fresh key from the app and re-export $LEADOCEAN_API_KEY.
402Your records are spent. The free plan is 1,000 records, one-off.Run GET /v1/account to check the balance, or move to Solo at $97 a month. Paid plans have a fair-use pace, not credits.
404We hold nothing for that identifier.Nothing to fix. A 404 costs 0 records. Try the email or person_id for that row.
429Above your plan's rate limit (5 requests a second on Free, 200 on Agency), or a paid plan's fair-use window reached.Wait the Retry-After seconds and retry. A one-at-a-time loop does not hit this.
Empty search resultsA filter value is not an exact enum string, such as Vice President instead of VP.Call leadocean_list_enum_values and pass the exact string it returns.
Export refusedOne export is capped at 50,000 rows, and an account at 500,000 rows a day.Split the job across requests and days, or narrow the filters.

FAQ

Can Claude Code enrich every row without a script?

Yes for small files. It makes one leadocean_get_lead call per row, so a long file fills the session with output. Keep chat runs to about 25 rows and use the Step 4 script for the rest.

How many records does the run use?

One per matched row. A row we hold nothing for costs 0. Counts, filter lookups and account checks are free.

What if my CSV has no LinkedIn URLs?

Use email or phone as the key instead. Each request takes exactly one identifier, so pick one column per run. Rows with a LinkedIn URL usually match best.

Does Claude Code see my API key?

Not through the MCP route. The server uses OAuth sign-in and you paste no key into the chat. The script reads $LEADOCEAN_API_KEY from your shell.

Can I upload a CSV to the Exports page instead?

No. The Exports page in the app builds a file from filters, with a column picker and a price preview. It does not take an uploaded list. To enrich your own rows, use Claude Code or the API.

Does this work in other editors?

Yes. The same MCP URL works elsewhere. See enrich a CSV in VS Code and enrich a CSV in Claude. To find new contacts rather than enrich yours, read Claude Code sales contact finder. Plans are on pricing.

More recipes live in the blog hub.

Enrich your own CSV in Claude Code, free

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

Get your free API key →