IP Geolocation API for Developers

A free IP lookup API for integrators: one JSON call returns IP geolocation, RDAP data for the IP address, IP-to-ASN identity, reverse DNS, and IP classification (hosting, mobile, business) — no signup needed, no API key for baseline lookups.

This page covers the raw API and integration path — evaluating VPN/proxy/Tor detection and fraud scoring instead? See the fraud detection page, or for the business/procurement case see the business overview page.

An IP address lookup API for developers

Quickstart

Every call below is unauthenticated — no API key needed to start. Each asks for the geo, network and security groups on a sample IP.

curl
curl "https://ip.xakki.pro/api/v1/ip?include=geo,network,security&ip=9.9.9.9"
Go
package main

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
)

type geoBlock struct {
	CountryCode string `json:"countryCode"`
	City        string `json:"city"`
}

type ipResponse struct {
	Status string    `json:"status"`
	IP     string    `json:"ip"`
	Geo    *geoBlock `json:"geo"`
}

func main() {
	resp, err := http.Get("https://ip.xakki.pro/api/v1/ip?include=geo&ip=9.9.9.9")
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}

	var out ipResponse
	if err := json.Unmarshal(body, &out); err != nil {
		panic(err)
	}

	if out.Geo != nil {
		fmt.Printf("%s: %s, %s\n", out.IP, out.Geo.City, out.Geo.CountryCode)
	}
}
Python
import requests

resp = requests.get(
    "https://ip.xakki.pro/api/v1/ip",
    params={"include": "geo,network,security", "ip": "9.9.9.9"},
    timeout=5,
)
resp.raise_for_status()
data = resp.json()

print(data["status"], data["ip"])
if data.get("geo"):
    print(data["geo"]["countryCode"], data["geo"]["city"])
classification = (data.get("network") or {}).get("classification")
if classification:
    print(classification["type"])

Example response

A trimmed real response (abbreviated for readability — the full field reference lives in the API docs).

{
  "schemaVersion": 15,
  "status": "ok",
  "ip": "9.9.9.9",
  "version": 4,
  "cachedAt": "2026-07-28T12:00:06Z",
  "flags": {
    "isGlobal": true,
    "isBogon": false,
    "isCloud": false,
    "isTor": false,
    "isAnycast": true
  },
  "network": {
    "asn": "19281",
    "asName": "QUAD9-AS-1 - Quad9, US",
    "asnOrg": "Quad9",
    "asnDomain": "quad9.net",
    "prefix": "9.9.9.0/24",
    "registry": "arin",
    "classification": {
      "type": "gov",
      "provider": "QUAD9-AS-1 - Quad9",
      "confidence": "medium"
    },
    "rdap": {
      "handle": "NET-9-9-9-0-1",
      "name": "CLEAN-97",
      "org": "Quad9 Admin"
    }
  },
  "dns": {
    "ptr": "dns9.quad9.net",
    "hostnames": ["dns9.quad9.net"]
  },
  "geo": {
    "countryCode": "US",
    "region": "California",
    "city": "Berkeley",
    "timezone": "-07:00",
    "lat": 37.87159,
    "lon": -122.27275
  },
  "privacy": {
    "isProxy": false,
    "isVpn": false,
    "isTor": false,
    "isAnonymous": false
  },
  "threat": {
    "isMalicious": false,
    "sources": []
  },
  "fraud": {
    "score": 0,
    "level": "low",
    "signals": []
  },
  "tier": "anonymous"
}

Errors & rate limits

Baseline classification (flags, network summary, reverse DNS) is always attempted and returned as HTTP 200 — an unparseable ?ip= value is simply ignored, and the endpoint falls back to your own address rather than erroring.

The geo, network and security groups are metered per unique caller IP per day, with a per-minute ceiling too. Go over budget and the group is soft-withheld: it's named in the response's limited array, status drops to partial, and the usual RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset headers are set — the HTTP status still stays 200.

Repeat lookups of the same IP address within the same window are free: the meter only counts distinct addresses, not requests, so polling your own traffic never burns your budget.

A total enrichment failure (every attempted source errored) is the only case that returns a non-200 status: status becomes error and the HTTP code is 500.

There is no hard 429 from the API on this path. The only 429 comes from the nginx gateway's own per-IP abuse-rate ceiling — independent of your plan's limits and outside this JSON contract.

Current rate limits

Fetched live from the same catalog the pricing page uses — nothing below is hardcoded, so it never drifts out of date.

Loading current limits…

* Per-minute and per-day limits are counted per unique IP address looked up. An overall request-rate ceiling also applies at the gateway, independent of the per-minute/per-day limits above, returning HTTP 429 once exceeded.

Ready to integrate?

Frequently asked questions

Is this IP geolocation API free to use?
Yes — baseline classification and lookups need no signup or API key. A free registered tier adds its own metering budget across the geo, network and security groups; paid plans raise the per-minute/per-day ceilings further. See the pricing page for current limits.
How do I get the full RDAP record for an IP address?
Add network to ?include=: the response's network.rdap object carries the full RDAP record — handle, name, status, events and contacts — instead of the trimmed baseline subset.
What does the IP classification field mean (hosting, mobile, business, ...)?
network.classification.type is one of hosting, datacenter, isp, mobile, home, business or private from the runtime heuristic, plus rule-derived edu, gov or telco labels from curated network datasets — a synthesized verdict from AS name, RDAP, PTR and IP-block signals, with its own confidence and source (rule, cloud or heuristic) for transparency.
Does the reverse DNS lookup return more than one hostname?
The baseline dns block always includes ptr (the resolved reverse-DNS hostname) and hostnames (every hostname a multi-PTR lookup returned) for any address with a reverse record.
What happens when I exceed my plan's rate limit?
The metered group (geo, network or security) is soft-withheld — named in limited[], with status downgraded to partial — the response still returns HTTP 200 with everything else populated. There is no hard 429 from the API itself; only the gateway's own abuse-rate ceiling can return a plain 429, independent of your plan.