Skip to content
Developers

TraceFind API

Run email, username and phone searches from your own code, and read your search history and PDF reports. The API uses the same search engine as the site and is billed the same way: 5 credits per search, refunded when nothing is found.

Base URL
{API base URL}
Format
JSON over HTTPS
Auth
Bearer token from your dashboard

Endpoints at a glance

EndpointScopeWhat it does
POST /api/v1/searchessearchStart a search, optionally waiting for the result
GET /api/v1/searches/{id}readStatus of one API search, with results once done
GET /api/v1/searchesreadYour most recent API searches
GET /api/v1/reportsreadYour full search history (site and API), paged
GET /api/v1/reports/{report_id}readOne report with its results
GET /api/v1/reports/{report_id}/pdfreadThe report as a PDF file
GET /api/v1/accountreadCredits left, token details and limits

"read" endpoints work with both token scopes; only POST /api/v1/searches needs a search token.

Using an AI assistant such as Claude, Cursor or VS Code? Connect it to the TraceFind MCP server and it can run these searches as tools, with the same token.

Quick start

  1. Buy credits. API searches spend purchased credits only (the free signup credits are for the website). Buy credits.
  2. Create a token. Go to Account → API, name the token after the tool that will use it, pick the Search and read scope and copy the token. It is shown once.
  3. Store it as an environment variable so it never ends up in your source code:
    export TRACEFIND_TOKEN="tf_live_…"
  4. Run your first search. With "wait": 90 the request holds until the search finishes (most do within a minute). If it is still running after that, the response says "status": "running" and you poll GET /api/v1/searches/{id}.
curl -X POST {API base URL}/api/v1/searches \
  -H "Authorization: Bearer $TRACEFIND_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"type": "email", "query": "someone@example.com", "wait": 90}'

Keep tokens on your server. Never put one in a web page, mobile app or public repository: anyone who has it can spend your credits.

Authentication

Every request needs an API token in the Authorization header:

Authorization: Bearer tf_live_3hV0q…

Tokens start with tf_live_ followed by 43 random characters. Create, list and revoke them on your dashboard under Account → API.

ScopeCan doUse it for
search
Search and read
Start searches (spends credits), read searches, reports, PDFs and the accountScripts and integrations that run lookups
read
Read only
Everything except starting searchesDashboards, exports and backups of your history
  • Shown once. Only a SHA-256 hash of the token is stored, so we cannot show it to you again. Lost it? Revoke it and create a new one.
  • Expiry. A token can expire after 30, 90 or 365 days, or never. Revoking takes effect on the very next request.
  • Up to 10 active tokens per account. Use one per tool, so you can cut one off without touching the others; the dashboard shows when each was last used.
  • Not your login. The API never accepts your account ID and never returns it, so a leaked token cannot be used to take over your account.

A missing, wrong, revoked or expired token returns 401 with a WWW-Authenticate: Bearer header:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer

{"detail": "This API token is invalid or has been revoked."}

Searches

An API search runs the same checks as the website. It is queued, picked up by a worker within a second or two, and saved to your history as a report when it finishes.

POST/api/v1/searches

Starts a search. Needs a search token and at least 5 purchased credits.

Request body (JSON)

FieldTypeDescription
type requiredstringemail, username or phone (case-insensitive).
query requiredstringWhat to search, 1–320 characters. Phone numbers in international format (+4915112345678); spaces and dashes are fine. Usernames: 2–64 characters of letters, digits, ., _ and -.
waitintegerSeconds (0–120) to hold the request until the search finishes. Default 0: return at once with "status": "queued". Set your HTTP client's timeout a little higher than this.
curl -X POST {API base URL}/api/v1/searches \
  -H "Authorization: Bearer $TRACEFIND_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"type": "phone", "query": "+49 151 12345678"}'
