A marketing automation dataset is a filtered list of people and companies that you refresh on demand. You do not buy it once a quarter. This guide builds one with five calls to the LeadOcean API.
TL;DR
- Five calls: count (free), search, enrich, export and a paging loop. About 20 minutes end to end.
- The test run spends about 120 records: 100 from one search page and 20 enrichments. Counts spend nothing.
- The same filters work in the API, the MCP server and the Exports page, so agents and people share one audience.
Prerequisites
- A LeadOcean account on the free plan (1,000 records for life, 5 requests a second, no card). Create the key in app.leadocean.io.
- The key in your shell:
export LEADOCEAN_API_KEY=... curlandjq. For the agent step, any MCP client that can add a remote server (https://api.leadocean.io/mcp).
Step 1: Size the audience for free
Start with a count. It tells you whether the filters are right before you spend a record.
Send count=true as a query parameter and limit 1 in the body. The JSON body rejects unknown fields, so the flag must not go in the body.
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": ["Advertising & Marketing"],
"jobLevel": ["VP", "C-Team"],
"country": ["US"],
"technologyCategories": ["Marketing automation"],
"limit": 1
}' | jq '.meta'meta.total is capped at 100,000 and meta.countOnly comes back true. Enum values are exact strings. If you are unsure, GET /v1/enums lists them, and so does the leadocean_list_enum_values MCP tool.
Step 2: Search one page
Run the same filters without count. The body is flat JSON, filters are arrays, limit runs 1 to 100, and you page with 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 '{
"jobFunction": ["Advertising & Marketing"],
"jobLevel": ["VP", "C-Team"],
"country": ["US"],
"technologyCategories": ["Marketing automation"],
"emailStatus": ["verified", "catch_all_valid"],
"limit": 100
}' > page1.json
jq '.meta' page1.jsonThis spends 100 records, one per person returned. To get the next page, pass meta.nextCursor back as cursor. One search pages to 10,000 rows. When meta.depthCapped is true, narrow the filters instead of walking further.
The technologyCategories filter reads from about 7.8M crawled company sites, so it finds employers that run marketing automation tooling, not people who say they do.
Step 3: Enrich the rows that matter
Search returns the profile. Enrich returns the contact data and career history. Pass exactly one of linkedin_url, email, phone or person_id. There is no name plus domain lookup, so search first, then enrich by person_id.
PID=$(jq -r '.data[0].meta.source_ids.person_id' page1.json)
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\": \"$PID\", \"reveal_email\": true}"reveal_email returns the address and costs no extra record. A person we hold nothing for is a 404 and costs nothing. Since October 2026 the response also carries resume_data: past roles, education, certifications and awards when held. Past employers have no company_id yet.
Step 4: Export the full list
For more than a few hundred rows, export instead of paging. POST /v1/exports takes up to 50,000 rows a request and 500,000 a day. Bulk CSV export is not on the free plan.
curl -s -X POST "https://api.leadocean.io/v1/exports" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filters": {
"jobFunction": ["Advertising & Marketing"],
"jobLevel": ["VP", "C-Team"],
"country": ["US"],
"technologyCategories": ["Marketing automation"],
"emailStatus": ["verified", "catch_all_valid"]
},
"limit": 2898,
"preset": "full"
}'The search filters go under filters, with a row limit and a column preset. Poll GET /v1/exports/{id}, then fetch the CSV from GET /v1/exports/{id}/download. The Exports page in the app runs the same job: filter builder, column picker, a price preview before you start and a CSV download.
Step 5: Keep it fresh with a pagination loop
A dataset feeding automation needs a repeatable pull. This loop walks the cursor, stops at the depth cap and writes one JSON line per person.
CURSOR=""
for i in $(seq 1 5); do
BODY=$(jq -n --arg c "$CURSOR" '{
jobFunction: ["Advertising & Marketing"],
jobLevel: ["VP", "C-Team"],
country: ["US"],
technologyCategories: ["Marketing automation"],
emailStatus: ["verified", "catch_all_valid"],
limit: 100
} + (if $c != "" then {cursor: $c} else {} end)')
RES=$(curl -s -X POST "https://api.leadocean.io/v1/people/search" \
-H "x-api-key: $LEADOCEAN_API_KEY" -H "Content-Type: application/json" -d "$BODY")
echo "$RES" | jq -c '.data[]' >> audience.jsonl
CURSOR=$(echo "$RES" | jq -r '.meta.nextCursor // empty')
[ -z "$CURSOR" ] && break
sleep 0.3
done
wc -l audience.jsonlFive pages is 500 records on the free plan, so cap the loop while you test. The sleep keeps you under 5 requests a second. Re-run the loop monthly, or whenever a campaign needs a fresh list, and compare against your last file by person_id.
Step 6: Give the audience to an agent
Add the MCP server https://api.leadocean.io/mcp to your client. Sign-in is OAuth 2.1, so you paste no key. The server has 13 tools. Three of them are free and do the sizing work: leadocean_count_leads, leadocean_count_companies and leadocean_list_enum_values.
This prompt keeps the agent from spending records by accident.
Use leadocean_count_leads to size: VPs and C-Team in Advertising &
Marketing, US, at companies using Marketing automation tech. Report the
total, totalAll and distinctCompanies. Do not call search_leads or
export_leads until I confirm the number.What you get
Count run on 2026-10-21 through the leadocean_count_leads MCP tool, with the filters from Step 1.
{
"total": 2898,
"totalIsExact": true,
"totalAll": 6810,
"distinctCompanies": 1917,
"credits": 0
}total counts people whose best email is verified, catch_all_valid or catch_all, which is the tool's default. totalAll is the same filters with no email condition. The 2,898 are not a promise that every address will land: read email_status before you send, because bounced emails are not refunded.
A record from search has this shape (response shape from the API reference, placeholder values):
{
"success": true,
"data": [
{
"profile_data": { "profile_full_name": "Jane Doe" },
"contact_data": {
"has_email": true,
"email_status": "verified",
"contact_current_experiences": [ { "job_title": "VP Marketing", "company_name": "Acme" } ]
},
"meta": { "source_ids": { "person_id": "1234567890" } }
}
],
"meta": { "count": 1, "nextCursor": "..." }
}Search rows carry email flags, not addresses. Enrich by meta.source_ids.person_id (Step 3) for the address.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing or wrong x-api-key header | Re-copy the key from app.leadocean.io and check $LEADOCEAN_API_KEY is exported in this shell. |
| 429 | Above the plan's rate limit: 5 requests a second on Free, 200 on Agency | Wait the Retry-After seconds, or batch work into exports. |
| 402 | Out of records. The free plan's 1,000 are one-off | Move to Solo ($97 a month) or Agency ($497 a month). 402 responses cost nothing. |
| Empty results | A filter value that is not an exact enum string, such as "VP Marketing" as a jobLevel | Look the value up with GET /v1/enums or leadocean_list_enum_values. |
| Body rejected on a count | count=true sent in the JSON body | Send it as a query parameter, with limit 1 in the body. |
| Export refused | Over 50,000 rows a request or 500,000 a day, or the call is on the free plan | Split by country or seniority, or move to a paid plan. |
FAQ
Is the dataset real time?
The dataset is refreshed monthly, and each record carries its own fetched_at date. Calls are live: every search and enrichment hits the current index, with no stale file in between.
How many records does the whole recipe spend?
About 120 for the test run: 100 from one search page plus 20 enrichments. Counts, enum lookups and an enrichment that finds nothing are free.
Can I do this without code?
Yes. The Exports page in the app takes the same filters, shows the record price first and downloads a CSV. The app has no sequencer, dialer or CRM, so the CSV goes to the tool you already use.
Which plan do I need for production?
Solo at $97 a month suits one person: 50 requests a second and bulk CSV export included. Agency at $497 a month suits teams: 200 requests a second, 5 keys and a fair-use pace instead of credits.
Where to go next
See real-time B2B data for AI agents, the GTM agent platform comparison, how to build a GTM agent on Claude and pricing.
Size your marketing automation audience for free
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →