Skip to content

Repository files navigation

NetRiskScan Python SDK

PyPI version Python versions CI

Official Python SDK for the NetRiskScan IP Risk & Reputation API -- a Python IP risk API for IP reputation, IP lookup, proxy/VPN/Tor detection, datacenter and search-crawler identification, and network intelligence.

pip install netriskscan
from netriskscan import NetRiskScan

client = NetRiskScan()  # no signup, no API key required

result = client.ip_risk("8.8.8.8")

print(result.risk.index)  # 0-100, higher = cleaner. Example output; live scores can change.
print(result.risk.band)  # "excellent" | "good" | "fair" | "poor" | "high_risk" | "unknown"

Table of contents

Installation

Requires Python 3.9+.

pip install netriskscan

Quick start

from netriskscan import NetRiskScan

client = NetRiskScan()
result = client.ip_risk("8.8.8.8")

print(result.risk.index, result.risk.band)
print(result.network.organization, result.network.asn)
print(result.flags.proxy, result.flags.vpn, result.flags.tor)

Anonymous usage

The API can be called without an account, metered by the server (a daily allowance per source IP, currently 30 requests/day -- the SDK does not hardcode this limit; read it from the response):

from netriskscan import NetRiskScan

client = NetRiskScan()
result = client.ip_risk("8.8.8.8")

if result.usage is not None:  # only present on anonymous calls
    print(result.usage.remaining, "of", result.usage.daily_limit, "requests left today")

Anonymous quota, rate limits, and eligibility are decided entirely by the server -- the SDK never re-implements or assumes those business rules.

Authentication

Pass an API key explicitly, or set the NETRISKSCAN_API_KEY environment variable. Precedence: explicit argument > environment variable > anonymous.

from netriskscan import NetRiskScan

client = NetRiskScan(api_key="nrs_live_xxxxxxxxxxxxxxxxxxxx")
export NETRISKSCAN_API_KEY="nrs_live_xxxxxxxxxxxxxxxxxxxx"
from netriskscan import NetRiskScan

client = NetRiskScan()  # reads NETRISKSCAN_API_KEY if set, else anonymous

The key is always sent as Authorization: Bearer <api-key> -- never as a URL query parameter, never logged, and never included in exception messages.

IP risk lookup

The core call. ip_risk() is a Python IP risk score API endpoint -- it returns a 0-100 risk index, reputation band, network intelligence, and detection flags in a single request:

result = client.ip_risk("8.8.8.8")

result.risk.index  # int | None -- 0-100 cleanliness score (higher = cleaner), None if unscoreable
result.risk.band  # str | None -- "excellent" | "good" | "fair" | "poor" | "high_risk" | "unknown"
result.risk.assessment_grade  # str -- "complete" | "partial" | "limited" | "insufficient"
result.risk.reasons  # list[RiskReason] -- may be empty, never a signal by itself

result.network.type  # "residential" | "mobile" | "hosting" | "datacenter" | "public_infrastructure" | ...
result.network.connection_type  # "direct" | "vpn" | "proxy" | "tor" | ...
result.network.asn  # e.g. "AS15169"
result.network.organization  # e.g. "Google LLC"

result.flags.proxy  # bool | None -- see "null is not false" below
result.flags.proxy_type  # populated only when flags.proxy is True
result.flags.vpn  # bool | None
result.flags.tor  # bool | None -- Tor *exit* node specifically
result.flags.datacenter  # bool | None
result.flags.scanner  # bool | None -- behavioral scanner/bot activity
result.flags.abuse  # bool | None
result.flags.search_crawler  # bool | None -- verified search-engine crawler identity
result.flags.search_crawler_name  # e.g. "Google", populated only when search_crawler is True

result.location  # IpLocation | None -- network-level geolocation, not device GPS
result.tor  # TorInfo | None -- present only when the address is a Tor relay

IP lookup and network intelligence

Every ip_risk() call returns IP intelligence for the address: ASN, organization, network type (residential, mobile, hosting, datacenter, ...), and connection type -- everything a Python IP lookup API needs to return. result.location adds IP geolocation data -- country, region, city, and time zone -- at the network level (derived from routing/registration data), not device-level GPS location.