Response 201 Created
{
  "id": "4CQL0DSL4KCC",
  "type": "phone",
  "query": "+4915112345678",
  "status": "queued",
  "created_at": "2026-11-01T12:34:05.118000+00:00",
  "finished_at": null,
  "found": false,
  "report_id": null,
  "summary": null,
  "error": null,
  "credits_spent": 0
}

GET/api/v1/searches

Your most recent API searches, newest first, without results.

QueryTypeDescription
limitinteger1–100, default 20.
curl "{API base URL}/api/v1/searches?limit=5" -H "Authorization: Bearer $TRACEFIND_TOKEN"
Response 200 OK
{"searches": [ {"id": "4CQL0DSL4KCC", "type": "phone", "status": "done", …}, … ]}

The search object

FieldTypeDescription
idstring12 characters, A–Z and 0–9.
type, querystringWhat was searched; query is normalised (e.g. phone numbers without spaces).
statusstringSee the table below.
created_at, finished_atstring | nullISO 8601 timestamps in UTC.
foundbooleanWhether anything turned up. If false the search was free.
report_idstring | nullThe report in your history, for reports and the PDF.
summaryobject | nullA few headline numbers, see below.
errorstring | nullWhy a search failed.
credits_spentinteger5 when something was found, otherwise 0.
resultsarrayOnly on GET /searches/{id} and POST once the status is done. See result format.
statusMeaning
queuedWaiting for a worker; usually a second or two.
runningSearching.
doneFinished. found says whether anything turned up; results holds it.
failedThe search did not finish (see error). Nothing is charged; start it again.
waiting_for_creditsYour credits ran out before it could start. It continues once you buy credits.
cancelledStopped before it ran.

The summary object

SearchFields
emailhits, breaches, accounts, breach_names (up to 8), account_names (up to 8), deliverable (true, false or null if unknown)
usernamehits, profiles, sites (up to 12 site names)
phonehits, messengers (e.g. ["WhatsApp", "Telegram"]), country, carrier

Reports

Every search you have run, on the website or through the API, is saved as a report in your history.

GET/api/v1/reports

Your history, newest first.

QueryTypeDescription
typestringall (default), email, username or phone.
limitinteger1–50, default 20.
offsetintegerHow many reports to skip, default 0. Page until a response has fewer than limit reports.
resultsbooleantrue includes each report's results; otherwise you get a result_count instead (much smaller).
curl "{API base URL}/api/v1/reports?type=email&limit=10" \
  -H "Authorization: Bearer $TRACEFIND_TOKEN"
Response 200 OK
{
  "reports": [
    {
      "report_id": "K2DD8QW1ZJ4M",
      "timestamp": "2026-10-30T09:12:44.507000+00:00",
      "search_type": "email",
      "search_term": "someone@example.com",
      "tier": "paid",
      "locked": false,
      "result_count": 14
    }
  ],
  "limit": 10,
  "offset": 0
}

GET/api/v1/reports/{report_id}

One report with its results. A report from the free trial on an account that has never bought credits comes back as a preview, as on the site: "locked": true, an empty results list and a preview object with counts per category. Buying credits unlocks it.

curl {API base URL}/api/v1/reports/K2DD8QW1ZJ4M -H "Authorization: Bearer $TRACEFIND_TOKEN"
Response 200 OK
{
  "report_id": "K2DD8QW1ZJ4M",
  "timestamp": "2026-10-30T09:12:44.507000+00:00",
  "search_type": "email",
  "search_term": "someone@example.com",
  "tier": "paid",
  "locked": false,
  "results": [
    {"type": "social", "name": "Spotify", "url": "https://spotify.com", "others": {…}},
    {"type": "leaks", "data": [{"Name": "ExampleBreach", …}]},
    {"type": "email_verify", "data": {"available": true, "reachable": true, …}},
    …
  ]
}

GET/api/v1/reports/{report_id}/pdf

The report as a PDF (application/pdf, file name tracefind_{report_id}.pdf), the same document as the site's export. See the example report. Free previews return 402.

