Migrating from v1

v1 keeps working exactly as it does today; nothing is being removed. v2 is where new capabilities land: several identities per request, risk scores on every module, and richer email, phone and IP data. Same keys, same billing, same engine - only the response shape differs.

What changes

Several identities per request. v1 takes one identity - with both email and phone present, the email wins and the phone is ignored. v2 sweeps both, and every footprint result names the identity it belongs to.

Grouped response. The footprint lives under footprint, each module is a top-level object (email, phone, ip), and what ran is in meta.modules.

Risk scores. Every module carries risk, and the root risk is the total of every signal that fired - see risk scores. In v1, ipDetails.riskScore is the network source’s own figure and stays so; v2’s ip.risk.score is our score. They are different numbers - do not compare them.

Before and after
// v1
const r = await post("/v1/lookup?wait=true", { email, ip, checks: { ip: true } });
r.matches; r.results; r.ipDetails.isVpn;

// v2
const r = await post("/v2/lookup?wait=true", { email, phone, ip, checks: { email: true, ip: true } });
r.footprint.matches; r.footprint.results; r.ip.isVpn;
r.risk.score; r.email.risk.score; r.ip.risk.score;

Field map

v1v2
POST /v1/lookupPOST /v2/lookup
query (echo)input.{email, phone, username, ip}
platformsChecked, matches, socialMatches, derivedMatches, hits, servedFromCachefootprint.*
results[]footprint.results[] (each with identity)
breaches, webMentions, lockedPlatformsfootprint.breaches, footprint.webMentions, footprint.lockedPlatforms
instant, deadlineMs, platformsPendingmeta.instant, meta.deadlineMs, meta.platformsPending
emailDetailsemail (+ address, customDomain, roleAccount, domain{}, breaches{}, risk)
emailDetails.domainAgeDays / domainRegisteredAtemail.domain.ageDays / email.domain.createdAt
phoneDetailsphone (+ number, e164, nationalFormat, callingCode, carrier-network fields, risk)
ipDetailsip (+ isResidentialProxy, isMobile, isAnonymous, location{}, risk)
ipDetails.ipip.address
ipDetails.countryCode, regionName, cityName, latitude, longitude, timezoneip.location.{countryCode, region, city, latitude, longitude, timezone}
ipDetails.riskScore (network source figure)ip.risk.score (our score, from the rules)
POST /v1/email | /v1/phone | /v1/ip → { emailDetails | phoneDetails | ipDetails }POST /v2/email | /v2/phone | /v2/ip → { lookupId, email | phone | ip }

Stream events

v1 eventv2 event
platform_resultfootprint.result
breach_resultfootprint.breaches
web_mentions_resultfootprint.mentions
email_details_resultemail
phone_details_resultphone
ip_details_resultip
(none)risk
donedone - { lookupId, footprint{counts}, meta }
lookup_errorerror

What does not change

API keys, authentication, rate limits, errors, caching and the null rules are identical. A lookup made on either version can be read back on either version - GET /v1/lookup/:id answers in the v1 shape, GET /v2/lookup/:id in the v2 shape. Webhooks deliver the v2 shape. The v1 reference stays at /docs/v1.