API
The same finder the site runs, over REST. POST a list of domains, then read the batch until it is done and download the addresses it found. The API is on the Pro plan at $19 a month and the Agency plan at $49 a month. Keys are created in the dashboard, and a key is shown once.
Use found contacts for individual, relevant outreach only. No bulk unsolicited email. You are responsible for CAN-SPAM, GDPR and CASL compliance.
Authentication
Send the key as a Bearer token on every request. x-api-key works too. A missing, unknown or revoked key is a 401 and nothing else runs.
export OEF_API_KEY=the_key_from_your_dashboard
curl -X POST https://outreachemailfinder.com/api/v1/find \
-H "Authorization: Bearer $OEF_API_KEY" \
-H "Content-Type: application/json" \
-d '{"targets": "a.com\nb.com"}'Find the emails a domain publishes
POST /api/v1/find takes a JSON body with one string, targets, holding one domain or URL per line. A line is normalised the same way the web form normalises it: the scheme, www., the path and the trailing dot are dropped, and a domain listed twice in one body is queued once. A 201 means the batch is on the queue, not that it has finished.
{
"batchId": "cm2q7v0e80000x9k4bq1hn3zt",
"message": "2 domains queued.",
"rejected": []
}A line that cannot be a public hostname is skipped rather than failing the whole body, and it comes back in rejected with the reason. The batch is still queued for the lines that were usable:
{
"batchId": "cm2q7v0e80000x9k4bq1hn3zt",
"message": "1 domain queued. 1 line skipped.",
"rejected": [
{ "input": "localhost", "reason": "not a public hostname" }
]
}Read a batch
GET /api/v1/batches/{id} returns the batch and one row per domain. Poll it until status is DONE or FAILED; doneCount counts the domains that have finished and chargedCount counts the ones that counted against the month. A key reads only its own account batches, and anything else is the same 404 as an id that does not exist.
curl https://outreachemailfinder.com/api/v1/batches/cm2q7v0e80000x9k4bq1hn3zt \ -H "Authorization: Bearer $OEF_API_KEY"
{
"id": "cm2q7v0e80000x9k4bq1hn3zt",
"status": "DONE",
"domainCount": 2,
"doneCount": 2,
"chargedCount": 2,
"createdAt": "2026-10-15T09:12:04.512Z",
"finishedAt": "2026-10-15T09:13:41.007Z",
"expiresAt": "2026-11-14T09:12:04.512Z",
"domains": [
{
"domain": "a.com",
"status": "ok",
"message": "Found 1 email address on 6 pages.",
"pagesFetched": 6,
"charged": true,
"foundEmails": [
{
"address": "[email protected]",
"sourceUrl": "https://a.com/contact",
"pageType": "contact",
"how": "mailto",
"seenCount": 2,
"mx": "found",
"disposable": false,
"role": true,
"freeMail": false,
"personal": false
}
]
},
{
"domain": "b.com",
"status": "no-emails",
"message": "No email address was published on the 4 pages we fetched.",
"pagesFetched": 4,
"charged": true,
"foundEmails": []
}
]
}domain.status is one of ok, no-emails, robots-blocked, bot-wall, dns-fail, unreachable, refused, or failed when the check itself did not complete. message is a plain sentence for the reader, how says where the address sat on the page (mailto, text or written), and mx is found, a-fallback, none or error.
Download the CSV
GET /api/v1/batches/{id}/csv returns the same batch as a file. The columns, in this order: domain,address,labels,mx,source_url,page_type,found_by,domain_status,domain_message. A domain that published no address still gets a row, with an empty address and its status sentence, so the file accounts for every domain you sent.
curl -OJ https://outreachemailfinder.com/api/v1/batches/cm2q7v0e80000x9k4bq1hn3zt/csv \ -H "Authorization: Bearer $OEF_API_KEY"
domain,address,labels,mx,source_url,page_type,found_by,domain_status,domain_message "a.com","[email protected]","Domain accepts mail (MX found); Role address","found","https://a.com/contact","contact","mailto","ok","Found 1 email address on 6 pages."
Limits
- 60 requests a minute per key, in fixed one minute windows. Once the key is accepted, every answer carries
x-ratelimit-limitandx-ratelimit-remaining, and a 429 carriesRetry-After. - Batches of up to 200 domains on Pro and 1,000 on Agency. A line past the cap is skipped and reported in
rejected, and the rest of the batch is queued. - Pro covers 1,000 domains a month and Agency 5,000, shared with the batches you run in the web app. A body the month cannot cover is a 402 and nothing is queued.
- A domain counts only when we fetched at least one of its pages (DECISIONS 6). Invalid lines, duplicates, a domain that does not resolve, one whose robots.txt keeps us off every page, and one the safety guard refuses are not counted.
- A request refused with a 429 is never charged, whether it hit the rate limit or a plan cap.
Errors
Every error is one sentence under error, with the same rate headers as a success:
400the body is not JSON, has notargetsstring, or holds nothing that can be a public hostname.401missing, unknown or revoked key.403the plan does not include the API, or the account is suspended.402the month cannot cover the domains in the body.404no batch this key may read.429the rate limit or a plan cap. Nothing was charged, and nothing was queued.
{
"error": "Missing, unknown or revoked API key. Send it as Authorization: Bearer <key>."
}{
"error": "No valid domains.",
"rejected": [
{ "input": "localhost", "reason": "not a public hostname" }
]
}What the API never does
It reads the pages you name and nothing else. It does not search the web for domains, does not guess an address that no page carries, does not work around a bot wall or a protected address, and sends nothing to the addresses it returns. What it fetches, how often, and how to block it: the crawler page. The duties that stay with you are in the terms.