You get a job that reads Attio people records, sends each email to LeadOcean, and upserts job title, phone and LinkedIn URL back to the same record. It matches on email, so it never creates duplicates. LeadOcean has no Attio app, so the link is two plain HTTP calls: LeadOcean's enrich API and Attio's REST API.
How it works
The recipe is a small script that calls two APIs in a row, run from cron or any scheduler. Attio's REST API takes JSON over HTTPS and a bearer token (Attio docs, September 2026).
- Create a LeadOcean key. Sign up at app.leadocean.io/register. The free plan has no card.
- Create an Attio access token. Generate a single-workspace token with the
record_permission:read-writeandobject_configuration:readscopes. Those are the scopes the upsert endpoint requires (Attio docs, September 2026). - Pick the rows to enrich. List people where job title is empty, or add a "Needs enrichment" checkbox and filter on it.
- Call LeadOcean per row. Send
POST /v1/people/enrichwith the email. A person LeadOcean holds nothing for returns 404 and costs nothing. - Upsert to Attio. Call
PUT /v2/objects/people/recordswithmatching_attribute=email_addresses. A record with that email is updated, otherwise one is created (Attio docs, September 2026). - Schedule it. Run hourly or nightly. Skip rows already filled, so each run only spends records on new people.
For a one-off backfill of a whole list, use the Exports page in the LeadOcean app instead and import the CSV into Attio.
Configuration
Both calls are plain HTTP. Keep both secrets in environment variables.
curl -X POST "https://api.leadocean.io/v1/people/enrich" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "content-type: application/json" \
-d '{"email":"jane@acme.com","reveal_email":true}' > person.json
curl -X PUT "https://api.attio.com/v2/objects/people/records?matching_attribute=email_addresses" \
-H "Authorization: Bearer $ATTIO_TOKEN" \
-H "content-type: application/json" \
-d '{
"data": {
"values": {
"email_addresses": ["jane@acme.com"],
"job_title": "VP of Sales",
"phone_numbers": ["+14155550133"],
"linkedin": "https://www.linkedin.com/in/jane-doe"
}
}
}'The first call enriches one person with LeadOcean. The second upserts into Attio, matching on email. In a real run, fill the second request from person.json. Send exactly one identifier to enrich: email, linkedin_url, phone or person_id. Phones only come back when reveal_email is true, and it costs no extra record (LeadOcean docs, September 2026).
Attio's email_addresses attribute is unique, and creating a person also matches or creates a company from the email domain (Attio docs, September 2026). That is the dedupe. Do not match on name.
Wrap the two calls in a loop with a short sleep. A 25 writes a second ceiling means a plain loop with 50 ms between upserts stays under it. Log every 404 so you can see which people LeadOcean does not hold.
Field mapping
Map only what you will use in a view or a filter. More columns mean more noise for reps, and each extra attribute is one more thing to keep in sync.
The enrich response is one person object with a data key; paths below start inside it. Attio enriches person records itself, and those values cannot be overridden by API users, so write only the attributes Attio lists as writeable (Attio docs, October 2026).
| LeadOcean field | Attio attribute | Note |
|---|---|---|
contact_data.contact_emails[].email | email_addresses | Check status first. Write only verified or catch_all_valid. |
contact_data.contact_current_experiences[].job_title | job_title | Text. Current role only. Past roles are in resume_data. |
contact_data.contact_phones[].phone | phone_numbers | Needs reveal_email true. A string must start with "+" and the country code. |
contact_data.contact_current_experiences[].company_domain | company domains | Use it to match the company record. |
profile_data.profile_url | linkedin | Text attribute in Attio. |
Cost at 10,000 rows
Ten thousand enrichments fit on LeadOcean Solo at $97 a month, because Solo's pace is 10k enrichments every 5 hours. Agency at $497 a month raises that to 1M a day. Free covers the first 1,000 records, once.
| LeadOcean | Attio | |
|---|---|---|
| Plan price | Solo $97 a month. Fair-use pace, no overage invoice | Free, Plus $44 or Pro $99 per user a month billed monthly ($35 and $79 billed annually) |
| 10,000 rows | $97 a month on Solo | Records cap: 50,000 on Free, 250,000 on Plus, 1,000,000 on Pro |
| Speed limit | 50 requests a second on Solo | 25 writes a second, 100 reads a second |
Attio's write limit sets the run time. 10,000 upserts at 25 writes a second take about 400 seconds (Attio docs, September 2026). Attio's record caps and seat prices are from its pricing page, checked October 2026. Even Attio Free holds 50,000 records, so a 10,000-row run fits every Attio plan if the people are new. See LeadOcean pricing for what each plan includes.
Pick the schedule by volume. A nightly run suits a CRM that gains a few hundred people a day. An hourly run suits inbound forms, where a rep wants the job title before the first call. Either way the filter in step 3 keeps the run cheap, because rows already filled are skipped and cost nothing.
On the free plan, test with 20 rows before you loop. Check that the title, phone and LinkedIn URL land on the right record, and that a second run changes nothing.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| 401 from LeadOcean | Key missing or wrong | Send it in the x-api-key header, with no spaces. |
| 404 from LeadOcean | No record for that person | No charge. Skip the row and mark it checked. |
| 402 from LeadOcean | Free 1,000 records spent | Move to Solo or Agency. |
| 429 from LeadOcean | Over 5 requests a second on Free | Slow the loop or upgrade. |
| 429 from Attio | Over 25 writes a second | Honor the Retry-After header (Attio docs, September 2026). |
| Attio rejects a value | Enriched attribute or wrong shape | Write only writeable attributes. Check the attribute type docs. |
| Duplicate person | Matched on name, not email | Always pass matching_attribute=email_addresses. |
FAQ
Does LeadOcean have a native Attio integration?
No. You call the LeadOcean REST API and the Attio REST API yourself. That gives you every endpoint, not a fixed action list.
Will it overwrite what my team typed in Attio?
An upsert updates the attributes you send. Leave out any attribute you do not want touched, or check Attio first and send only empty fields.
Can I find phone numbers too?
Yes. Set reveal_email to true, or call GET /v2/people/phone. That endpoint costs one record per call, including a call that finds nothing.
Can I use the MCP server instead?
For small batches, yes. LeadOcean's MCP server is at https://api.leadocean.io/mcp and gives an agent 13 tools to count, search and enrich. For scheduled bulk work, use the scripted recipe.
Enrich your Attio records with a free LeadOcean key.
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →