Getting Started
One target in, one graded answer out. No account anywhere else — just your FetchLayer key.
1. Get your API key
Create an account and generate a FetchLayer API key from your dashboard. The same key works on every platform.
Create free account →2. Find an address
A bare domain is fine. So is a social profile URL or a YouTube channel.
curl -X POST https://api.fetchlayer.dev/email-finder/find-email \
-H "Authorization: Bearer ss-your-key" \
-H "Content-Type: application/json" \
-d '{"target":"python.org"}'That returned psf@python.org at confidence: "high" in 9.5 seconds on 17 September 2026, with sourceUrl: "https://www.python.org/about" — the page it was read off.
3. Read the grade, not just the address
high means the target published it themselves
high, however right the domain looks. stripe.com returned an address that genuinely is @stripe.com, found elsewhere on the web, and graded medium. That is the rule working. Why the rule exists
jane.diaz@stripe.com at high confidence — Stripe’s own fictional demo persona from their checkout examples. A plausible address on a perfect domain, belonging to nobody. Read emailSource, sourceUrl and onTargetDomain alongside the grade, and you can check any answer by opening one page. onTargetDomain is measured against the target you sent
false and grades low. @veritasium returned veritasium@gmail.com found on veritasium.com exactly that way. Correct behaviour — and sourceUrl is what tells you it is still a good address. 4. Handle a “no” properly
Some targets publish nothing, and that is a finished answer rather than a miss.
curl -X POST https://api.fetchlayer.dev/email-finder/find-email \
-H "Authorization: Bearer ss-your-key" \
-H "Content-Type: application/json" \
-d '{"target":"github.com"}'NO_EMAIL_LISTED is a 200, and you are charged for it
github.com, g2.com and ticketmaster.com all answer this way, each with a note naming what was examined. You are billed one credit, the same as a hit, because the work is the same — what you bought is a definitive answer about that target. Do not retry it. A 503 is the one to retry
{"error":"the target could not be read right now, please retry"}. indeed.com returned one in 6.2s, and a domain that does not resolve in 2.8s. Two different statuses on purpose: that is what makes the 200 worth trusting. 5. Check whether the answer is whole
searched[] lists what was examined, incomplete[] lists anything that could not be finished, and complete summarises the two.
Which inputs are strong
https://x.com/paulg came back a 200 with complete: false and incomplete: ["web-search"]. Read complete rather than inferring wholeness from how long the call took. 6. Triage a list before spending on it
Free, fetches nothing, and tells you what kind of target you have and what a lookup would examine.
curl -X POST https://api.fetchlayer.dev/email-finder/resolve-target \
-H "Authorization: Bearer ss-your-key" \
-H "Content-Type: application/json" \
-d '{"target":"https://x.com/naval"}'Answers in about a tenth of a second with targetKind, resolvedUrl, wouldSearch[] and pagesFetched: 0.
Speed and billing
A lookup took between 9.5 and 101.6 seconds across the targets measured, driven by how much there is to look through rather than by the kind of target. Budget against the top of that range and run lookups in the background.
One credit per completed lookup, hit or not, reported as pagesFetched: 1. /resolve-target is free. pagesCrawled (1 to 65 across the same set) shows how big a search was and prices nothing.
Errors and retries
A 400 names the field — a missing target, or an unrecognised key. A 401 means the key is missing or invalid. A 503 means the target could not be read; that is the one worth retrying. A NO_EMAIL_LISTED is not an error and should not be retried.
Error responses
Every error, on every route, is JSON in one shape: error (what went wrong, in words), status, code, retryable and requestId, which also comes as the X-Request-Id header. Branch on code and retryable rather than on the text; on a 5xx the text is generic. Quote requestId when reporting a problem.
{
"error": "Missing required parameter: url",
"status": 400,
"missing": ["url"],
"code": "invalid_input",
"retryable": false,
"requestId": "922874e3-c0d0-40d8-8866-d198af132420"
}| code | Status | Retryable | Meaning |
|---|---|---|---|
| invalid_input | 400 | no | The request needs fixing; error names the field. A missing required parameter also lists it in missing. |
| unauthorized | 401 | no | The API key is missing or not valid. |
| insufficient_credits | 402 | no | Nothing left to spend: no credits, a used-up free trial, or a monthly allowance and credit balance both spent. error says which. Waiting does not help; topping up does. |
| forbidden | 403 | no | This key or account is not allowed this request. |
| not_found | 404 | no | What the request names does not exist or is not public. |
| expired | 410 | no | What the request refers to is no longer available; ask for it afresh. |
| quota_exceeded | 429 | no | A request-rate limit was reached; error says which. Slow down before sending more. |
| cancelled | 499 | no | The caller closed or cancelled the request before it finished. The work was stopped and nothing was charged. |
| time_limit | 503 | yes | Your timeoutMs ran out before there was anything to return; ask again, or allow more time. |
| temporarily_unavailable | 503 | yes | Not available right now; ask again shortly. |
| posts_unavailable | 403 | no | TikTok user videos: TikTok is not showing this account’s posts publicly right now. Asking again at once returns the same answer; nothing is charged. |
| store_unreachable | 503 | yes | Store routes (Shopify, WooCommerce): the store itself can’t be reached right now — it may be offline. Ask again later. Where a store route doesn’t say so yet, an offline store is temporarily_unavailable. |
| upstream_error | 502 | yes | The source answered in a way that could not be used; ask again later. |
| internal_error | 500 | yes | Something failed on our side; ask again later, and quote requestId if it persists. |
| tool_error | — | no | MCP only: a tool call failed in a way no other code describes; error says what. |
More codes may be added; treat one you do not recognise by its retryable flag. Over MCP, a tool error carries the same fields in structuredContent, and as a JSON text block beside the message.