Python client for the ipfastcheck.com IP reputation API — detect proxies, VPNs, Tor exit nodes and datacenter ranges, plus geolocation and ASN data.
- No API key. No signup, no token to rotate, no secret to inject into your container.
- Zero dependencies. Standard library only, Python 3.8+.
- Caching built in. In-memory or SQLite, with TTLs chosen per result class.
- Fails open by design. A third-party outage must never take your signup flow down.
- IPv4 and IPv6.
pip install ipfastcheckfrom ipfastcheck import Client
client = Client()
result = client.check("185.220.101.1")
print(result.country) # Germany
print(result.asn) # AS60729
print(result.risk) # 100
print(result.is_tor) # True
print(result.is_datacenter) # TrueMost callers don't want raw flags, they want an action. decide() applies a
configurable policy and returns one of ALLOW / REVIEW / DENY:
from ipfastcheck import Client, Action
client = Client()
decision = client.decide(request.remote_addr)
if decision.action is Action.DENY:
return reject()
elif decision.action is Action.REVIEW:
return require_email_verification() # step up, don't slam the door
else:
return proceed()
print(decision.reasons) # ['risk_100_ge_90', 'proxy', 'datacenter_or_hosting']REVIEW exists because blocking is usually the wrong response. Corporate VPNs,
shared CGNAT and mobile carrier NAT all produce "suspicious" addresses for
entirely legitimate users. Step-up verification costs an abuser real effort
while costing a real user a single click.
from ipfastcheck import Client, Policy, Action
policy = Policy(
deny_risk=90, # risk >= 90 -> DENY
review_risk=50, # risk >= 50 -> REVIEW
deny_tor=True, # Tor exits are a hard no for signup
review_datacenter=True, # consumers don't browse from AS16509
review_when_degraded=True, # see "Trusting the answer" below
on_error=Action.ALLOW, # fail open
)
client = Client(policy=policy)Two things about this API are worth knowing before you gate anything on it.
Don't branch on flags.vpn alone. It under-reports. A live example — an
address on AS9009 (M247), well-known VPN infrastructure, returns:
{"flags": {"vpn": false, "proxy": true, "hosting": true}, "risk": 82}A naive if result.is_vpn: block lets that straight through. Use the composite
signal instead — risk, net_type and is_hosting together — which is exactly
what Policy does. is_vpn is still exposed, with a warning in its docstring.
Watch for degraded responses. When the upstream reputation backend doesn't
answer, the API falls back to a geolocation-only estimate. The response still
reports ok: true with a plausible risk score, and the only tell is
sources.risk == "geo-only" — where vpn is always false and fraud_score
is always null.
This client surfaces that as Result.degraded, caches such answers for two
minutes instead of hours, and by default escalates an otherwise-clean degraded
result to REVIEW rather than ALLOW:
result = client.check("45.83.91.1")
if result.degraded:
... # "we didn't get an answer", not "it's clean"The API rate-limits fresh lookups but serves cached ones freely, and a cold lookup costs roughly 450–600 ms. Any inline use needs a cache — so one is on by default.
from ipfastcheck import Client, SQLiteCache, MemoryCache
# Default: bounded in-process LRU, 10k entries.
client = Client()
# Survives restarts, safe across processes (WAL mode).
client = Client(cache=SQLiteCache("/var/cache/ipfastcheck.sqlite3"))
client = Client(cache=MemoryCache(maxsize=50_000))TTLs are picked per result, because volatility differs by two orders of magnitude between classes:
| Result | TTL |
|---|---|
Degraded (geo-only) |
2 minutes |
| Tor exit node | 15 minutes |
| Proxy / VPN / datacenter / risk ≥ 50 | 1 hour |
| Clean residential | 24 hours |
check_self() needs no argument and no key, which makes it a complete
health check for "is my egress address flagged?" — worth knowing before your
transactional email starts silently vanishing on a freshly provisioned VPS.
result = client.check_self()
if result.risk > 75:
alert(f"Our egress IP {result.ip} is flagged: risk={result.risk}")# The same idea without Python at all:
curl -s https://ipfastcheck.com/checkThere is no bulk endpoint, so check_many() is a polite serial loop. It
de-duplicates first, which matters a lot on access logs — the same address
recurs constantly, and cache hits are free while fresh lookups are not.
with open("access.log") as fh:
ips = (line.split()[0] for line in fh)
for ip, result in client.check_many(ips, delay=0.2):
if result.ok and result.risk >= 75:
print(f"{ip}\t{result.risk}\t{result.asn}\t{result.country_code}")| Method | Returns | Notes |
|---|---|---|
check(ip, use_cache=True) |
Result |
Single lookup. Cached. |
check_self() |
Result |
This host's egress address. Never cached. |
my_ip() |
str | None |
Public IP as plain text. |
country(ip) |
str | None |
Two-letter country code. |
decide(ip, policy=None) |
Decision |
Lookup plus policy. |
check_many(ips, delay=0.0) |
iterator of (ip, Result) |
De-duplicated. |
Network failures never raise — check Result.ok. Only malformed input raises
(InvalidAddress), since that's a caller bug rather than a runtime condition.
ip · country · country_code · city · asn · isp · net_type ·
risk · fraud_score · flags · is_tor · is_proxy · is_vpn ·
is_hosting · is_datacenter · is_residential · is_mobile · degraded ·
age_seconds · from_cache · ok · error · raw
Comparable free tiers, as of August 2026:
| Service | API key | HTTPS on free tier | VPN/proxy flags free | Commercial use |
|---|---|---|---|---|
| ipfastcheck | not required | yes | yes | yes |
| ip-api.com | not required | no | partial | non-commercial only |
| AbuseIPDB | required | yes | no (abuse score only) | yes |
| ipinfo.io | required | yes | paid add-on | yes |
| MaxMind GeoLite2 | account + license key | local database | no | yes |
Two practical consequences: an HTTP-only endpoint can't be called from Cloudflare Workers or any TLS-only environment, and a keyless API needs no secret binding, no key rotation and no credential in your container image.
git clone https://github.com/ipfastcheck/ipfastcheck-python
cd ipfastcheck-python
python -m unittest discover -s tests -v # 37 tests, no network access- ipfastcheck-js - TypeScript client with the same policy model, for Node, Cloudflare Workers and the browser
- ansible-role-fail2ban-ipfastcheck - reputation-tiered fail2ban ban durations
- Free IP reputation API documentation
MIT — see LICENSE.