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

Attio integration

LeadOcean + Attio: enrich records automatically

A recipe that reads Attio people, calls the LeadOcean enrich API, and upserts job title, phone and LinkedIn back by email.

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

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).

  1. Create a LeadOcean key. Sign up at app.leadocean.io/register. The free plan has no card.
  2. Create an Attio access token. Generate a single-workspace token with the record_permission:read-write and object_configuration:read scopes. Those are the scopes the upsert endpoint requires (Attio docs, September 2026).
  3. Pick the rows to enrich. List people where job title is empty, or add a "Needs enrichment" checkbox and filter on it.
  4. Call LeadOcean per row. Send POST /v1/people/enrich with the email. A person LeadOcean holds nothing for returns 404 and costs nothing.
  5. Upsert to Attio. Call PUT /v2/objects/people/records with matching_attribute=email_addresses. A record with that email is updated, otherwise one is created (Attio docs, September 2026).
  6. 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.

bash
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 fieldAttio attributeNote
contact_data.contact_emails[].emailemail_addressesCheck status first. Write only verified or catch_all_valid.
contact_data.contact_current_experiences[].job_titlejob_titleText. Current role only. Past roles are in resume_data.
contact_data.contact_phones[].phonephone_numbersNeeds reveal_email true. A string must start with "+" and the country code.
contact_data.contact_current_experiences[].company_domaincompany domainsUse it to match the company record.
profile_data.profile_urllinkedinText 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.

LeadOceanAttio
Plan priceSolo $97 a month. Fair-use pace, no overage invoiceFree, Plus $44 or Pro $99 per user a month billed monthly ($35 and $79 billed annually)
10,000 rows$97 a month on SoloRecords cap: 50,000 on Free, 250,000 on Plus, 1,000,000 on Pro
Speed limit50 requests a second on Solo25 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

SymptomCauseFix
401 from LeadOceanKey missing or wrongSend it in the x-api-key header, with no spaces.
404 from LeadOceanNo record for that personNo charge. Skip the row and mark it checked.
402 from LeadOceanFree 1,000 records spentMove to Solo or Agency.
429 from LeadOceanOver 5 requests a second on FreeSlow the loop or upgrade.
429 from AttioOver 25 writes a secondHonor the Retry-After header (Attio docs, September 2026).
Attio rejects a valueEnriched attribute or wrong shapeWrite only writeable attributes. Check the attribute type docs.
Duplicate personMatched on name, not emailAlways 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 →