curl -o report.pdf {API base URL}/api/v1/reports/K2DD8QW1ZJ4M/pdf \
  -H "Authorization: Bearer $TRACEFIND_TOKEN"

Account

GET/api/v1/account

Your purchased credits, how many searches they cover, and the calling token's name, scope, expiry and limits. A cheap way to check a token works.

curl {API base URL}/api/v1/account -H "Authorization: Bearer $TRACEFIND_TOKEN"
Response 200 OK
{
  "credits": 135,
  "searches_left": 27,
  "search_cost": 5,
  "token": {"name": "CRM sync", "scope": "search", "expires_at": null},
  "limits": {"requests_per_minute": 60, "running_searches": 5}
}

MCP server

TraceFind is also an MCP server, so AI assistants and agents can use it directly. Connect Claude, Cursor, VS Code or any other MCP client, and the assistant can run searches and read your reports when you ask it to, for example "check which accounts are linked to jane.doe@example.com".

Server URL
{API base URL}/mcp
Transport
Streamable HTTP
Auth
The same API token

It is the API above in another form: the same tokens and scopes, the same rate limit, and the same billing (5 credits per search, refunded when nothing is found). Give the assistant a token of its own, so you can see when it was last used and revoke it on its own. A read token lets the assistant read your history without being able to spend credits.

Connect your client

# In a terminal, with TRACEFIND_TOKEN set as in the quick start.
claude mcp add --transport http tracefind {API base URL}/mcp \
  --header "Authorization: Bearer $TRACEFIND_TOKEN"
  • Claude Code: run the command in a terminal.
  • Claude Desktop: Settings → Developer → Edit Config (claude_desktop_config.json), then restart Claude. Needs Node.js.
  • Cursor: ~/.cursor/mcp.json (or .cursor/mcp.json in a project).
  • VS Code: .vscode/mcp.json in your project, or "MCP: Open User Configuration" for all projects.
  • Windsurf: ~/.codeium/windsurf/mcp_config.json, then refresh the MCP servers in Cascade.
  • Codex CLI: ~/.codex/config.toml, with the token in the TRACEFIND_TOKEN environment variable.
  • Gemini CLI: ~/.gemini/settings.json (or .gemini/settings.json in a project).
  • Cline: Cline → MCP Servers → Configure MCP Servers (cline_mcp_settings.json).

Restart the client after editing its config. A token in a config file is stored in plain text, so keep that file private. Signed in? Account → API has the same snippets with one-click install for Cursor and VS Code, and can fill in your token for you.

Tools

ToolScopeWhat it does
search_startsearchRun an email, username or phone search and wait up to 50 seconds for the result (wait, up to 120). Spends credits.
search_getreadStatus of a search, with its findings once it is done
search_listreadRecent searches started through the API or MCP
report_listreadYour whole search history, newest first
report_getreadOne report with all of its findings
get_accountreadCredits left, the token's scope and limits

With a read token, search_start is not offered to the assistant at all. Errors such as too few credits come back to the assistant as a readable message, so it can tell you what went wrong.

Test it without a client

The server speaks JSON-RPC over plain HTTP POST, so curl is enough to check that a token works:

curl -X POST {API base URL}/mcp \
  -H "Authorization: Bearer $TRACEFIND_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}'

Only search people you have a lawful reason to look into; that applies to an assistant acting for you as much as to you. See the legal and ethical guide.

Result format

results is a list of objects, each with a type. Which types appear depends on the search, and a type only appears when that check returned something:

