Metadata-Version: 2.5
Name: unmaskip
Version: 0.3.0
Summary: IP lookup with proxy, VPN, Tor and hosting detection, powered by checkip.me
Project-URL: Homepage, https://checkip.me
Project-URL: Documentation, https://checkip.me/api
Author: checkip.me
License-Expression: MIT
License-File: LICENSE
Keywords: checkip,datacenter,fraud,geoip,hosting,ip,ip-intelligence,ip-lookup,proxy-detection,risk-score,tor,vpn-detection
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Internet
Classifier: Topic :: Security
Classifier: Topic :: System :: Networking
Classifier: Typing :: Typed
Requires-Python: >=3.9
Provides-Extra: test
Requires-Dist: pytest>=7; extra == 'test'
Description-Content-Type: text/markdown

# unmaskip

**IP lookup with proxy, VPN, Tor and hosting detection, powered by [checkip.me](https://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](https://www.npmjs.com/package/unmaskip)).

## Install

```bash
pip install unmaskip
```

Requires Python 3.9+.

## Quick start

```python
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

```python
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 |

```python
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

```python
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 `ValueError`s.

## Command line

```bash
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
```

```text
$ 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
