unmaskip
IP lookup with proxy, VPN, Tor and hosting detection, powered by checkip.me.
- Geolocation, ASN, ISP, connection type (Residential / Business / Wireless / Hosting)
- Detects proxy, VPN, Tor, hosting/datacenter, compromised hosts and scrapers
- Risk score and confidence from 0 to 100
- Operator intelligence: which VPN/proxy network, its anonymity level, services and policies
- Abuse history, sighting counts and device estimates
- No API key, zero dependencies, fully typed
- Built-in cache and automatic retry on rate limits
- Command-line tool included
Also available for JavaScript: npm install unmaskip (npm).
Install
pip install unmaskip
Requires Python 3.9+.
Quick start
import unmaskip
info = unmaskip.lookup("8.8.8.8")
print(info.country, info.city, info.isp) # United States Mountain View Google LLC
print(info.type) # Business
print(info.risk, info.confidence) # 0-100, 0-100
if info.is_vpn or info.is_proxy or info.is_tor:
print("Masked connection via", info.operator_name)
# One-liners
unmaskip.is_vpn("1.2.3.4")
unmaskip.is_proxy("1.2.3.4")
unmaskip.is_tor("1.2.3.4")
unmaskip.is_hosting("1.2.3.4")
unmaskip.risk("1.2.3.4")
# Your own IP
unmaskip.my_ip()
unmaskip.lookup()
Example: block anonymous sign-ups
import unmaskip
def is_suspicious(ip: str) -> bool:
info = unmaskip.lookup(ip)
return (
info.is_masked
or info.is_hosting
or (info.risk or 0) >= 80
or info.attack_history.get("registration_attempt", 0) > 0
)
The IPInfo object
Network and location
| Field | Type | Description |
|---|---|---|
ip |
str |
The IP address |
hostname |
str |
Reverse DNS |
asn, isp, organisation |
str |
Network owner |
type |
str |
Residential, Business, Wireless or Hosting |
range |
str |
Network range (CIDR) |
continent, continent_code |
str |
Continent |
country, country_code |
str |
Country |
region, region_code |
str |
Region / state |
city, postal_code |
str |
City |
latitude, longitude |
float |
Approximate coordinates |
timezone |
str |
IANA time zone |
currency |
str |
ISO 4217 code, e.g. USD |
currency_name, currency_symbol |
str |
e.g. Dollar, $ |
Detections
| Field | Type | Description |
|---|---|---|
proxy, vpn, tor, hosting |
bool |
Detections |
compromised, scraper, anonymous |
bool |
Other risk signals |
risk |
int |
Risk score, 0 (clean) to 100 (very risky) |
confidence |
int |
Confidence of the detection, 0 to 100 |
first_seen, last_seen |
datetime |
When the IP was first and last flagged |
times_seen |
int |
How many times it was flagged |
delisted |
bool |
Removed from detection lists |
delisted_at |
datetime |
When it was (or will be) delisted |
attack_history |
dict |
Abuse counts, e.g. {"login_attempt": 7} |
operator |
Operator |
VPN/proxy operator, when known (see below) |
device_estimate |
DeviceEstimate |
Estimated devices on the IP (address) and its subnet (subnet) |
last_updated |
datetime |
When the data was refreshed |
raw |
dict |
Full JSON for this IP, as returned by the API |
Every str, int, float, datetime and object field except ip is None
when the value is unknown. Datetimes are timezone-aware (UTC).
Helpers: is_proxy, is_vpn, is_tor, is_hosting, is_anonymous,
is_masked (proxy or VPN or Tor) and operator_name.
to_dict() returns a plain, JSON-serializable dict.
Operator
| Field | Type | Description |
|---|---|---|
name |
str |
e.g. TOR, NordVPN |
url |
str |
Operator website |
anonymity, popularity |
str |
low, medium or high |
services |
tuple |
e.g. ("vpn", "datacenter_proxies") |
protocols |
tuple |
e.g. ("WireGuard", "OpenVPN") |
policies |
dict |
e.g. {"logging": False, "free_access": True} |
additional_operators |
tuple |
Other networks seen on this IP |
info = unmaskip.lookup("185.220.101.1")
info.operator.name # 'TOR'
info.operator.anonymity # 'high'
info.attack_history # {'login_attempt': 7, 'registration_attempt': 3}
info.times_seen # 14609
Client options
from unmaskip import Client
client = Client(
timeout=10.0, # seconds per request
cache_ttl=3600, # cache results for 1 hour (0 disables the cache)
cache_size=1024, # max cached IPs
max_retries=2, # retries on HTTP 429
max_retry_wait=60, # never sleep longer than this; raise instead
)
info = client.lookup("1.1.1.1")
The module-level functions (unmaskip.lookup, unmaskip.is_vpn, ...) share one
default client, so their results are cached too.
Rate limits
The free API allows 120 lookups of other IPs per hour per client; looking
up your own IP is unlimited and cached IPs do not count. When the limit is hit,
unmaskip waits for Retry-After and retries. If the wait is too long, it raises
unmaskip.RateLimitError, whose retry_after attribute tells you how long to wait.
Errors
All errors inherit from unmaskip.UnmaskIPError:
| Exception | When |
|---|---|
InvalidIPError |
Not a valid IPv4/IPv6 address (raised before any request) |
PrivateIPError |
Private or reserved address such as 10.0.0.1 or 127.0.0.1 |
RateLimitError |
HTTP 429 and the retry budget is used up |
NetworkError |
DNS failure, timeout, connection refused, ... |
APIError |
Any other unexpected response |
InvalidIPError and PrivateIPError are also ValueErrors.
Command line
unmaskip # your own IP
unmaskip 8.8.8.8 1.1.1.1 # several IPs
unmaskip 8.8.8.8 --json # raw JSON
unmaskip 8.8.8.8 -f vpn # one field: true / false
unmaskip 8.8.8.8 -f country # United States
unmaskip 8.8.8.8 -f network.provider # any raw JSON path
unmaskip 1.2.3.4 --strict # exit code 3 if proxy / VPN / Tor / hosting
$ unmaskip 185.220.101.1
IP 185.220.101.1
Hostname berlin01.tor-exit.artikel10.org
ASN AS60729
ISP Stiftung Erneuerbare Freiheit
Organisation Artikel10 e.V
Type Business
Range 185.220.101.1/28
Location Berlin, State of Berlin, Germany
Timezone Europe/Berlin
Proxy yes
VPN no
Tor yes
Hosting no
Anonymous yes
Compromised yes
Scraper no
Risk 100/100
Confidence 100/100
Operator TOR (anonymity: high)
Seen 14609 times, last 2026-09-20
Attacks login attempt 7, registration attempt 3
Devices ~10 on IP, ~1520 on subnet
Exit codes: 0 success, 1 lookup error, 2 bad arguments, 3 flagged IP (with --strict).
python -m unmaskip works too.
Privacy
unmaskip sends the IP addresses you look up to api.checkip.me over HTTPS,
together with a User-Agent header (unmaskip/<version>). Nothing else is
collected or sent. Results are cached only in your process memory.
Links
- API docs: https://checkip.me/api
- Website: https://checkip.me
- JavaScript package: https://www.npmjs.com/package/unmaskip
License
MIT
Metadata
Release files for unmaskip 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| unmaskip-0.3.0.tar.gz | 18.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| unmaskip-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 34.9 kB
Release files / unmaskip-0.3.0.tar.gz
| Download URL | unmaskip-0.3.0.tar.gz |
|---|---|
| Size | 18.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
88e677f75fcb25f974b97f0851388b4ded1fdb2fb2266a062f3cdc57879acaef
|
|
BLAKE2b-256 checksum How to use checksums |
a4aaa03e06a153069437415dfbf5d9e2fc4f08e5703cea62085e2f21fa45e03d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|
Release files / unmaskip-0.3.0-py3-none-any.whl
| Download URL | unmaskip-0.3.0-py3-none-any.whl |
|---|---|
| Size | 16.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
44d5fd236b045af66ae82122a4ecc5cbc2c9916760aaaedacc5baec90bc004bd
|
|
BLAKE2b-256 checksum How to use checksums |
9cd7568196211b84536090794c54189853f062e9e310d96a7cac65f0f8b4475a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|