Prerequisites
- Python 3.8 or higher
- An ip-api.io API key — get one free
- No other dependencies — the client is pure standard library
Install the package
Add the official ip-api-io package to your project. It has
zero dependencies — nothing to pull in beyond the standard library.
Installing into a virtual environment keeps your project isolated.
python -m venv .venv && source .venv/bin/activate
pip install ip-api-io Initialize the client
Import IpApiClient and create one instance with your API key. Read the
key from an environment variable — never hardcode it. The API rejects keyless
requests with 401.
import os
from ipapi_io import IpApiClient
client = IpApiClient(api_key=os.environ["IP_API_IO_KEY"]) Reuse a single client across your application — it is safe to keep as a module-level singleton.
Look up an IP address
Call lookup(ip) for any IPv4 or IPv6 address, or lookup()
with no argument to resolve the caller's own IP. Every response is a plain
dict with a location object and a
suspicious_factors object.
info = client.lookup("8.8.8.8")
print(info["ip"]) # "8.8.8.8"
print(info["isp"]) # "Google LLC"
print(info["location"]["country"]) # "United States"
print(info["location"]["city"]) # "Mountain View"
print(info["location"]["latitude"],
info["location"]["longitude"]) # 37.4056 -122.0775
print(info["location"]["timezone"]) # "America/Los_Angeles"
# Resolve the caller's own IP (no argument):
me = client.lookup()
print(me["ip"], me["location"]["country"]) ipapi_io.types ships TypedDict definitions such as
IpInfo — annotate with info: IpInfo = client.lookup(...)
and your editor autocompletes info["location"]["city"].
Detect VPN, proxy, Tor & batch lookups
The suspicious_factors dict carries seven boolean flags on every
lookup — this is what sets ip-api.io apart from plain geolocation APIs. You get
security intelligence in the same call, at no extra cost.
info = client.lookup("185.220.101.45") # example Tor exit node
sf = info["suspicious_factors"]
if sf["is_vpn"] or sf["is_proxy"] or sf["is_tor_node"]:
print("Anonymized traffic — consider a CAPTCHA or block")
if sf["is_datacenter"]:
print("Cloud/datacenter IP — likely automated")
if sf["is_threat"]:
print("Active threat signal — block this request")
is_risky = sf["is_vpn"] or sf["is_proxy"] or sf["is_tor_node"] or sf["is_threat"]
Need to check many IPs at once? lookup_batch takes up to 100 addresses in
a single request (it raises ValueError if the list is empty or longer
than 100):
batch = client.lookup_batch(["8.8.8.8", "1.1.1.1", "9.9.9.9"])
print(batch["total_processed"], batch["successful_lookups"])
for ip, result in batch["results"].items():
print(ip, result["location"]["country"], result["suspicious_factors"]["is_vpn"]) client.risk_score(ip) for a combined
fraud risk score from
0 to 100.
Production-ready error handling
The client raises typed exceptions so you can react to each failure precisely. It
never retries on its own — on a rate limit, RateLimitError
tells you exactly when to try again.
from ipapi_io import (
IpApiClient,
IpApiError,
RateLimitError,
AuthenticationError,
InvalidRequestError,
ServerError,
)
client = IpApiClient(api_key=os.environ["IP_API_IO_KEY"])
try:
info = client.lookup("8.8.8.8")
# ... use info
except RateLimitError as e:
# HTTP 429 — quota exhausted
print(f"Rate limited. Remaining: {e.remaining}. Resets at {e.reset}")
except AuthenticationError:
print("Invalid or missing API key — get one free at https://ip-api.io")
except InvalidRequestError as e:
print("Bad request:", e)
except ServerError:
print("ip-api.io is having issues — try again later")
except IpApiError as e:
print(f"error {e.status_code}: {e}") RateLimitError.reset (a Unix timestamp) to schedule the next attempt
instead of hammering the API. Transport failures (DNS, timeout) surface as
urllib.error.URLError / socket.timeout, not an
IpApiError.
In a Flask app, run the lookup server-side and pass the real client IP from the
X-Forwarded-For header:
import os
from flask import Flask, request, jsonify
from ipapi_io import IpApiClient, IpApiError
app = Flask(__name__)
client = IpApiClient(api_key=os.environ["IP_API_IO_KEY"])
@app.get("/check")
def check():
xff = request.headers.get("X-Forwarded-For", "")
client_ip = xff.split(",")[0].strip() or request.remote_addr
try:
info = client.lookup(client_ip)
return jsonify(
ip=info["ip"],
country=info["location"]["country"],
is_vpn=info["suspicious_factors"]["is_vpn"],
is_threat=info["suspicious_factors"]["is_threat"],
)
except IpApiError:
# Treat ip-api.io as a non-critical dependency
return jsonify(ip=client_ip, country=None), 200