Running a lookup

You are reading the v1 reference. v1 keeps working unchanged; new capabilities (several identities per request, risk scores, richer module data) are in v2. See migrating from v1.

Send one of query, email, or phone. Use query when you don't know which you have, since it is auto-detected.

Synchronous

?wait=true blocks until every platform has answered. Simplest to integrate; expect several seconds.

POST /v1/lookup?wait=true
curl -X POST "https://api.digifootprint.dev/v1/lookup?wait=true" \
  -H "Authorization: Bearer dfp_your_key" \
  -H "Content-Type: application/json" \
  -d '{"query": "someone@example.com"}'

Queued

Without ?wait=true you get a lookupId straight away and poll for the result. For a responsive UI, prefer streaming over polling.

POST /v1/lookup
# Returns immediately with an id to poll
curl -X POST "https://api.digifootprint.dev/v1/lookup" \
  -H "Authorization: Bearer dfp_your_key" \
  -H "Content-Type: application/json" \
  -d '{"query": "someone@example.com"}'

# {"lookupId": "8f2c...", "status": "processing"}

curl "https://api.digifootprint.dev/v1/lookup/8f2c..." \
  -H "Authorization: Bearer dfp_your_key"

Depth: instant, standard and deep

Three depths, selected by query parameter. The default is standard - send neither flag and you get it. ?instant=true and ?deep=true are mutually exclusive; sending both is ambiguous, so send one or neither.

Standard (no flag) checks the default band of platforms and is what the queued and ?wait=true paths above run.

?instant=true is latency-bounded: it runs a high-signal subset against a wall clock (900ms by default) and returns whatever answered in time. It is always synchronous - the point is an answer right now, so it implies ?wait=true and you never need to send it. Reach for it when you are fanning out to several APIs inside one transaction and cannot absorb a slow one.

?deep=true checks the full platform set including the long tail. Slower and unbounded - use it when coverage matters more than latency, and pair it with streaming so results surface as they land. The queued path (POST without wait) already runs at this depth.

POST /v1/lookup?instant=true
# Always synchronous - ?instant=true implies ?wait=true
curl -X POST "https://api.digifootprint.dev/v1/lookup?instant=true" \
  -H "Authorization: Bearer dfp_your_key" \
  -H "Content-Type: application/json" \
  -d '{"query": "someone@example.com"}'

Reading an instant response

An instant lookup adds three fields that are absent from every other response, so an existing integration sees nothing new unless it asks for instant.

200 OK - ?instant=true
{
  "query": "someone@example.com",
  "instant": true,
  "deadlineMs": 600,
  "platformsChecked": 25,
  "platformsPending": 3,
  "matches": 2,
  "results": [
    { "platform": "github",  "registered": true, "method": "public_api", "checkedAt": "2026-08-31T09:12:04.377Z" },
    { "platform": "spotify", "registered": true, "method": "public_api", "checkedAt": "2026-08-31T09:12:04.201Z" }
  ],
  "breaches": [],
  "webMentions": null
}

instant: true marks the response as bounded. deadlineMs echoes the budget it was held to, so you can tell which ceiling produced this answer. platformsPending is the one to branch on: it counts platforms that had not answered when the deadline hit.

A non-zero platformsPending means fast, not complete. Those platforms were not checked and found absent - they simply ran out of time, and a standard lookup would likely return more. This is the same rule as everywhere else here: a platform missing from results was never confidently answered, and instant mode only adds “we ran out of time” to the list of reasons why. Nothing is guessed to fill the gap. See instant vs deep lookups for choosing between them.

Choosing which platforms to check

By default a lookup sweeps the whole band for its depth. Send platforms to check only the ones you name - useful when you care about one specific service and do not want to pay the latency of a full sweep. A narrowed lookup is dramatically faster: one platform answers in tens of milliseconds against roughly fourteen seconds for the full set.

POST /v1/lookup?wait=true
curl -X POST "https://api.digifootprint.dev/v1/lookup?wait=true" \
  -H "Authorization: Bearer dfp_your_key" \
  -H "Content-Type: application/json" \
  -d '{"query": "someone@example.com", "platforms": ["google", "github"]}'