Proxy, VPN, and Tor detection

result.flags carries anonymous IP detection signals for the address: proxy, vpn, and tor (Tor exit node specifically -- see result.tor for relay/exit/bad-exit detail), plus datacenter for hosting/datacenter network identification. Each flag is tri-state (see "None is not False" below): True means detected, False means checked and clear, None means not evaluated this round.

The index is a cleanliness score, not a threat score

risk.index runs 0-100 where higher means cleaner / more trustworthy. It is not a fraud or threat score where higher is worse. Never invert or rescale it client-side.

None is not False

Every detection flag (proxy, vpn, tor, datacenter, scanner, abuse, search_crawler) is a three-valued signal:

  • True -- detected
  • False -- checked, and confirmed not detected
  • None -- unknown / not evaluated this round

Treating None as False turns "we don't know" into "we checked and it's clean," which is a different and stronger claim than the data supports. This SDK never performs that coercion, and code consuming these fields should not either.

An unscoreable address is a success, not an error

Private, loopback, and other special-purpose addresses return 200 OK with risk.index is None, risk.band is None, and risk.assessment_grade == "insufficient" -- not an exception. Check for None explicitly rather than assuming every successful call returns a numeric score.

Search crawler identity vs. scanner behavior

flags.search_crawler answers a narrow question: is this address in a range list the search-engine operator itself publishes? It is independent of flags.scanner, which tracks behavioral scanning/bot activity. A verified crawler is not automatically "not a scanner," and vice versa -- read both.

Usage / quota

Requires an API key (there is no anonymous account to report usage for):

usage = client.usage()

print(usage.plan)
print(usage.units.used, "/", usage.units.limit)
print(usage.rate_limit.requests_per_minute)

Calling usage() without an API key raises ValidationError immediately, without a network call.

Async client

The same Python IP reputation API, available as an async client with an identical interface, built on httpx:

from netriskscan import AsyncNetRiskScan


async def main():
    async with AsyncNetRiskScan() as client:
        result = await client.ip_risk("8.8.8.8")
        print(result.risk.index)

Error handling

from netriskscan import (
    NetRiskScan,
    ValidationError,
    AuthenticationError,
    RateLimitError,
    QuotaExceededError,
    NotFoundError,
    FeatureNotAvailableError,
    ApiError,
    TimeoutError,
    NetworkError,
)

client = NetRiskScan(api_key="nrs_live_xxxxxxxxxxxxxxxxxxxx")

try:
    result = client.ip_risk("8.8.8.8")
except ValidationError as e:
    ...  # bad IP address / bad request (HTTP 400)
except AuthenticationError as e:
    ...  # missing, invalid, or disabled API key (HTTP 401/403)
except QuotaExceededError as e:
    ...  # billing-period quota or anonymous daily limit exhausted (HTTP 429)
except RateLimitError as e:
    ...  # short-lived per-minute rate limit (HTTP 429); e.retry_after in seconds
except (NotFoundError, FeatureNotAvailableError):
    ...  # unknown route, or a documented capability not open yet (HTTP 404)
except ApiError as e:
    ...  # any other non-2xx response, e.g. HTTP 503 temporarily_unavailable
except TimeoutError as e:
    ...  # request did not complete in time; never retried automatically
except NetworkError as e:
    ...  # DNS/connection failure -- never reached the server

