Endpoints

Three endpoints, all returning JSON.

The same contract as a machine-readable OpenAPI 3.1 document, generated from the running service rather than written by hand: /openapi/v1.json

GET /v1/check/{ip}

Returns what is known about an IP address. Accepts IPv4 and IPv6; the address you get back is the canonical form of what you sent, so an expanded IPv6 address comes back compressed.

Parameters

Name In Description
ip path The address to look up. Required.

Response

Field Type Description
ipstringThe address, canonicalised.
scorenumber0 to 1, higher meaning more evidence of anonymisation. Always present on a 200.
riskstringThe score as a bucket, so you do not have to choose a threshold.
proxybooleanAn open HTTP, SOCKS or residential proxy was detected.
vpnbooleanA VPN endpoint was detected.
torbooleanThe address is a known Tor exit node.
hostingbooleanThe address belongs to a datacentre or hosting provider rather than a consumer network.
residential_proxybooleanA residential proxy: traffic relayed through a consumer connection, which is the hardest kind to spot.
detectionsstring[]The raw signal names behind the booleans. Passed through unchanged, so new detection types appear here before they get a dedicated field.
networkobjectAutonomous system number, operator name, reverse-DNS name where available, and the cloud provider announcing the range where one is known. The last two are null for most addresses.
locationobjectCountry and continent, each as both a name and its code: country_code is ISO 3166-1 alpha-2, continent_code is two letters. Branch on the codes, display the names.
cachedbooleanWhether the answer came from the shared response cache. Always false on the free plan, which has no cache and pays the full round trip on every lookup. Cached answers are billed the same as fresh ones: the cache saves latency, not quota.
request_idstringIdentifies this request in our logs. Quote it in a support ticket.

Risk buckets

These boundaries are stable. If you need a different line, use the score directly rather than reinterpreting the buckets.

riskscore
none< 0.15
low0.15 – 0.50
medium0.50 – 0.85
high≥ 0.85

Detection names

Current values include HttpProxy, SocksProxy, ResidentialProxy, Vpn, OpenVpn, Ipsec, Pptp, Sstp, SoftEther, Tor, Server, Ssh, Smtp, Web, Mysql and Hostname. Treat the list as open: new signals are added over time, and a client that switches on unknown names must not break. The boolean fields are the stable interface.

POST /v1/check

Checks up to 100 addresses in one call. Same lookups, same billing, one round trip: this is what you want when the question is "score the forty thousand signups I already have" rather than "is this login what it claims to be".

curl -X POST -H "Authorization: Bearer nv_live_..." \
     -H "Content-Type: application/json" \
     -d '{"ips": ["203.0.113.7", "198.51.100.4"]}' \
     https://novpn.fr/v1/check

Entries come back in the order they were sent, so you can zip the array against your own records by index. Each one carries exactly one of check or error, never both and never neither, which is the same rule the single endpoint follows. A malformed address fails only its own entry.

{
  "results": [
    { "ip": "203.0.113.7", "check": { "score": 0.95, "risk": "high", ... }, "error": null },
    { "ip": "198.51.100.4", "check": null,
      "error": { "code": "ip_not_found", "detail": "Not in the database yet; a scan has been queued." } }
  ],
  "billable": 1,
  "request_id": "01JXQ..."
}

What a batch costs

A batch spends one per-minute request per address, not one for the call. That is deliberate: the rate limit exists to protect the detection service, and a hundred lookups cost it a hundred lookups however they arrived. So a batch can never be larger than your plan's per-minute rate, and one that is answers batch_too_large rather than being silently truncated. GET /v1/me reports burst_per_minute so you can size batches without guessing.

Quota works exactly as it does for a single check: only entries that came back with a result are billable. If the allowance runs out partway through, the addresses that fit are still answered and the rest carry a quota_exceeded error of their own. A batch sent with nothing left at all is refused whole with a 429, rather than returning a hundred identical refusals.

GET /v1/me

Reports your plan and where you stand in the current period, including whether your plan has the response cache. Useful for a startup check that your key works, and for alerting before you run out.

{
  "plan": "pro",
  "quota_limit": 500000,
  "quota_used": 128443,
  "quota_remaining": 371557,
  "quota_resets_at": "2026-09-01T00:00:00+00:00",
  "burst_per_minute": 1200,
  "cache_enabled": true,
  "request_id": "01JXQ..."
}

This endpoint never counts against your quota, and it keeps answering after you have exhausted it. That is precisely when you need to see it.