TL;DR
- A Maps listing gives you a business name, a phone and usually a website. It rarely gives you an email. The website domain is the bridge to a person.
- It takes about 15 minutes for one listing. The API route spends about 4 LeadOcean records per business: 3 for a search at
limit3, plus 1 for the work-email lookup. - For a list, pull the domains out of your Maps export and run one export. Each row costs one record, and you read
email_statusbefore you send.
Prerequisites
- A LeadOcean account from app.leadocean.io. The free plan has no card and 1,000 records, one-off.
curl,jqandawk.- A list of Maps listings with a website column. A saved list or a CSV you already hold works.
- An API key in your shell as
$LEADOCEAN_API_KEY. Free keys get both scopes, search and enrich.
export LEADOCEAN_API_KEY="paste-your-key-here"Step 1: Read the listing and copy the website (the manual way)
Open the listing and copy the Website field. That is the only part of a Maps listing that leads to an email. The phone and address do not.
Acme Plumbing
123 Example St
+1 415 555 0133
Website: https://www.acme.com/Strip the scheme and www. so you hold the bare domain: acme.com. If the listing has no website, stop. You can still search by company name later, but a domain is cleaner.
Step 2: Check the site for a published address
Small businesses often print an address on the contact page. Fetch the page and grep for a mailto: link.
curl -sL https://www.acme.com/contact | grep -oiE 'mailto:[^"?]+' | sort -uYou will usually find a shared box such as info@ or hello@. That reaches a front desk, not the owner. Use it only if a shared box is what you want, and check Google's terms before you automate anything against Maps itself.
For the person who decides, go on to the next step.
Step 3: Pull the domains out of a list of listings
If you have many listings, extract the domains once. This reads a CSV with the website in column 4 and writes one unique domain per line.
awk -F',' 'NR>1 {print $4}' maps-listings.csv \
| sed -E 's#https?://##; s#^www\.##; s#/.*$##' \
| sort -u > domains.txt
wc -l domains.txtAdjust the column number to your file. Drop chains and directory sites from the list: a franchise domain returns head-office staff, not the local owner.
Step 4: Size the people at a domain for free
Ask how many owners and founders we hold at the domain before you pull a record. A search with count=true as a query parameter and limit at 1 is a free sizing call. Send the domain as an array.
curl -s "https://api.leadocean.io/v1/people/search?count=true" \
-H "x-api-key: $LEADOCEAN_API_KEY" -H "content-type: application/json" \
-d '{
"domain": ["acme.com"],
"seniority": ["owner", "founder"],
"limit": 1
}' | jq '.meta | {total, totalIsExact, credits}'A zero means we hold nobody at that level there. Skip the domain and save the records. Seniority values are lowercase strings from the catalogue.
Step 5: Search the domain for the decision maker
There is no domain-only email lookup, because an address belongs to a person. Search the domain by seniority or title and keep limit small. Each person returned costs one record.
curl -s "https://api.leadocean.io/v1/people/search" \
-H "x-api-key: $LEADOCEAN_API_KEY" -H "content-type: application/json" \
-d '{
"domain": ["acme.com"],
"seniority": ["owner", "founder"],
"limit": 3
}' | jq '{count: .meta.count, people: [.data[] | {person_id: .meta.source_ids.person_id, profile_data}]}'A search row is thin: name, headline and current role. It carries no address. Each row has a person_id in meta.source_ids.
Step 6: Get the work email for one person
Send the person_id to the work-email endpoint. It returns one address, or null, and only an address at the person's current employer that is verified, catch_all_valid or catch_all.
PERSON_ID="paste-person_id-from-step-5"
curl -s "https://api.leadocean.io/v2/people/email/work?person_id=$PERSON_ID" \
-H "x-api-key: $LEADOCEAN_API_KEY" | jq '.data'This costs one record per call, including a call that finds nothing. A null means no address qualifies, not that the person has no email.
Step 7: Do the whole list with an export
For more than a handful of businesses, export instead of looping. An export writes one CSV with numbered columns such as email_1_address and email_1_status. A request takes up to 50,000 rows and a day takes up to 500,000. Bulk CSV export is not on the Free plan.
curl -s https://api.leadocean.io/v1/exports \
-H "x-api-key: $LEADOCEAN_API_KEY" -H "content-type: application/json" \
-d '{
"name": "Owners at three Maps domains",
"filters": {
"domain": ["acme.com", "example.com", "example.org"],
"seniority": ["owner", "founder"],
"emailStatus": ["verified", "catch_all_valid", "catch_all"]
},
"limit": 100,
"preset": "full"
}' | jq '.data | {id, status, requested, estimatedTotal}'Each row written costs one record. Poll GET /v1/exports/{id}, then fetch GET /v1/exports/{id}/download when the status is done. The app does the same on its Exports page, with a filter builder, a column picker and the record price before you start.
What you get
If you only need the audience, you do not need your own Maps list. We ran leadocean_count_leads on 2026-10-21, which spends 0 records, for seniority owner in the Restaurants industry in the United States.
{
"total": 38435,
"totalIsExact": true,
"totalAll": 209411,
"distinctCompanies": 11248
}That is 38,435 mailable owners on 2026-10-21 (verified, catch_all_valid or catch_all), across 11,248 companies. Without an email filter the same filters match 209,411 people. It is a mailable count, not a count of Maps listings.
The work-email lookup returns the shape below. This is the response shape from the API reference, with placeholder values.
{
"success": true,
"data": {
"email": "jane.doe@acme.com",
"status": "verified",
"verified_batch_date": "2026-09-20"
}
}LeadOcean gives no refund or credit-back for bounced emails, so read status before you send.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing, wrong or revoked key | Re-copy the key from app.leadocean.io and export $LEADOCEAN_API_KEY again. |
| 429 | Above the plan's rate limit (5 requests a second on Free, 200 on Agency) | Wait for Retry-After, slow the loop, or use an export for large batches. |
| 402 | The 1,000 free records are spent | Check GET /v1/account, or move to a paid plan on pricing. Free records do not reset. Paid plans slow down instead of answering 402. |
| Search returns no rows | A filter value outside the catalogue, such as Owner for seniority | Seniority is lowercase, owner. Check strings with GET /v1/enums or leadocean_list_enum_values. |
data: null on the work email | No address at the current employer is verified, catch_all_valid or catch_all | It still costs one record. Try another person at the domain. |
400 export_limit_rows | The export limit is above 50,000 rows | Split the domains into groups and run several exports. |
FAQ
Does Google Maps show an email address?
Rarely. A listing shows a name, address, phone and website. Take the website domain and find a named person there.
How many records does it cost?
One per person returned by a search and one per work-email call. Counts are free. A search at limit 3 plus one lookup spends about 4 records.
Will I get the owner or a shared inbox?
A domain search by seniority returns named owners and founders. A mailto: link on the site is usually a shared box such as info@.
What if a business has no website?
Search by company name with companyName, which matches name phrases and can match more than one company. Check the domain on each row before you spend a lookup.
Where else can I start from?
A profile works too. See how to find an email from an X profile and how to find an email from an email signature. The find work email skill runs the lookup from Claude. More recipes are in the blog hub, and plans are on pricing.
Turn a Google Maps listing into a checked work email, free
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →