Guide

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.

Python

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:

Python
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:

Python
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):

Python
#!/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)))
Output
$ 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,,,,

Start with a free API key

Free tier: 2,000 lookups/day, no card. Paid plans from $2.50/month. Full field reference here, or see the data-enrichment use case here.