IP Lookup API — geolocation, RDAP, ASN and security data
IP lookUp is a free REST API that turns any IP address into geolocation, RDAP registry, ASN/network, and VPN/proxy/threat-intel data. Call GET /api/v1/ip for a baseline result with no signup, or add ?include=geo,network,security for the full field set documented below.
Intro
IP lookUp turns a caller's (or any queried) IP address into trust & security signals, geolocation, and network/ASN identity — resolved live from RDAP, geo, and threat-intel data rather than static lookup tables.
Base URL:
/api/v1
A basic lookup needs no signup or API key — enrichment groups and higher limits unlock with a free token.
Quickstart
1. Basic lookup — no key required
curl /api/v1/ip
2. Add enrichment groups
curl "/api/v1/ip?include=geo,network,security"
3. Authenticate with a token for higher limits
curl -H "Authorization: Bearer <token>" "/api/v1/ip?include=all"
Try it
Build a real request against this API and see the live response.
Endpoint
GET /api/v1/ip
Include groups
Status: · Time:
What data you get
Every field below is part of the v2 JSON response envelope, generated from the live API schema.
Geo
| Field | Type | Description |
|---|---|---|
| geo.alternatives | web.geoAltV2[] | Alternatives are the losing geo sources' readings for this IP: each is that source's most-specific covering location plus its own source name and date. Omitted when only one source covers the IP. |
| geo.city | string | City is the city name from the winning source; "geo" group. |
| geo.cityNameLocal | string | CityNameLocal is the city's Russian name from the offline GeoNames gazetteer — resolved from the winner's (countryCode, region, city) text. Empty when the gazetteer has no match, no ru altname exists for the matched city, or there was nothing to resolve (no country/city on the winner). Part of the "geo" group like City itself — omitted on a baseline country-only response. |
| geo.confidence | string | Confidence flags city-level geo precision as "low" for network types where the city reading is unreliable (by default, mobile/carrier ranges, where the registered location often differs from the subscriber's). |
| geo.continent | string | Continent is the two-letter continent code, re-derived alongside CountryName from the country table; baseline. |
| geo.countryCode | string | CountryCode is the ISO 3166-1 alpha-2 country code from the winning geo source (DB-IP City Lite or an RFC 8805 geofeed); baseline (always present whenever any geo data exists for the IP). |
| geo.countryName | string | CountryName is the country's display name, re-derived from the country table for CountryCode (so a country-only override never carries a stale name); baseline. |
| geo.date | string | Date is the winning geo source's last-seen date, RFC3339 UTC date-time — the same format as the top-level cachedAt. Omitted when unknown. |
| geo.geonameId | integer | GeonameID is the matched GeoNames gazetteer entry's id — a stable cross-source city identifier distinct from City's raw text. Nilable: nil (JSON null/absent) means a resolve miss, never a fabricated 0. Part of the "geo" group like City itself. |
| geo.isEu | boolean | IsEu reports whether CountryCode is an EU member state, re-derived from the country table like CountryName/Continent; baseline. |
| geo.lat | number | Lat is the latitude in decimal degrees; nil means genuinely unknown, distinct from a real 0.0 reading at the equator. "geo" group. |
| geo.license | string | License is the winning source's data license (e.g. "CC BY 4.0" for dbip-city) — provenance/attribution detail, not baseline. Present only alongside the extended geo fields above, on a verbose request. Omitted when the source has no recorded license. |
| geo.lon | number | Lon is the longitude in decimal degrees; same nil-vs-0.0 semantics as Lat. "geo" group. |
| geo.matchConfidence | string | MatchConfidence is the match-quality signal for the cityNameLocal/geonameId resolve: "high" when country+admin1+name all matched, "low" when it fell back to a country+name-only homonym pick. Omitted (not "unknown") on a resolve miss — a consumer must not read absence as low confidence, only as "no match at all". Deliberately a SEPARATE field from Confidence above: Confidence is net-type-driven geo precision (mobile/MVNO carrier heuristic), MatchConfidence is gazetteer string-match quality — unrelated signals, never merged. |
| geo.postal | string | Postal is the postal/ZIP code; always empty today (DB-IP City Lite carries no postal data) — reserved for a future source. "geo" group. |
| geo.region | string | Region is the state/province/administrative-region name from the winning source; part of the "geo" group (entitlement.SetGeo) — empty on a baseline country-only response. |
| geo.regionMatchConfidence | string | RegionMatchConfidence reports the stored raw-region to GeoNames admin1 resolution quality. Omitted when the raw fallback is used. |
| geo.retrievedDate | string | RetrievedDate is when the continuous RIR crawler retrieved the winning source observation, RFC3339 UTC. Verbose-only and omitted for non-RIR or legacy unmarked RIR rows; this is deliberately distinct from Date (SCD-2 last_seen). |
| geo.source | string | Source is geo provenance: the name of the trust-ranked winning geo source (e.g. "dbip-city" or "aws-geofeed"). Always populated when geo is present (the winner is whichever source the merge picked); omitempty is kept only for the degenerate case of an unnamed source. |
| geo.timezone | string | Timezone is the IANA zone name (e.g. "Europe/Moscow") for the resolved location; "geo" group. |
| geo.url | string | URL is the winning geo source's own reference link (its dataset homepage or download page, e.g. "https://db-ip.com/db/lite.php" for dbip-city) — attribution/provenance detail alongside License, not a per-IP resource link. Same gating and omission rule as License. |
Network
| Field | Type | Description |
|---|---|---|
| network.allAsns | string[] | AllASNs lists every ASN Team Cymru reports announcing this prefix (multi-origin); always a non-nil array, empty when there's only one/none. |
| network.allocated | string | Allocated is Prefix's allocation date from Team Cymru — an allocation TIMESTAMP, not a usage-type flag (see UsageHint for that). |
| network.asName | string | ASName is the Team Cymru AS holder name — the coarse registered name, distinct from the refined network.classification.provider. |
| network.asn | string | ASN is the announcing Autonomous System number from Team Cymru (e.g. "AS15169"); baseline (always present for a routable IP with ASN data). |
| network.asnDomain | string | AsnDomain is the offline iplocate org's domain, sibling to AsnOrg; nil under the same conditions. |
| network.asnFetchedAt | string | ASNFetchedAt is the fetch time of the Team Cymru ASN facts above (asn/ allAsns/asName/prefix/registry/allocated/usageHint); see RDAPFetchedAt's doc above for the nil-vs-empty-string rule. Verbose-only. |
| network.asnOrg | string | AsnOrg/AsnDomain carry the offline iplocate org name/domain, populated from the MariaDB data resolver. nil/null when the resolver is off or the IP has no matching ASN block. These are distinct from the live Cymru facts above (asn/asName/prefix/registry/allocated/usageHint). |
| network.classification | netinfo.Result | Classification is the synthesized IP-type verdict (hosting/datacenter/ residential/mobile/…); always populated for a routable IP. |
| network.prefix | string | Prefix is the announced CIDR block covering the IP, from Team Cymru. |
| network.rdap | rdap.Info | RDAP is a pointer so the block is JSON null when there is no RDAP data at all; when the "network" group isn't included in the response it instead carries a trimmed {country, handle, name} subset (always present at baseline). asnOrg/asnDomain above are nullable for the same not-included reason. |
| network.rdapFetchedAt | string | RDAPFetchedAt/ASNFetchedAt are the fetch times of the RDAP registry record and the Team Cymru ASN facts above, RFC3339 (same format as the top-level cachedAt). Verbose-only: nil (omitted) on a non-verbose request. On a verbose request the pointer distinguishes two states that both stringify to "": nil means the source was never resolved (disabled, or genuinely no data), while a non-nil pointer to "" means the sibling data (rdap/asn) IS present but the store that served it can't report a fetch time (e.g. a MariaDB-only deployment). Without this, a consumer can't tell "no data" from "data, unknown age". |
| network.registry | string | Registry is the RIR that allocated Prefix (e.g. "ripencc", "arin"), from Team Cymru. |
| network.usageHint | string | UsageHint is Team Cymru's coarse allocation type (e.g. "DIRECT ALLOCATION", "ASSIGNED PA"), not a usage-type classification of the IP itself. |
Security
| Field | Type | Description |
|---|---|---|
| privacy.isAnonymous | boolean | IsAnonymous is derived as IsTor || IsVPN || IsProxy; inherits IsTor's degraded-false posture when the cloudtor matcher didn't run, and IsProxy's when the net-flags resolver didn't run/match. |
| privacy.isProxy | boolean | IsProxy is the IPFire libloc "is-anonymous-proxy" network flag: false when the underlying resolver is disabled, has no covering data for the address, or errored — same best-effort posture as IsVPN's rules signal. No other proxy data source is wired. |
| privacy.isRelay | boolean | IsRelay always reports false — no adopted data source (unused today). |
| privacy.isResidentialProxy | boolean | IsResidentialProxy always reports false — no free data source exists (known gap). |
| privacy.isTor | boolean | IsTor reuses the cloudtor Tor-exit match (flags.isTor); degraded to false (not an authoritative negative) when Tor detection isn't enabled server-side. |
| privacy.isVpn | boolean | IsVPN is the coarse VPN/hosting-posture signal: true only when network.classification.source is "rule" (X4BNet-derived hosting/ datacenter rows) AND type is hosting or datacenter — excludes the weaker cloud- and heuristic-sourced hosting matches. |
| threat.isMalicious | boolean | IsMalicious is the aggregate malicious-IP verdict across the loaded threat-intelligence feeds; only present when the security group was evaluated. |
| threat.reports | web.threatReportV2[] | Reports is the verbose evidence-API payload: one entry per persisted MISP event backing this IP's match. Omitted (nil) for a non-verbose caller, an unmatched/unevaluated IP, or when the evidence lookup errored. |
| threat.sources | string[] | Sources lists which loaded threat-intel feeds flagged the IP; always a non-nil array, empty ([]) for a clean IP. |
| threat.threatLevel | integer | ThreatLevel is the aggregated (most-severe/min) MISP threat_level_id across every matching entry that carries one: 1=High, 2=Medium, 3=Low, 4=Undefined. Nil (omitted) when no matching entry carries a level — e.g. a clean IP, or a match backed only by thin/index-only (non-MISP) sources. Gated identically to isMalicious/sources (the security group only) — independent of verbose, unlike Reports below. |
| recon.isScanner | boolean | IsScanner is the advisory active-recon/brute-force verdict from the Dataplane.org matcher; only present when recon was actually evaluated (this whole block is null otherwise). NOT threat.isMalicious. |
| recon.lastSeen | string | LastSeen is the most recent contributing signal's own observation timestamp (RFC3339, same format as cachedAt/geo.date) — lets a consumer judge how fresh the scanning activity is, since Dataplane's recon feeds age out via a configured decay window rather than staying "hot" forever between refreshes. Omitted when unknown (e.g. IsScanner false, or every contributing row carried an unparseable timestamp). |
| recon.sources | string[] | Sources lists which Dataplane.org signal(s) recently observed the IP (e.g. "sshpwauth", "telnetlogin"); always a non-nil array, empty ([]) for a clean IP — mirrors threat.sources' convention. |
| censorship.confirmedRate | number | ConfirmedRate is the fraction of OONI measurements classified as confirmed blocking over the rolling window, for the caller's ASN+country. |
| censorship.isCensored | boolean | IsCensored reports whether ConfirmedRate exceeds the configured blocking threshold for the caller's ASN+country (OONI-derived). |
| censorship.sampleSize | integer | SampleSize is the number of OONI measurements aggregated into ConfirmedRate. |
| fraud.level | string | Level buckets Score into low/medium/high against the service's configured thresholds. |
| fraud.score | integer | Score is the 0-100 risk-score rollup: a weighted sum of the fired Signals, saturating at 100. |
| fraud.signals | string[] | Signals names every weighted input that fired (e.g. "hosting", "vpn", "tor", "malicious", "censored", "ptrMissing", "bulkMail"), so Score is explainable; empty ([]) for a clean IP. |
Plan & Limits
| Field | Type | Description |
|---|---|---|
| plan.groups | map<web.groupPlanV2> | Groups is keyed by metered group name (geo/network/security); request-scoped like limited[]/gated[] — a group this request's metering pipeline never reached (not requested, gated, or concurrent-duplicate withheld) has no entry, since there is no live-peek primitive for an unrequested group's cardinality without mutating its SET. |
| plan.servedFromTopup | string[] | ServedFromTopup lists metered groups whose per-day budget was exhausted this request and were served from the persistent top-up bucket instead. |
| plan.tier | string | Tier duplicates the top-level Tier field (self-contained for a consumer reading only the plan section, same as GET /api/v1/usage's own "tier"). |
| plan.tokenName | string | TokenName is the API token's name, present when the caller authenticated via an opaque API token; omitted for a session-JWT-authed or anonymous caller. |
Endpoints
| Endpoint | Metered group | ?include= support | Description |
|---|---|---|---|
| GET /api/v1/ip | geo, network, security (opt-in) | yes — geo, network, security, all |
Baseline classification, always returned; opt into geo/network/security enrichment with ?include=. |
| GET /api | geo, network, security (opt-in) | yes — geo, network, security, all |
Version-agnostic alias — always mirrors the current latest baseline endpoint (today /api/v1/ip). |
Snippets
Ready-to-paste examples for calling the API from common languages.
Auth & Limits
Auth & tokens
Create and manage API tokens from your dashboard — sign in, no separate application process.
Send it as a request header:
Authorization: Bearer <token>
A basic lookup never needs a key at all. A token buys higher per-group limits than the anonymous tier — see the comparison below.
Get a tokenLimits & metering
Enrichment is metered per unique caller IP, tracked separately for each group — geo, network, security — against both a per-day and a per-minute budget. The baseline /api/v1/ip fields are always returned; only the enrichment groups are budgeted.
Going over budget never fails the request. The response still comes back HTTP 200 with status: "partial" and the withheld group listed in limited[] — that group's fields are simply null for this call.
The response's plan section shows your tier, the remaining per-day/per-minute budget for each requested group, and your top-up balance — so you can see exactly what's left without a separate call.
| Dimension | Anonymous | With API token |
|---|---|---|
| Baseline lookup | Always available, no signup | Always available |
| Enriched groups (geo/network/security) | Metered at the anonymous tier's per-day/per-minute budget | Metered at your tier's own (usually higher) per-day/per-minute budget |
| Exact limit figures | See /pricing.html or GET /api/v1/usage — always read live from your account | |
| Over budget | HTTP 200 with status: "partial" and the group listed in limited[] — never a hard error | |
Open-source projects can request a free Pro or Business tier for one year, on request.
View pricing & tiersFrequently asked questions
- Is the IP lookup API free to use?
- Yes. GET /api/v1/ip needs no signup or API key and returns baseline classification, network and DNS facts plus country-level geolocation for free. The geo, network and security enrichment groups are metered per unique IP per day; registering for a free token moves that metering to your account's own (usually higher) budget, and open-source projects can request a free Pro or Business tier for a year.
- What data does the IP lookup API return?
- Every response includes IP classification, ASN/network facts, DNS (PTR/hostnames) and country-level geolocation. Add ?include=geo,network,security to also get extended geo fields, the full RDAP registry record, and privacy/threat/fraud signals — see the full field reference at https://ip.xakki.pro/docs/.
- Does the API detect VPN, proxy or Tor use?
- Partly, and precisely: isVpn is inferred, not looked up against a VPN provider list — it's true only when the address's network classification is hosting/datacenter infrastructure (from curated hosting-network rules), the kind of network posture VPN services typically run on. isProxy comes from a separate anonymous-proxy network flag (IPFire libloc), also infrastructure-based, not a proxy-service database. isTor matches known Tor exit-node addresses. isAnonymous is simply isTor || isVpn || isProxy. Two related fields, isResidentialProxy and isRelay, are reserved in the schema but always return false today — no data source is wired for them, so don't treat them as working signals. The threat block separately reports known-malicious sources, and fraud rolls every signal into a single 0-100 risk score.
- How is the API authenticated and rate-limited?
- Create a token from your dashboard and send it as Authorization: Bearer <token>. Enrichment is metered by the number of distinct IP addresses you look up — per day and per minute, separately for each group — so repeat lookups of the same address within a window cost nothing. Going over budget never fails the request: it returns HTTP 200 with status: "partial" and the withheld group listed in limited[].
- Can I look up ASN and RDAP registry data for an IP?
- Yes. The network group returns the full RDAP registry record (org, contacts, remarks, status) plus Team Cymru ASN facts — announcing ASN, AS name, prefix, registry and allocation date.
Full reference
Browse the complete API surface in Swagger UI, or grab the raw OpenAPI spec.