The list overrides depth selection rather than narrowing within it, so you can name a platform that is not in the instant band and still get it. Combining it with ?instant=true keeps the latency ceiling and drops the band. It also implies ?wait=true: the queued path re-derives its own work and would discard your selection.

An unknown slug is rejected, not ignored. A typo returns 422 unknown_platforms naming exactly what was not recognised - silently checking fewer platforms than you asked for would tell you a service came back clean when it was never checked at all.

A platform that is real but cannot be reached from the input you sent - asking for reddit with an email address, when Reddit is keyed on a username - is skipped instead, and simply absent from results. One incompatible entry does not fail the whole request. Every slug and the inputs that reach it are listed by GET /v1/platforms.

The response

200 OK
{
  "lookupId": "8f2c1d3e-...",
  "query": "someone@example.com",
  "platformsChecked": 844,
  "matches": 3,
  "socialMatches": 2,
  "servedFromCache": false,
  "results": [
    {
      "platform": "github",
      "registered": true,
      "method": "public_api",
      "icon": "https://.../github.svg",
      "checkedAt": "2026-08-26T11:40:55.902Z"
    },
    {
      "platform": "google",
      "registered": true,
      "method": "domain_derived",
      "icon": "https://.../google.svg",
      "checkedAt": "2026-08-26T11:40:55.902Z",
      "mailboxProvider": true
    }
  ],
  "breaches": [{ "name": "Adobe", "date": "2013-10-04" }],
  "webMentions": null,
  "phoneDetails": null
}

Absent is not negative. A platform we could not get a confident answer for is omitted from results entirely, rather than returned as registered: false. Not every one of the platforms we check will appear. A site that blocked us or timed out is an unknown, and we will not report an unknown as a fact.

hits is the short answer. hits lists the platforms that came back registered, so you do not have to filter results yourself. It always has exactly matches entries - both come from the same place - and it excludes derived-handle hits for the same reason matches does: those say a handle exists, not that this person owns it.

Modules are opt-in. A lookup returns the digital footprint - which platforms the identity is registered on, plus breaches and web mentions - and that is what every request gets by default. Email, phone and IP intelligence are separate modules, switched on per request with checks:

Opting in to a module
{
  "email": "someone@example.com",
  "checks": { "email": true, "ip": true },
  "ip": "8.8.8.8"
}

The complete set of keys is footprint, email, phone and ip. Only footprint runs without being asked for; the other three are off unless a request names them. Breach records and web mentions are part of the footprint rather than keys of their own, so {"footprint": false} switches them off together with the platform sweep. An unknown key is ignored rather than rejected, so a typo silently does nothing - check the field you expected is populated.

With a module enabled, its field is populated: emailDetails (deliverable, disposable, catch-all, domain age), phoneDetails (carrier, line type, and whether the line is an app-minted virtual number), or ipDetails (risk score, VPN and proxy flags, network owner). Each is documented on its own page, and each is also available as a standalone endpoint that skips the platform sweep entirely.

Setting {"checks": {"footprint": false}} turns the platform sweep off, which is how the standalone endpoints answer in about a second instead of tens of seconds.

null is not empty. breaches and webMentions are null when that source is not available on this deployment (it is not a plan feature - upgrading does not change it), which is a different fact from [] meaning we checked and found nothing. A breach date is the ISO date (YYYY-MM-DD) the breach occurred, not when it was disclosed.

lockedPlatforms is a paywall, not a miss. Present only when the account’s plan did not include some of the platforms this query could reach - today the free trial’s phone lookup, which checks WhatsApp only and lists Telegram, Facebook, Instagram and X here. Those platforms were not checked at all, are not counted in platformsChecked, and are unlocked by subscribing or topping up. Absent on paid accounts, so an existing integration sees no new field.

matches vs socialMatches. matches counts every registration. socialMatches excludes accounts that came with the email address rather than being signed up for. A Microsoft 365 tenant provisions an account for every employee, so that result is true of the whole company and says nothing about the individual. Those carry mailboxProvider: true. For fraud scoring, socialMatches is usually the number you want.

Listing past lookups

Scoped to your own account, most recent first. Limited to 20 requests a minute, see rate limits.

GET /v1/lookup
curl "https://api.digifootprint.dev/v1/lookup?limit=20&offset=0" \
  -H "Authorization: Bearer dfp_your_key"