NewConnect Claude, Gemini, ChatGPT, and other AI agents to API Ninjas via our MCP server

How to Get IP Geolocation in Python

IP Lookup APIUpdated August 28, 2026
Create a free API key and it will be pre-filled into every code sample on this page. No credit card required.
Geolocating an IP address in Python takes one GET request to the IP Lookup API — country, region, city, coordinates, timezone, and ISP in a single response. This guide covers the lookup itself, then the pattern that matters in practice: geolocating every visitor in a web-server log without paying for the same IP twice.

Prerequisites

You need three things:

  • Python 3.8 or newer.
  • The requests library.
  • A free API Ninjas API key — issued instantly, no credit card. If you already have an account, it's on your profile page.
shell
pip install requests

Step 1: Look up your first IP

The endpoint is /v1/iplookup and takes one parameter: address, an IPv4 or IPv6 address.

ip.py
import requests response = requests.get( 'https://api.api-ninjas.com/v1/iplookup', params={'address': '24.48.0.1'}, headers={'X-Api-Key': 'YOUR_API_KEY'}, timeout=10, ) response.raise_for_status() info = response.json() print(f"{info['city']}, {info['country']}") # Montreal, Canada

The request on the wire and the full response:

api.api-ninjas.com
GET/v1/iplookup?address=24.48.0.1
200 OKapplication/json
{ "address": "24.48.0.1", "is_valid": true, "country": "Canada", "country_code": "CA", "region": "Quebec", "city": "Montreal", "zip": "H1S", "lat": 45.5808, "lon": -73.5825, "timezone": "America/Toronto", "isp": "Le Groupe Videotron Ltee" }

Step 2: Read the response

Everything arrives in one flat object: the location chain (country, region, city, zip), coordinates (lat, lon), the IANA timezone, and the isp name. Two reading tips: is_valid confirms the address parsed correctly, and city-level placement is an estimate — IP geolocation reliably answers “which country and region,” not “which street.”

Step 3: Harden the client

Move the key into an environment variable and share one Session across lookups — the connection reuse matters as soon as you process more than a handful of addresses:

ip_client.py
import os import requests API_URL = 'https://api.api-ninjas.com/v1/iplookup' session = requests.Session() session.headers['X-Api-Key'] = os.environ.get('API_NINJAS_KEY', 'YOUR_API_KEY') def lookup_ip(ip: str) -> dict: """Geolocate one IPv4 or IPv6 address.""" response = session.get(API_URL, params={'address': ip}, timeout=10) response.raise_for_status() return response.json() info = lookup_ip('24.48.0.1') print(f"{info['city']}, {info['region']}, {info['country']} ({info['isp']})")

Run export API_NINJAS_KEY=your_key once, and lookup_ip() becomes the building block for the batch job in the next step.

Step 4: Geolocate a whole log file

The realistic job isn't one IP — it's an access log with thousands of lines and heavy repetition. Two habits keep that cheap: deduplicate before looking anything up, and cache results so tomorrow's run only pays for new addresses:

geolocate_log.py
import re import sqlite3 LOG_LINE = re.compile(r'^(\S+)\s') # 1. Collect unique IPs from the access log ips = set() with open('access.log') as f: for line in f: match = LOG_LINE.match(line) if match: ips.add(match.group(1)) # 2. Cache lookups so re-runs never repeat a request db = sqlite3.connect('ip_cache.db') db.execute('CREATE TABLE IF NOT EXISTS ip_cache (ip TEXT PRIMARY KEY, country TEXT, city TEXT)') for ip in ips: cached = db.execute('SELECT country, city FROM ip_cache WHERE ip = ?', (ip,)).fetchone() if cached is None: info = lookup_ip(ip) cached = (info['country'], info['city']) db.execute('INSERT INTO ip_cache VALUES (?, ?, ?)', (ip, *cached)) db.commit() print(f'{ip:15} {cached[0]:20} {cached[1]}')

The SQLite cache is the difference between thousands of requests per run and a few dozen — most log traffic comes from IPs you've already seen.

Step 5: Handle errors

Malformed input is the common failure here — log files contain garbage. A 400 means the address didn't parse; handle it per-IP so one bad line doesn't kill the batch:

errors.py
from requests.exceptions import HTTPError, RequestException try: info = lookup_ip('not-an-ip') except HTTPError as error: status = error.response.status_code if status == 400: print('Invalid IP address format') elif status == 401: print('Missing or invalid API key') elif status == 429: print('Rate limit exceeded — slow down and retry') else: print(f'HTTP {status}: {error.response.text}') except RequestException as error: print(f'Network error: {error}')

Where to go from here

Once you have coordinates for a visitor, the rest of the location stack is one request away: the Timezone API for local time, the Weather API for conditions, and the Geocoding API for the reverse direction — place names to coordinates. The complete script is below.

ip_lookup.py

The complete script from this guide: session reuse, geolocation lookup, and a command-line IP list.

Frequently asked questions

Does the API support IPv6?

Yes. Pass either an IPv4 or IPv6 address in the address parameter — the response shape is the same.

How accurate is IP geolocation?

Country-level accuracy is 99%+ globally. City-level accuracy is typically 80%+ for IPv4 in well-mapped regions like North America and Europe — treat city as an estimate, not a street address.

How do I look up my own public IP?

Your own machine only knows its private address, so first fetch your public IP from a service like ipify, then pass that to the IP Lookup endpoint.

Does the response include ISP information?

Yes — the isp field names the provider, alongside city, region, country, coordinates, and timezone.

How should I handle large batches of lookups?

Deduplicate first and cache results — web logs repeat the same addresses constantly, so a SQLite cache typically cuts requests by 10x or more. Step 4 of this guide shows the pattern.