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.
// 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
| v1 | v2 |
|---|---|
| POST /v1/lookup | POST /v2/lookup |
| query (echo) | input.{email, phone, username, ip} |
| platformsChecked, matches, socialMatches, derivedMatches, hits, servedFromCache | footprint.* |
| results[] | footprint.results[] (each with identity) |
| breaches, webMentions, lockedPlatforms | footprint.breaches, footprint.webMentions, footprint.lockedPlatforms |
| instant, deadlineMs, platformsPending | meta.instant, meta.deadlineMs, meta.platformsPending |
| emailDetails | email (+ address, customDomain, roleAccount, domain{}, breaches{}, risk) |
| emailDetails.domainAgeDays / domainRegisteredAt | email.domain.ageDays / email.domain.createdAt |
| phoneDetails | phone (+ number, e164, nationalFormat, callingCode, carrier-network fields, risk) |
| ipDetails | ip (+ isResidentialProxy, isMobile, isAnonymous, location{}, risk) |
| ipDetails.ip | ip.address |
| ipDetails.countryCode, regionName, cityName, latitude, longitude, timezone | ip.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 event | v2 event |
|---|---|
| platform_result | footprint.result |
| breach_result | footprint.breaches |
| web_mentions_result | footprint.mentions |
| email_details_result | |
| phone_details_result | phone |
| ip_details_result | ip |
| (none) | risk |
| done | done - { lookupId, footprint{counts}, meta } |
| lookup_error | error |
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.