SearchResult types
Emailsocial (an account on a site, with name, url and any profile details in others), leaks (data breaches in data), google (public Google account details), email_verify (whether the mailbox exists), domain_validation (the domain's mail setup)
Usernameusername (a profile, with sitename, url, tags and extracted profile data such as display name, bio or avatar)
Phonelookup (validity, carrier, country, line type), whatsapp, telegram, signal, facebook, instagram (each with data.exists), temp (listings on receive-SMS sites), mentions (pages that mention the number)

Forward compatible: new result types and new fields may be added to /api/v1 at any time. Ignore what you don't recognise; fields will not be removed or renamed within v1.

Errors

Errors use standard HTTP status codes and a JSON body with a human-readable detail string, safe to log or show:

HTTP/1.1 402 Payment Required

{"detail": "A search needs 5 purchased credits. Buy credits at https://tracefind.info/purchase.html"}
StatusWhenWhat to do
400Unknown type, or the query is not a valid email, username or phone number.Fix the input; don't retry.
401No token, or it is wrong, revoked or expired, or the account was deleted.Check the header; create a new token.
402Fewer than 5 purchased credits, or a PDF of a free preview.Buy credits.
403A read-only token tried to start a search.Use a token with the search scope.
404No such search or report on your account.Check the ID.
422The request is malformed: missing field, wrong type, wait above 120, limit out of range.Fix the request; detail is a list naming the field.
429More than 60 requests in a minute, or 5 API searches already running.Wait and retry (see below).
5xxSomething went wrong on our side.Retry with backoff; nothing was charged.

For 422, detail is a list of problems instead of a string:

{"detail": [{"type": "less_than_equal", "loc": ["body", "wait"],
             "msg": "Input should be less than or equal to 120", "input": 300}]}

Limits and credits

LimitValueOn exceeding it
Requests per token60 per minute (counted per calendar minute)429 with a Retry-After header (seconds until the next minute)
Running API searches per account5 queued or running at once429, no Retry-After: retry once one of your searches finishes
Active tokens per account10Revoke one on the dashboard
wait on POST /searches120 seconds422

Polling counts towards the request limit: one poll every 5 seconds is 12 requests a minute per search. Use wait instead of polling where you can.

Credits

  • A search costs 5 credits and is free when it finds nothing or fails, the same as on the site.
  • API searches use purchased credits only, not the free signup credits. Buy credits; they don't expire.
  • Reading searches, reports, PDFs and the account is free.
  • If your credits run out while a search waits for a worker, it pauses as waiting_for_credits instead of failing.

Acceptable use

Searches only query public sources and sign-up checks; the person you search is never contacted or notified. Use the API only for lawful purposes and with a legitimate reason, as set out in our terms of service. Results can contain personal data: store and share them according to the data protection law that applies to you (for example the GDPR).

Recipes

Retry on 429 with backoff (Python)

def call(method, path, **kw):
    for attempt in range(5):
        r = requests.request(method, f"{API}{path}", headers=HEADERS, timeout=130, **kw)
        if r.status_code != 429 and r.status_code < 500:
            break
        time.sleep(int(r.headers.get("Retry-After", 2 ** attempt * 5)))
    if not r.ok:
        raise RuntimeError(f"{r.status_code}: {r.json().get('detail')}")
    return r.json()

Search a list, at most 5 at a time (JavaScript)

const usernames = ['johndoe', 'jane_d', 'jd1990'];
const results = [];
for (let i = 0; i < usernames.length; i += 5) {
    const batch = usernames.slice(i, i + 5).map((query) =>
        fetch(`${API}/searches`, {
            method: 'POST', headers,
            body: JSON.stringify({ type: 'username', query, wait: 120 }),
        }).then((r) => r.json()));
    results.push(...(await Promise.all(batch)));
}
console.table(results.map((s) => ({ query: s.query, status: s.status, hits: s.summary?.hits })));

Export your whole history to JSON (Python)

reports, offset = [], 0
while True:
    page = requests.get(f"{API}/reports", headers=HEADERS,
                        params={"limit": 50, "offset": offset, "results": "true"}).json()["reports"]
    reports += page
    if len(page) < 50:
        break
    offset += 50
json.dump(reports, open("tracefind-history.json", "w"), indent=2)

Running many searches from a spreadsheet? The bulk search page does the same without code.

Create a tokenQuestions? Help centre