Skip to main content

Base URL

To look up one IP, pass it as the ipAddress query parameter:
The path form GET https://vpn-proxy-detection.whoisxmlapi.com/api/v1/ip/{ip}?apiKey=YOUR_API_KEY is still supported and returns the same response.

Endpoint inventory

The paths in this inventory are relative to the base URL.

Lookup

On Business and Enterprise, add as_of=YYYY-MM-DD to a single-IP lookup for our answer on a past day: see point-in-time lookups.

Aggregates

Bulk exports

Account

Conventions

IP format

Always use canonical string form. IPv4 and IPv6 addresses are both accepted:
  • IPv4: 1.1.1.1
  • IPv6: 2606:4700:4700::1111
The API covers IPv4 today. IPv6 addresses are accepted but currently return little or no classification; IPv6 coverage is on the roadmap.
Private and reserved ranges return 400 invalid_ip_reserved — we don’t classify them. This covers RFC 1918 private space, loopback, multicast, link-local, the unspecified address and the TEST-NET documentation ranges (e.g. GET /api/v1?ipAddress=0.0.0.0 → 400 invalid_ip_reserved). RFC 6598 shared address space (CGNAT, 100.64.0.0/10) is not rejected: it is looked up, and counts as a lookup, like any other address.

Response shape

Every key in the lookup response schema is returned unless its description says otherwise; new keys may be added. Field availability depends on tier — see tiers. Most tier-gated fields keep their keys present, set to null (or false), rather than being omitted, so gate on your own tier, not on field presence:
  • The residential-attribution precision sub-flags (is_residential_proxy_high_confidence, is_residential_proxy_mobile) are always present but forced to false below the pro tier. (The base is_residential_proxy boolean is shown to all tiers.)
  • provider, operator_group and operator_legal_name are null below the starter tier, and below the pro tier when the classification is a residential proxy label.
  • netblock, netname and netblock_org are null below the starter tier.
  • confidence, first_seen, last_seen, observation_count, hits_days_pct, providers_num, confidence_decay, freshness_class and operator_profile are null below the pro tier.
A few keys are left out instead of nulled: original_classification, asn_abuse_promoted and hosting_risk_calibrated are not returned on Intro or the free sign-up lookups; the asn_abuse breakdown (flagged_ratio, flagged_ip_count, total_announced_ips) is not returned below the pro tier; observed_location is not returned below the enterprise tier; data_status appears only when a check could not run; and vpn_exit_confidence is reserved and not returned on any plan today. Note that asn_abuse and sanctions_risk are legitimately object-or-null on every plan; a null there is never a tier strip. It means we hold no data, unless data_status names that check as degraded: then the check could not run, and the null says nothing about risk. sanctions_risk is reserved and is null today on every plan.

Caching

Aggregate endpoints (/stats, /providers, /stats/country/*, /stats/asn-abuse) are server-side cached for 60 s – 5 min. The Cache-Control header reflects this. Lookup endpoints are not cached server-side: each request reads the serving database, which refreshes once a day.

Time

All timestamps are ISO 8601 with a numeric UTC offset, for example 2026-05-02T04:53:02.210236+00:00. Fractional seconds appear only when they are not zero: the first_seen of an address we saw as a residential proxy exit is usually recorded at 00:00 UTC and reads 2026-05-02T00:00:00+00:00. This applies to every timestamp field the API emits (first_seen, last_seen, last_updated, computed_at). Use a lenient ISO 8601 parser — don’t hard-code a Z-suffix or fractional-second format string or regex.