Skip to content

API reference

Plain HTTP and JSON. No client library is needed in any language.

Endpoints

EndpointPurpose
GET /v1/lookup/{ip}Look up one address. IPv4, IPv6, or a hostname.
GET /health200 when ready, 503 while databases are still loading.
GET /v1/attributionThe credit the data licence requires.
GET /docsInteractive OpenAPI documentation.
GET /Redirects to /docs.

Status codes

CodeMeaning
200Found. Fields may be empty strings if the address is not in the database.
400Not a valid IP address or a hostname that resolves.
422Valid (or resolved), but private/loopback/link-local — not something any geolocation database can answer.
503Databases not loaded yet.

Hostnames

Anything that doesn't parse as an IP is treated as a hostname: it is resolved through the host's own DNS resolver (preferring IPv4 when a name has both) and the resolved address is looked up — the response's ip field tells you which address that was.

That DNS query is the only network traffic a lookup can cause, it carries only the name, and literal-IP lookups never make it.

Caching

IP responses carry Cache-Control: public, max-age=86400, since results only change when the monthly database does. Hostname responses get max-age=300 — a name can point somewhere new whenever its DNS does.

All responses — errors included — are pretty-printed JSON. They are a few hundred bytes at most, so readability in a browser or a terminal costs nothing measurable.

Using it as an ip2location drop-in

Behind nginx:

nginx
location ~ ^/api/geo/(.+)$ {
    proxy_pass http://evo_locate:9100/v1/lookup/$1;
}

The response uses ip2location's field names (country_code, country_name, region_name, city_name, asn, as). Their paid-tier fields — isp, domain, usage_type — are omitted, exactly as they are on the free tier, so existing callers that fall back on them keep working unchanged.

Client examples

Python, standard library only:

python
import json
from urllib.request import urlopen

# also accepts a hostname, e.g. .../v1/lookup/google.com
with urlopen("http://localhost:9100/v1/lookup/8.8.8.8") as resp:
    geo = json.load(resp)

print(f"{geo['ip']}: {geo['city_name']}, {geo['country_name']} ({geo['as']})")
# 8.8.8.8: Mountain View, United States (Google LLC)

A 400/422/503 raises urllib.error.HTTPError; the JSON body's detail field says why.

Node.js (18+, built-in fetch, no dependencies):

js
const resp = await fetch("http://localhost:9100/v1/lookup/8.8.8.8");
if (!resp.ok) {
  const { detail } = await resp.json();
  throw new Error(`lookup failed (${resp.status}): ${detail}`);
}
const geo = await resp.json();

console.log(`${geo.ip}: ${geo.city_name}, ${geo.country_name} (${geo.as})`);
// 8.8.8.8: Mountain View, United States (Google LLC)

Documentation hub for Evomedia.net LLC products.