Every exception carries .message, .status_code, .code (the server's open-vocabulary error code), and .request_id where available -- include request_id when reporting issues. QuotaExceededError is a subclass of RateLimitError, so except RateLimitError alone catches both.

Rate limits

from netriskscan import get_response_meta

result = client.ip_risk("8.8.8.8")
meta = get_response_meta(result)

if meta:
    print(meta.rate_limit.remaining, "/", meta.rate_limit.limit)
    print(meta.quota.remaining, "/", meta.quota.limit)
    print(meta.request_id)

get_response_meta() returns None for a header that was never sent -- it is never coerced to 0.

Automatic retries

GET requests are retried automatically for HTTP 429/502/503/504 and for transient network failures, honoring the server's Retry-After header when present, otherwise using exponential backoff with jitter. Retries are capped by max_retries (default 2) and max_retry_delay (default 10 seconds); a Retry-After longer than max_retry_delay raises immediately instead of blocking. 400/401/403/404 responses and request timeouts are never retried.

Configuration

from netriskscan import NetRiskScan

client = NetRiskScan(
    api_key="nrs_live_xxxxxxxxxxxxxxxxxxxx",  # optional; falls back to NETRISKSCAN_API_KEY, then anonymous
    base_url="https://api.netriskscan.com",  # override for testing/staging/mocking
    timeout=10.0,  # seconds, per attempt
    max_retries=2,
    max_retry_delay=10.0,  # seconds
)

Pass http_client=httpx.Client(...) (or httpx.AsyncClient(...) for AsyncNetRiskScan) to inject your own transport, for example httpx.MockTransport in tests.

Type hints

The package ships py.typed and is fully annotated. Results are plain, immutable dataclasses -- no ORM-style magic, easy to log, cache, or serialize with dataclasses.asdict().

Use cases

  • Detect proxy, VPN, and Tor infrastructure (anonymous IP detection) before signup or login
  • Evaluate IP reputation and risk score as one signal in a fraud detection pipeline
  • Distinguish verified search-engine crawlers from generic bot/scanner traffic
  • Inspect datacenter IP addresses and hosting traffic separately from residential networks
  • Add network intelligence (ASN, organization, connection type) to abuse-prevention systems
  • Gate CI/CD or infrastructure checks on a minimum risk index

FAQ

How do I check IP reputation in Python?

Install the SDK (pip install netriskscan) and call client.ip_risk("8.8.8.8") -- no API key required for anonymous, rate-limited use. result.risk.band and result.risk.reasons give the reputation read. See IP risk lookup.

How do I check an IP risk score in Python?

result.risk.index is a 0-100 cleanliness score (higher = cleaner) from the same ip_risk() call. See "The index is a cleanliness score, not a threat score".

How do I look up an IP address in Python?

client.ip_risk(ip) is the SDK's IP lookup call -- one request returns risk, network intelligence (ASN, organization, connection type), and IP geolocation. See IP lookup and network intelligence.

How do I detect a proxy IP in Python?

Check result.flags.proxy (and result.flags.proxy_type when True). It's tri-state -- None means "not evaluated," not "not a proxy." See Proxy, VPN, and Tor detection.

How do I detect VPN in Python?

Check result.flags.vpn from the same ip_risk() response. Combine with result.flags.proxy and result.flags.tor for full anonymous IP detection.

Can Python detect Tor exit nodes?

Yes -- result.flags.tor flags Tor exit nodes specifically, and result.tor (present only for Tor relays) adds is_relay, is_exit, and is_bad_exit detail.

How do I detect datacenter IP addresses in Python?

Check result.flags.datacenter, or read result.network.type ("hosting", "datacenter", "residential", "mobile", ...) for the broader network classification.

Does the SDK support async IP lookups?

Yes -- AsyncNetRiskScan mirrors the sync client's API on an httpx-based async transport. See Async client.

API documentation

Base URL: https://api.netriskscan.com

  • GET /v1/ip-risk/{ip} -- IP risk, reputation, and network intelligence (works with or without an API key)
  • GET /v1/usage -- current billing-period usage and quota (requires an API key)

Full endpoint and error-code reference: Developer API documentation.

NetRiskScan ecosystem

Need JavaScript or TypeScript instead? See @netriskscan/sdk.

Examples

Runnable scripts in examples/:

File Demonstrates
examples/quickstart.py Sync client, anonymous IP risk lookup
examples/async_quickstart.py Async client
examples/anonymous.py Reading the anonymous daily allowance from a response
examples/usage_quota.py Authenticated usage/quota lookup
examples/error_handling.py Catching the full exception hierarchy

Security

  • The API key is only ever sent as an Authorization: Bearer header, never in a URL, log line, or exception message.
  • This SDK makes no calls to any host other than the configured base_url.
  • No telemetry of any kind is collected or transmitted by this package.

Found a security issue? See SECURITY.md.

License

MIT -- see LICENSE.

Releases

Packages

Contributors

Languages