requests
This tutorial shows how to use Python to look up IP geolocation data — country, city,
latitude, longitude, and timezone — using the requests library and the
ip-api.io API. Unlike browser GPS, IP-based geolocation works server-side without
asking for user permission, and returns VPN, proxy, Tor, and threat signals in the
same response.
What you'll build
requests library (installed below)
Install the requests library if you don't have it already. Add it to your
requirements.txt for reproducible installs.
pip install requests
# requirements.txt
requests>=2.28
Call GET /api/v1/ip/ with no IP in the path. The API resolves the IP address
from the incoming request and returns its geolocation data.
Store your API key in an environment variable — never hardcode it.
import os
import requests
API_KEY = os.environ["IP_API_IO_KEY"]
resp = requests.get(
"https://ip-api.io/api/v1/ip/",
params={"api_key": API_KEY},
timeout=5,
)
resp.raise_for_status()
data = resp.json()
print(data["ip"]) # "78.55.53.58"
print(data["location"]["country"]) # "Germany"
print(data["location"]["city"]) # "Berlin"
print(data["location"]["latitude"]) # 52.5694
print(data["location"]["longitude"]) # 13.3753
print(data["location"]["timezone"]) # "Europe/Berlin"
print(data["location"]["local_time"]) # "2024-05-20T22:16:52+02:00"
/api/v1/ip/ from the
browser — you'll call it server-side and pass the client's IP from the
X-Forwarded-For header. See step 5 for a Flask/Django example.
To look up any IPv4 or IPv6 address, append it to the path:
GET /api/v1/ip/{ip}.
import os
import requests
API_KEY = os.environ["IP_API_IO_KEY"]
def lookup_ip(ip: str) -> dict:
resp = requests.get(
f"https://ip-api.io/api/v1/ip/{ip}",
params={"api_key": API_KEY},
timeout=5,
)
resp.raise_for_status()
return resp.json()
data = lookup_ip("8.8.8.8")
loc = data["location"]
print(f"{loc['city']}, {loc['country']}") # "Mountain View, United States"
print(f"Timezone: {loc['timezone']}") # "America/Los_Angeles"
print(f"Coords: {loc['latitude']}, {loc['longitude']}")
Every response includes a suspicious_factors object with seven boolean flags.
This is what sets ip-api.io apart from basic geolocation APIs — you get security
intelligence in the same call, at no extra cost per lookup.
data = lookup_ip("185.220.101.45") # example Tor exit node
sf = data["suspicious_factors"]
if sf["is_vpn"] or sf["is_proxy"] or sf["is_tor_node"]:
print("Anonymous or masked traffic — consider blocking or CAPTCHA")
if sf["is_datacenter"]:
print("Cloud/datacenter IP — likely automated or bot traffic")
if sf["is_threat"]:
print("Active threat signal — block this request")
if sf["is_spam"]:
print("Associated with spam or phishing activity")
# Combine signals for a risk decision
is_risky = any([
sf["is_vpn"],
sf["is_proxy"],
sf["is_tor_node"],
sf["is_threat"],
])
print(f"Risk flag: {is_risky}") # True
block /
review / allow recommendation, use the
Risk Score API at
/api/v1/risk-score/{ip}.
A complete helper function with error handling, plus a utility to extract the real
client IP from X-Forwarded-For headers in Flask, Django, or any
WSGI/ASGI framework.
import os
import requests
API_KEY = os.environ["IP_API_IO_KEY"]
_BASE = "https://ip-api.io/api/v1/ip"
def get_ip_intel(ip: str | None = None) -> dict:
"""Fetch geolocation and security signals for an IP address.
Pass ip=None to resolve the caller's IP automatically.
Raises RuntimeError on timeout or non-2xx response.
"""
url = f"{_BASE}/{ip}" if ip else f"{_BASE}/"
try:
r = requests.get(url, params={"api_key": API_KEY}, timeout=5)
r.raise_for_status()
return r.json()
except requests.Timeout:
raise RuntimeError("ip-api.io request timed out")
except requests.HTTPError as exc:
raise RuntimeError(f"ip-api.io HTTP {exc.response.status_code}") from exc
except requests.RequestException as exc:
raise RuntimeError("Could not connect to ip-api.io") from exc
def get_client_ip(request) -> str:
"""Extract the real client IP from a Flask or Django request object.
Handles reverse proxies that set X-Forwarded-For.
"""
xff = request.headers.get("X-Forwarded-For")
return xff.split(",")[0].strip() if xff else request.remote_addr
Using the helper in a Flask route:
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/check")
def check_ip():
client_ip = get_client_ip(request)
data = get_ip_intel(client_ip)
sf = data["suspicious_factors"]
return jsonify({
"ip": data["ip"],
"country": data["location"]["country"],
"is_vpn": sf["is_vpn"],
"is_proxy": sf["is_proxy"],
"is_tor": sf["is_tor_node"],
"is_threat": sf["is_threat"],
})
Prefer async Python? Use httpx as a drop-in replacement for
requests:
import httpx
import os
API_KEY = os.environ["IP_API_IO_KEY"]
async def get_ip_intel_async(ip: str | None = None) -> dict:
url = f"https://ip-api.io/api/v1/ip/{ip}" if ip else "https://ip-api.io/api/v1/ip/"
async with httpx.AsyncClient(timeout=5) as client:
r = await client.get(url, params={"api_key": API_KEY})
r.raise_for_status()
return r.json()
Every IP lookup returns a JSON object with three top-level keys.
All location fields are nullable — they may be null for private ranges
or unrecognized addresses.
{
"ip": "78.55.53.58",
"suspicious_factors": {
"is_proxy": false,
"is_tor_node": false,
"is_spam": false,
"is_crawler": false,
"is_datacenter": false,
"is_vpn": false,
"is_threat": false
},
"location": {
"country": "Germany",
"country_code": "DE",
"city": "Berlin",
"latitude": 52.5694,
"longitude": 13.3753,
"zip": "13409",
"timezone": "Europe/Berlin",
"local_time": "2024-05-20T22:16:52+02:00",
"local_time_unix": 1716236212,
"is_daylight_savings": true
}
}
suspicious_factors — security signals (all boolean)
| Field | Description |
|---|---|
is_vpn |
VPN service, corporate gateway, or self-hosted VPN detected |
is_proxy |
HTTP, HTTPS, or SOCKS proxy (~99.5% accuracy) |
is_tor_node |
Tor exit node, relay, or bridge (updated in real time from Tor consensus) |
is_datacenter |
Cloud provider (AWS, GCP, Azure), VPS, or hosting facility |
is_threat |
Active security threat — malware C&C, botnet, or DDoS source |
is_spam |
Associated with spam, phishing, or malware email campaigns |
is_crawler |
Known web crawler, scraper, or bot (search engines, price monitors) |
location — geographic and timezone data
| Field | Type | Description |
|---|---|---|
country |
String | Full country name in English (ISO 3166-1, e.g. "Germany") |
country_code |
String | ISO 3166-1 alpha-2 code (e.g. "DE", "US", "GB") |
city |
String | City or municipality name (85–95% accuracy) |
latitude |
Float | Decimal degrees, WGS84 — ~50 km median accuracy radius |
longitude |
Float | Decimal degrees, WGS84 — ~50 km median accuracy radius |
zip |
String | Postal or ZIP code in country-specific format |
timezone |
String | IANA timezone identifier (e.g. "America/Los_Angeles") |
local_time |
String | Current local time in ISO 8601 with UTC offset |
local_time_unix |
Integer | Unix timestamp (seconds since epoch) in local timezone |
is_daylight_savings |
Boolean | True if the location is currently observing DST; null if not applicable |
| Field | Type | Description |
|---|---|---|
ip |
String | Analyzed IP address in normalized format (IPv4 dotted decimal or compressed IPv6) |
suspicious_factors |
Object | Seven boolean security signals — always present, never null |
location |
Object | Geographic and timezone data — fields may be null for private/unrecognized IPs |
Common questions about using the Python geolocation API.
Use the requests library to call the ip-api.io API:
import requests
data = requests.get(
"https://ip-api.io/api/v1/ip/8.8.8.8",
params={"api_key": YOUR_KEY},
timeout=5,
).json()
print(data["location"]["country"]) # "United States"
print(data["location"]["city"]) # "Mountain View"
The response includes country, city, latitude, longitude, timezone, and local time
inside the location object. IPv4 and IPv6 are both supported.
Yes. Every ip-api.io response includes a suspicious_factors object with
boolean flags you can check directly in Python:
sf = data["suspicious_factors"]
if sf["is_vpn"]:
print("VPN detected")
if sf["is_proxy"]:
print("Proxy detected")
if sf["is_tor_node"]:
print("Tor exit node")
The flags cover VPN, proxy, Tor exit nodes, datacenter IPs, spam sources, crawlers, and active threats — all returned in a single API call.
Country-level accuracy is 99.8%. City-level accuracy is 85–95% depending on region. Latitude and longitude coordinates have a median accuracy radius of approximately 50 km using the WGS84 standard.
IP geolocation is not GPS-level precision and should not be used as a substitute for device location. It is best suited for regional routing, fraud signals, timezone detection, and content localization — not street-level targeting.
Yes. Call GET /api/v1/ip/ with no IP in the path. The API automatically
resolves the IP address from the incoming HTTP request:
data = requests.get(
"https://ip-api.io/api/v1/ip/",
params={"api_key": API_KEY},
timeout=5,
).json()
In a web application, call this server-side and pass the client IP from the
X-Forwarded-For header to the /api/v1/ip/{ip} endpoint
for accurate results behind a load balancer or reverse proxy.
Start using IP-API.io to make your website safer and more user-friendly. Keep out unwanted bots, show visitors content that's relevant to where they are, and spot risky IP addresses quickly. It's perfect for making online shopping more personal and keeping your site secure. Get started today with one of the plans!
Explore how IP-API.io can enhance your security, provide robust bot protection, and improve IP geolocation accuracy for your applications.
Contact SupportCustomize your experience with tailored plans that fit your IP security and geolocation needs.
Email Us