A RevOps stack scales when one layer owns the data and every other tool reads from it. Build that layer first. CRM, sequencer and warehouse then become swappable parts.
This guide builds the data layer against the LeadOcean API. Each step has a request you can run today.
TL;DR
- Put one data source behind your CRM, sequencer and warehouse. Size, search, enrich and export through it. Swapping a downstream tool then costs an afternoon, not a migration.
- It takes about 45 minutes. Steps 1 to 4 spend about 30 records on the free plan. The 100-row export test in Step 5 needs Solo or above.
- Count audiences for free before you spend anything, and let the API errors tell you when you hit a ceiling.
Prerequisites
- A LeadOcean account on the free plan. Sign up at app.leadocean.io/register. No card needed.
- An API key from the app, exported as
$LEADOCEAN_API_KEY. curlandjq. Any HTTP client works the same way.- A list of the tools in your stack today: CRM, sequencer, warehouse, enrichment. A text file is enough.
Step 1: Write the stack contract
Start with a contract: which layer owns which record, and which direction data flows. Most stacks break because two tools both think they own the contact.
layers:
data: # owns person and company facts, email_status, fetched_at
source: LeadOcean API (https://api.leadocean.io)
writes_to: [warehouse]
warehouse: # owns history, scoring inputs, dedupe keys
key: person_id
writes_to: [crm]
crm: # owns deals, owners, stages. Never edits data-layer fields
reads_from: [warehouse]
sequencer: # owns sends and replies
reads_from: [crm]
rules:
- one key per person: person_id
- send only email_status in [verified, catch_all_valid]
- nothing writes back upstreamKeep the file in the repo next to your sync jobs. When a tool changes, you edit one line and see what else it touches.
The person_id key matters. LeadOcean's enrich endpoint takes exactly one of linkedin_url, email, phone or person_id. Use person_id as the join key and you never match on a name.
Step 2: Size your audience for free
Count before you pull. A count costs no records, so size every segment you plan to feed the stack. Do this for each territory and persona, and you know the volume your CRM and sequencer must handle before you buy seats for either.
Send count=true as a query parameter, not in the JSON body. The body is flat JSON and rejects unknown fields.
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 '{
"jobFunction": ["Operations"],
"jobLevel": ["VP", "Director"],
"technologyCategories": ["CRM", "Marketing automation"],
"employeeRange": ["51-200", "201-500"],
"country": ["US"],
"limit": 1
}' | jq '.meta'Enum values are exact strings. Resolve them with GET /v1/enums/{name} or the free list_enum_values MCP tool before you guess. technologyCategories searches the roughly 7.8M companies whose sites we crawled, not the whole database.
Step 3: Pull the people for one segment
Pick one segment from Step 2 and pull a small page. You are testing the shape of the data and your mapping into the warehouse, not building the full list yet.
Search returns people as a thin projection, with no contact points. Each person returned counts one record. Set limit between 1 and 100 and page with the cursor.
curl -s -X POST "https://api.leadocean.io/v1/people/search" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": ["Head of Revenue Operations", "VP Revenue Operations"],
"emailStatus": ["verified", "catch_all_valid"],
"emailType": ["work"],
"limit": 25
}' > page1.json
jq '.meta | {count, credits, nextCursor}' page1.jsonTo read the next page, send the same body plus "cursor": "<meta.nextCursor>". One search pages through at most 10,000 rows. Past that no cursor is issued and meta.depthCapped is true. Narrow the filters and run smaller searches.
Step 4: Enrich on demand, by person_id
Search gives you the list. Enrichment is the step you run per record, when a rep needs the email. Pass the id from search and set reveal_email to true.
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": "PERSON_ID_FROM_SEARCH", "reveal_email": true}'Revealing the email costs no extra record. A person we hold nothing for returns a 404 and costs nothing. There is no name plus domain lookup, so search by domain first, then enrich by person_id.
For phones, call GET /v2/people/phone. The v2 contact endpoints cost one record per call, including a call that finds nothing.
Step 5: Export in bulk for the warehouse
Do not page 40,000 rows through search. At 100 rows a page that is 400 calls, and the 10,000-row depth cap stops you anyway. Run an export job: create it, poll it, download the CSV. One export takes up to 50,000 rows and your account can run 500,000 rows a day. Bulk CSV export is not on Free: it is included on Solo ($97 a month) and Agency ($497 a month, 50k rows a file).
curl -s -X POST https://api.leadocean.io/v1/exports \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "revops-stack-test",
"filters": {
"jobFunction": ["Operations"],
"jobLevel": ["VP", "Director"],
"country": ["US"],
"emailType": ["work"]
},
"limit": 100,
"columns": ["profile_full_name", "current_job_title",
"current_company_domain", "email_N_address", "email_N_status"]
}' | jq '.data.id'The API answers 202 with a job id. Poll GET /v1/exports/{id} until the status is done, then GET /v1/exports/{id}/download. Numbered columns such as email_N_address come back as email_1_address, email_2_address and so on.
Test with a limit of 100 first. An export reserves up to limit records when it starts and bills each row written. The app's Exports page does the same job with a filter builder and shows the record price before you start.
Step 6: Add a cost and rate guard
Read your quota on every response. The X-Quota-* and X-RateLimit-* headers ride on each call, and GET /v1/usage shows the totals.
curl -s -i https://api.leadocean.io/v1/usage \
-H "x-api-key: $LEADOCEAN_API_KEY" | grep -i -E "^x-(quota|ratelimit)|^HTTP"Wire two alerts into your stack: one on remaining records, one on any 429. A stack that scales reads its own limits instead of failing in the middle of a sync.
Rate limits are per plan: 5 requests a second on Free, 50 on Solo, 200 on Agency and 10,000 on Platform. Paid plans have no credits. Solo is $97 a month and Agency $497, each with a fair-use pace: past it, the key slows until the window resets. See pricing.
What you get
The Step 2 audience, counted through the free count_leads MCP tool on 2026-10-21:
count_leads
jobFunction: Operations
jobLevel: VP, Director
technologyCategories: CRM, Marketing automation
employeeRange: 51-200, 201-500
country: US
facets: false
total: 4020
totalIsExact: true
totalAll: 12992
credits: 0
defaults: counted verified, catch_all_valid and catch_all addressesThat is 4,020 mailable people out of 12,992 matching the other filters. The count spent 0 records. A second count on 2026-10-21, titles "Head of Revenue Operations" or "VP Revenue Operations" worldwide, returned 856 mailable people.
A record-level response has this shape (response shape from the API reference, placeholder values):
{
"profile_data": { "...": "profile fields" },
"contact_data": {
"has_email": true,
"has_phone": false,
"email_status": "verified",
"email_type": "work"
},
"meta": { "source": "leadocean", "schema": "leadocean.person.v1" }
}contact_emails and contact_phones appear only after you set reveal_email to true on enrich. Read email_status before you send. There is no refund or credit-back for bounced emails.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing or wrong x-api-key header | Send the key in the x-api-key header, from $LEADOCEAN_API_KEY. Do not put it in the URL. |
| 429 | Above your plan's rate limit: 5 a second on Free, 50 on Solo, 200 on Agency | Wait for Retry-After, queue requests and cap concurrency at your plan's limit. |
429 export_limit_daily | Past 500,000 export rows in a day | Run the rest after resetsAt in the response. |
| 402 | The free plan's 1,000 records are spent | Check GET /v1/usage. Cut the limit, or move to Solo ($97) or Agency ($497). |
| Empty results or a withheld total | A wrong enum string, or a filter we cannot apply | Resolve values with GET /v1/enums/{name}. Read meta.unsupportedFilters and meta.totalWithheld. |
400 export_limit_rows | Export limit above 50,000 | Split into several exports under 50,000 rows each. |
FAQ
Which layer of a RevOps stack should I build first?
The data layer. CRM, sequencer and warehouse all read people and company facts, so a clean source with one join key saves rework in every tool after it. Pick the CRM second.
How many records does this guide spend?
About 30 on the free plan: 25 from the Step 3 search and a handful of enrichments in Step 4. The 100-row export test adds 100 on Solo or above. The counts in Step 2 and the usage call in Step 6 are free.
Can I filter companies by the tools they use?
Yes, on companies we have crawled. technologyCategories takes categories such as CRM or Marketing automation, and technologies takes names detected on the site. Coverage is about 7.8M crawled companies, so size the segment first. For a full walkthrough, see how to build a prospect list by tech stack.
Can an AI agent run these steps for me?
Yes. The hosted MCP server at https://api.leadocean.io/mcp exposes the same data as 13 tools, with OAuth sign-in and no key pasted. See Claude Code RevOps workflows for agent recipes, and best tech stack lookup tools if you need to compare detection sources.
What should I check before adding a fifth tool?
Ask which layer in your contract it owns. If it duplicates a layer, replace the old tool instead of adding one. If it needs to write back upstream, it breaks the contract.
Does the API replace my CRM?
No. LeadOcean is the data layer. It has no rep workspace, sequencer, dialer or CRM. The app has an Exports page for filtered CSV downloads.
More guides for this section are in the blog hub.
Build the data layer of your RevOps stack on a free API key
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →