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: Falsedicts - β No external dependencies β only the Python standard library
- β Python 3.8+
pip install ipwhois-pythonThe 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 keyEverything else (lookup(), options, error handling) is identical.
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, CaliforniaGet 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.
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) |
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 withset_fields()(or the per-callfields=keyword), the API only returns the fields you ask for. Always include"success"in the list if you rely oninfo["success"]for error checking β otherwise the field will be missing on responses.
βΉοΈ
set_security(True)requires Business+ andset_rate(True)requires Basic+. See the table above for what's available where.
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.
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.
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.
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']}")A successful response includes (depending on your plan and selected options):
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
}- Python 3.8 or newer
- No third-party dependencies β only the standard library (
urllib,json)
Issues and pull requests are welcome on GitHub.
MIT Β© ipwhois.io
{ "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 } }