PythonGeolocationTutorial
Python IP Geolocation with requests — Country Detection and Currency Localisation
ApogeoAPI5 min read
Basic IP lookup with requests
import requests
API_KEY = "your_api_key"
def get_geo(ip: str = "me") -> dict:
"""Geolocate an IP address (or 'me' for the caller's IP)."""
url = f"https://api.apogeoapi.com/v1/ip/{ip}"
resp = requests.get(url, headers={"X-API-Key": API_KEY}, timeout=3)
resp.raise_for_status()
return resp.json()
# Get your own IP's geo data
geo = get_geo()
print(geo["country_code"]) # "DE"
print(geo["city"]) # "Berlin"
print(geo["timezone"]) # "Europe/Berlin"
print(geo["currency"]) # "EUR"
Subnet caching — stretch 1,000 free requests to ~250,000
Group IPs by their first three octets (the /24 subnet). An entire office or apartment building shares one subnet, so ~250 residents share one API call:
from functools import lru_cache
import time, requests
API_KEY = "your_api_key"
_cache: dict[str, tuple[dict, float]] = {}
TTL = 4 * 3600 # 4 hours
def _subnet(ip: str) -> str:
"""Return /24 prefix, e.g. '1.2.3.4' → '1.2.3'."""
parts = ip.split(".")
return ".".join(parts[:3]) if len(parts) == 4 else ip
def get_geo_cached(ip: str) -> dict:
key = _subnet(ip)
cached = _cache.get(key)
if cached and time.time() - cached[1] < TTL:
return cached[0]
url = f"https://api.apogeoapi.com/v1/ip/{ip}"
resp = requests.get(url, headers={"X-API-Key": API_KEY}, timeout=3)
resp.raise_for_status()
data = resp.json()
_cache[key] = (data, time.time())
return data
Async version with httpx
import httpx
async def get_geo_async(ip: str = "me") -> dict:
async with httpx.AsyncClient() as client:
resp = await client.get(
f"https://api.apogeoapi.com/v1/ip/{ip}",
headers={"X-API-Key": API_KEY},
timeout=3,
)
resp.raise_for_status()
return resp.json()
# FastAPI example
from fastapi import FastAPI, Request
app = FastAPI()
@app.get("/price")
async def localized_price(request: Request):
client_ip = request.client.host
geo = await get_geo_async(client_ip)
currency = geo.get("currency", "USD")
usd_price = 29.00
# Fetch exchange rate separately
rate_resp = await httpx.AsyncClient().get(
f"https://api.apogeoapi.com/v1/exchange-rates/USD",
headers={"X-API-Key": API_KEY},
)
rates = rate_resp.json().get("rates", {})
local_price = round(usd_price * rates.get(currency, 1.0), 2)
return {"currency": currency, "price": local_price}
Combining IP geo with exchange rates
import requests
API_KEY = "your_api_key"
def get_localized_price(usd_price: float, visitor_ip: str = "me") -> dict:
geo = get_geo_cached(visitor_ip)
currency = geo.get("currency", "USD")
if currency == "USD":
return {"currency": "USD", "price": usd_price, "country": geo.get("country_code")}
# Get exchange rates (cache this separately in production)
resp = requests.get(
"https://api.apogeoapi.com/v1/exchange-rates/USD",
headers={"X-API-Key": API_KEY},
timeout=3,
)
rates = resp.json().get("rates", {})
rate = rates.get(currency, 1.0)
return {
"currency": currency,
"price": round(usd_price * rate, 2),
"country": geo.get("country_code"),
"country_name": geo.get("country_name"),
}
# Usage:
print(get_localized_price(29.00, "8.8.8.8"))
# {'currency': 'USD', 'price': 29.0, 'country': 'US', 'country_name': 'United States'}
Error handling and fallback
DEFAULT_GEO = {"country_code": "US", "country_name": "United States", "currency": "USD"}
def safe_get_geo(ip: str) -> dict:
try:
return get_geo_cached(ip)
except (requests.Timeout, requests.HTTPError, ValueError):
return DEFAULT_GEO
Environment variable setup
# .env
# APOGEOAPI_KEY=your_api_key
import os
from dotenv import load_dotenv
load_dotenv()
API_KEY = os.getenv("APOGEOAPI_KEY", "")
Try ApogeoAPI free
1,000 requests/month forever. 14-day full-access trial. No credit card.
Get your free API key