How to Enrich IP Addresses for Analytics with an IP Geolocation API
Turn bare IPs from your logs into geography + network context — one flat row per IP — ready for analytics and BI.
You can turn the bare IPs in your logs into analytics-ready rows — geography, ASN, organisation and network type — with a single lookup each. The response nests fields under geoLocation and connection; this guide flattens them into one clean record per IP, handling the nulls you'll hit on infrastructure addresses.
The examples here are Python, standard library only — but the same logic maps directly onto our official SDKs for TypeScript, Go, Ruby, Java and 7 more. Field paths and the worked outputs below are all verified against the live API.
1Get a key and call the API securely
Create a free account here — 2,000 lookups/day, no card. Send the key as an Authorization: Bearer header from an environment variable, keeping it out of URLs and logs:
import os, urllib.request, json
API_KEY=*** # export it; never hardcode
def lookup(ip):
req = urllib.request.Request(
f"https://api.ipwho.org/ip/{ip}",
headers={"Authorization": f"Bearer {API_KEY}", "User-Agent": "MyApp/1.0"},
)
with urllib.request.urlopen(req, timeout=10) as resp:
return json.loads(resp.read())["data"]2Flatten the nested response into one row:
Pull geoLocation.countryCode / city and connection.asn_number / asn_org / connection_type into a flat dict. Expect null on infrastructure and some mobile IPs — keep the column, leave it empty:
def enrich(data):
geo = data.get("geoLocation") or {}
conn = data.get("connection") or {}
return {
"ip": data.get("ip"),
"country": geo.get("countryCode"),
"city": geo.get("city"), # can be null on infra IPs
"asn": conn.get("asn_number"),
"org": conn.get("asn_org"),
"type": conn.get("connection_type"),
}
print(enrich(lookup("91.0.0.1")))3Enrich a whole list to CSV — the full script:
Reads IPs from stdin and writes a CSV straight into your analytics pipeline. For large lists, prefer the bulk endpoint to resolve up to 100 per call. Tested against the live API (2026-10-26):
#!/usr/bin/env python3
"""Enrich a list of IPs into analytics rows (IPWho IP geolocation API).
Reads IPs from stdin (one per line), writes CSV to stdout."""
import os, sys, csv, urllib.request, json
API_KEY=*** lookup(ip):
req = urllib.request.Request(
f"https://api.ipwho.org/ip/{ip}",
headers={"Authorization": f"Bearer {API_KEY}", "User-Agent": "IPWho-Enrich/1.0"},
)
with urllib.request.urlopen(req, timeout=10) as resp:
return json.loads(resp.read())["data"]
def enrich(data):
geo = data.get("geoLocation") or {}
conn = data.get("connection") or {}
return {
"ip": data.get("ip"),
"country": geo.get("countryCode"),
"city": geo.get("city"),
"asn": conn.get("asn_number"),
"org": conn.get("asn_org"),
"type": conn.get("connection_type"),
}
if __name__ == "__main__":
ips = [l.strip() for l in sys.stdin if l.strip()] or ["91.0.0.1", "52.95.110.1", "133.0.0.1"]
w = csv.DictWriter(sys.stdout, fieldnames=["ip","country","city","asn","org","type"])
w.writeheader()
for ip in ips:
w.writerow(enrich(lookup(ip)))$ printf "91.0.0.1
52.95.110.1
133.0.0.1
" | python3 enrich.py
ip,country,city,asn,org,type
91.0.0.1,DE,Düsseldorf,3320,Deutsche Telekom AG,Cable/DSL
52.95.110.1,US,,16509,"Amazon.com, Inc.",Corporate
133.0.0.1,JP,,,,