Python Tutorial

Python Geolocation API:
IP Lookup with 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

  • Look up geolocation for the current request IP
  • Look up any specific IP address
  • Extract country, city, latitude, longitude, and timezone
  • Detect VPN, proxy, Tor, datacenter, and active threats

Prerequisites

  • Python 3.7 or higher
  • The requests library (installed below)
  • An ip-api.io API key — get one free
1

Install requests

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
2

Quickstart: current user IP

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"
Note: In a web app, you won't call /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.
3

Look up a specific IP address

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']}")
4

Detect VPN, proxy, Tor, and threats

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
Tip: For a combined risk score with a block / review / allow recommendation, use the Risk Score API at /api/v1/risk-score/{ip}.
5

Production-ready helper

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()

API Response Reference

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.

GET /api/v1/ip/78.55.53.58?api_key=YOUR_KEY
{
  "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

Top-level fields

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

Frequently asked questions

Common questions about using the Python geolocation API.

How do I get geolocation from an IP address in Python?

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.

Can Python detect VPN or proxy IPs?

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.

Is IP geolocation accurate?

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.

Can I geolocate the current user without passing an IP address?

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.

Pricing

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!

Small

€10 /mo
100,000 geo ip requests
10,000 advanced email validation requests
Location data
Email validation
Risk score calculation
Currency data
Time zone data
Threat data
Unlimited support
HTTPS encryption

Medium

€20 /mo
300,000 geo ip requests
25,000 advanced email validation requests
Location data
Email validation
Risk score calculation
Currency data
Time zone data
Threat data
Unlimited support
HTTPS encryption
Note: Your API key will be sent to your email after the subscription is confirmed.

Need support?

Explore how IP-API.io can enhance your security, provide robust bot protection, and improve IP geolocation accuracy for your applications.

Contact Support

Need more queries?

Customize your experience with tailored plans that fit your IP security and geolocation needs.

Email Us