Skip to main content

Official Python client for the ipwhois.io IP Geolocation API. Simple, dependency-free, supports single and bulk IP lookups.

Project description

ipwhois-python

PyPI Version Python Versions License

Official, dependency-free Python client for the ipwhois.io IP Geolocation API.

  • ✅ Single and bulk IP lookups (IPv4 and IPv6)
  • ✅ Works with both the Free and Paid plans
  • ✅ HTTPS by default
  • ✅ Localisation, field selection, threat detection, rate info
  • ✅ Never raises — all errors returned as success: False dicts
  • ✅ No external dependencies — only the Python standard library
  • ✅ Python 3.8+

Installation

pip install ipwhois-python

Free vs Paid plan

The same IPWhois class is used for both plans. The only difference is whether you pass an API key:

  • Free plan — create the client without arguments. No API key, no signup required. Suitable for low-traffic and non-commercial use.
  • Paid plan — create the client with your API key from https://ipwhois.io. Higher limits, plus access to bulk lookups and threat-detection data.
from ipwhois import IPWhois

free = IPWhois()                 # Free plan — no API key
paid = IPWhois("YOUR_API_KEY")   # Paid plan — with API key

Everything else (lookup(), options, error handling) is identical.

Quick start — Free plan (no API key)

from ipwhois import IPWhois

ipwhois = IPWhois()  # no API key

info = ipwhois.lookup("8.8.8.8")

print(info["country"], info["flag"]["emoji"])
# → United States 🇺🇸

print(f"{info['city']}, {info['region']}")
# → Mountain View, California

Quick start — Paid plan (with API key)

Get an API key at https://ipwhois.io and pass it to the constructor:

from ipwhois import IPWhois

ipwhois = IPWhois("YOUR_API_KEY")  # with API key

info = ipwhois.lookup("8.8.8.8")

print(info["country"], info["flag"]["emoji"])
# → United States 🇺🇸

print(f"{info['city']}, {info['region']}")
# → Mountain View, California

ℹ️ Pass nothing to look up your own public IP: ipwhois.lookup() — works on both plans.

Lookup options

Every option below can be passed per call as a keyword argument, or set once on the client as a default.

Option Type Plans needed Description
lang str Free + Paid One of: en, ru, de, es, pt-BR, fr, zh-CN, ja
fields list Free + Paid Restrict the response to specific fields (e.g. ["country", "city"])
rate bool Basic and above Include the rate block (limit, remaining)
security bool Business and above Include the security block (proxy/vpn/tor/hosting)

Setting defaults once

Every option can be passed two ways: per call (as a keyword argument to lookup() / bulk_lookup()) or once as a default on the client. Per-call options always override the defaults, so it's safe to set sensible defaults and only override what differs for a specific call.

Defaults are set with fluent setters — set_language(), set_fields(), set_security(), set_rate(), set_timeout(), set_connect_timeout(), set_user_agent() — and can be chained:

from ipwhois import IPWhois

# Free plan
ipwhois = (
    IPWhois()
    .set_language("en")
    .set_fields(["success", "country", "city", "flag.emoji"])
    .set_timeout(8)
)
from ipwhois import IPWhois

# Paid plan
ipwhois = (
    IPWhois("YOUR_API_KEY")
    .set_language("en")
    .set_fields(["success", "country", "city", "flag.emoji"])
    .set_timeout(8)
)

Either client behaves the same way at call time — per-call options always win over the defaults:

ipwhois.lookup("8.8.8.8")              # uses lang=en, the field whitelist, and timeout=8
ipwhois.lookup("1.1.1.1", lang="de")   # overrides lang for this single call only

⚠️ When you restrict fields with set_fields() (or the per-call fields= keyword), the API only returns the fields you ask for. Always include "success" in the list if you rely on info["success"] for error checking — otherwise the field will be missing on responses.

ℹ️ set_security(True) requires Business+ and set_rate(True) requires Basic+. See the table above for what's available where.

HTTPS encryption

By default, all requests are sent over HTTPS. If you need to disable it (for example, in environments without an up-to-date CA bundle), pass ssl=False to the constructor:

from ipwhois import IPWhois

# Free plan
ipwhois = IPWhois(ssl=False)
from ipwhois import IPWhois

# Paid plan
ipwhois = IPWhois("YOUR_API_KEY", ssl=False)

ℹ️ HTTPS is strongly recommended for production traffic — your API key is sent in the query string and would otherwise travel in clear text.

Bulk lookup (Paid plan only)

The bulk endpoint sends up to 100 IPs in a single GET request. Each address counts as one credit. Available on the Business and Unlimited plans.

from ipwhois import IPWhois

ipwhois = IPWhois("YOUR_API_KEY")

results = ipwhois.bulk_lookup([
    "8.8.8.8",
    "1.1.1.1",
    "208.67.222.222",
    "2c0f:fb50:4003::",     # IPv6 is fine — mix freely
])

