Quickstart
A working call in about two minutes, then the three things worth getting right before you ship.
1. Get your key
Create an account, open the API key page and generate your key. It is shown once, at that moment, and never again. It is stored hashed, so nobody can recover it for you. If you lose it, regenerate.
2. Make a call
Send the key in the Authorization header and the address in the path.
curl -H "Authorization: Bearer nv_live_..." \
https://novpn.fr/v1/check/203.0.113.7
3. Decide what to do with the answer
Most integrations only need the risk field, which buckets the score so you do not have to pick a threshold.
# Block anything the API is confident about
if [ "$(jq -r .risk <<< "$response")" = "high" ]; then
echo "blocked"
fi
If you would rather set your own line, use the raw score. The buckets are documented alongside it in the endpoint reference, so you can start with a bucket and move to a number later without changing anything else.
Handle the unknown address
This is the case most integrations get wrong. An address the detection database has never seen returns 404, because "we have no opinion" is genuinely different from "this address is clean". A scan is queued automatically, so the same address usually resolves within minutes.
Treat it as "no opinion", not as "clean": retry shortly, or let the request through if a blocked login costs you more than a missed one. There is deliberately no way to turn the 404 into a success. When the address is absent there is no country, operator or ASN to return either, so such a response would carry nothing you could act on.
Before you ship
- Set a client timeout of about two seconds. The API answers in milliseconds; a request that has not returned by then is not going to.
- Decide in advance what happens when the call fails. For most uses, failing open (letting the request through) is the right answer: a detection service outage should not become your outage.
- Cache results on your side if you check the same address repeatedly. Results are stable over minutes, and it saves your quota.
- Restrict your key to the IP addresses of your servers. A key that only works from your infrastructure is worth much less to anyone who steals it.
A complete integration
The four rules above in one function, in four languages. Standard library only, apart from Python's requests. Each one gives up after two seconds, treats an unknown address and any failure alike as a reason to let the request through, and asks for a challenge only when risk is high. Copy it and change the threshold, not the structure: the timeout, the unknown address and the failure path are the parts that are easy to leave out and expensive to leave out.
JavaScript
async function screen(ip) {
try {
const response = await fetch(`https://novpn.fr/v1/check/${ip}`, {
headers: { Authorization: `Bearer ${process.env.NOVPN_API_KEY}` },
signal: AbortSignal.timeout(2000),
});
if (response.status === 404) return "allow";
if (!response.ok) return "allow";
const { risk } = await response.json();
return risk === "high" ? "challenge" : "allow";
} catch {
return "allow";
}
}
Python
import os
import requests
def screen(ip: str) -> str:
try:
response = requests.get(
f"https://novpn.fr/v1/check/{ip}",
headers={"Authorization": f"Bearer {os.environ['NOVPN_API_KEY']}"},
timeout=2,
)
except requests.RequestException:
return "allow"
if response.status_code == 404:
return "allow"
if not response.ok:
return "allow"
return "challenge" if response.json()["risk"] == "high" else "allow"
PHP
function screen(string $ip): string
{
$curl = curl_init('https://novpn.fr/v1/check/' . rawurlencode($ip));
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 2,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NOVPN_API_KEY')],
]);
$body = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
if ($body === false || $status !== 200) {
return 'allow';
}
return json_decode($body, true)['risk'] === 'high' ? 'challenge' : 'allow';
}
Go
func Screen(ctx context.Context, ip string) string {
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
request, err := http.NewRequestWithContext(
ctx, http.MethodGet, "https://novpn.fr/v1/check/"+url.PathEscape(ip), nil)
if err != nil {
return "allow"
}
request.Header.Set("Authorization", "Bearer "+os.Getenv("NOVPN_API_KEY"))
response, err := http.DefaultClient.Do(request)
if err != nil {
return "allow"
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return "allow"
}
var check struct {
Risk string `json:"risk"`
}
if err := json.NewDecoder(response.Body).Decode(&check); err != nil {
return "allow"
}
if check.Risk == "high" {
return "challenge"
}
return "allow"
}