for row in results:
    if row.get("success") is False:
        # Per-IP errors (e.g. "Invalid IP address") are returned inline,
        # they do NOT raise — the rest of the batch is still usable.
        print(f"skip {row['ip']}: {row['message']}")
        continue
    print(f"{row['ip']}{row['country']}")

ℹ️ Bulk requires an API key. Calling bulk_lookup() without one will fail at the API level.

Error handling

The library never raises. Every failure — invalid IP, bad API key, rate limit, network outage, bad options — comes back inside the response dict with success set to False and a message. Just check info["success"] after every call:

info = ipwhois.lookup("8.8.8.8")

if not info["success"]:
    print(f"Lookup failed: {info['message']}")
    return

print(info["country"])

This means an outage of the ipwhois.io API (or of your machine's DNS, connection, etc.) will never surface as an unhandled exception in your application — you decide how to react.

Error response fields

Every error response contains success: False, a human-readable message, and an error_type so you can branch on the category of the failure. Some errors include extra fields you can branch on:

Field When it's present
success Always — false for error responses (true for successful responses)
message Always — human-readable description of what went wrong
error_type Always — one of 'api', 'network', or 'invalid_argument'
http_status On HTTP 4xx / 5xx responses
retry_after On HTTP 429 — free plan only (the paid endpoint does not send a Retry-After header)
import time

info = ipwhois.lookup("8.8.8.8")

if not info["success"]:
    if info.get("http_status") == 429:
        time.sleep(info.get("retry_after", 60))
        # ...retry

    if info.get("error_type") == "network":
        # DNS failure, connection refused, timeout, ...
        pass

    print(f"Error: {info['message']}")

Response shape

A successful response includes (depending on your plan and selected options):

{
    "ip": "8.8.4.4",
    "success": true,
    "type": "IPv4",
    "continent": "North America",
    "continent_code": "NA",
    "country": "United States",
    "country_code": "US",
    "region": "California",
    "region_code": "CA",
    "city": "Mountain View",
    "latitude": 37.3860517,
    "longitude": -122.0838511,
    "is_eu": false,
    "postal": "94039",
    "calling_code": "1",
    "capital": "Washington D.C.",
    "borders": "CA,MX",
    "flag": {
        "img": "https://cdn.ipwhois.io/flags/us.svg",
        "emoji": "🇺🇸",
        "emoji_unicode": "U+1F1FA U+1F1F8"
    },
    "connection": {
        "asn": 15169,
        "org": "Google LLC",
        "isp": "Google LLC",
        "domain": "google.com"
    },
    "timezone": {
        "id": "America/Los_Angeles",
        "abbr": "PDT",
        "is_dst": true,
        "offset": -25200,
        "utc": "-07:00",
        "current_time": "2026-05-08T14:31:48-07:00"
    },
    "currency": {
        "name": "US Dollar",
        "code": "USD",
        "symbol": "$",
        "plural": "US dollars",
        "exchange_rate": 1
    },
    "security": {
        "anonymous": false,
        "proxy": false,
        "vpn": false,
        "tor": false,
        "hosting": false
    },
    "rate": {
        "limit": 250000,
        "remaining": 50155
    }
}

For the full field reference, see the official documentation.

An error response looks like:

{
    "success": false,
    "message": "Rate limit exceeded",
    "error_type": "api",       // 'api' / 'network' / 'invalid_argument'
    "http_status": 429,         // present for HTTP 4xx / 5xx
    "retry_after": 60       // additionally present on HTTP 429 — free plan only
}

Requirements

  • Python 3.8 or newer
  • No third-party dependencies — only the standard library (urllib, json)

Contributing

Issues and pull requests are welcome on GitHub.

License

MIT © ipwhois.io

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ipwhois_python-1.2.1.tar.gz (18.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ipwhois_python-1.2.1-py3-none-any.whl (13.1 kB view details)

Uploaded Python 3

File details

Details for the file ipwhois_python-1.2.1.tar.gz.

File metadata

  • Download URL: ipwhois_python-1.2.1.tar.gz
  • Upload date:
  • Size: 18.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.6

File hashes

Hashes for ipwhois_python-1.2.1.tar.gz
Algorithm Hash digest
SHA256 adaefdaecd4106b3497c6a3fd245f64b1b58a8da6da615ea9ed5afe358e87494
MD5 8009afae6933590d2b4abc7d70b50693
BLAKE2b-256 53669121a4bb5a0a4f3acd2f5f68d1760ed40442adc2c720254fc4b483af9594

See more details on using hashes here.

File details

Details for the file ipwhois_python-1.2.1-py3-none-any.whl.

File metadata

  • Download URL: ipwhois_python-1.2.1-py3-none-any.whl
  • Upload date:
  • Size: 13.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.6

File hashes

Hashes for ipwhois_python-1.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 edbfad45c8872501e2c0f8358db01326031bfe2a737f747b0f5198677121c5dc
MD5 e380c130f149b0427bc23ac654e86ea6
BLAKE2b-256 b91a686215fed2540396d0758df6a459456b1e0e2a9e2b324da663b102c6ea02

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page