myipcheck.net

API reference

Connection intelligence and IP risk scoring for businesses. One API shows how a connection really looks from the outside (address and network, TCP/IP and TLS fingerprints, DNS resolvers, WebRTC) and scores how far an IP can be trusted. Base URL: https://probe.myipcheck.net.

What the API is for and how access works: API overview. Who we are and how risk scoring works: About.

Overview

Calls are HTTPS GET requests that return JSON. Unknown fields may be added over time; ignore the ones you do not use. A missing measurement is null, never a guess.

What you can get

  • The connection’s public IPv4 and IPv6 addresses.
  • Approximate location and network (ASN, operator).
  • One merged card about any address: location with its precision, network type, the lists network owners publish, block registration and how often the address was checked on myipcheck.net.
  • Fraud scores of six independent services for any address, each on its own scale, with a vote across them.
  • An IP risk score from 0 to 100, with the reasons behind it.
  • The TCP/IP fingerprint and estimated operating system of the connecting host, with MTU and hops.
  • The TLS and HTTP fingerprint: JA3/JA4, ALPN and headers.
  • The DNS resolvers used, with EDNS Client Subnet.
  • WebRTC: the address a browser reveals over UDP, through the STUN endpoint below.
  • Round-trip latency.
  • How many other visitors checked an IP and its subnet in the last 30 days.

Use of the data is governed by the integration agreement.

Access

Access is for business clients. Each client has a prepaid balance of credits, its own keys, and a dashboard to manage them. Limits and terms are agreed per integration. Requests made with server keys share one rate limit per client; requests made with permissions are limited per end user (sub). To get access, write to admin@myipcheck.net.

Dashboard

Every client gets a dashboard: the balance, the risk sources enabled for it with ready URLs, keys, requests and an activity log. People sign in to the dashboard with an email code, and each sees only their own client.

Screenshots of every screen: API overview.

Authentication

Send credentials in the Authorization: Bearer header. Two kinds are accepted.

Server keys

A server key starts with sk_ and identifies your client. Create and revoke keys in the dashboard; the full key is shown once. Use it only from your servers, never in a browser or app.

bash
curl https://probe.myipcheck.net/v1/echo \
  -H "Authorization: Bearer sk_YOUR_SERVER_KEY"

Delegated permissions

To let a browser or app call the API directly, your server signs a short-lived permission for one end user. The permission is a JWT signed with ES256 by a key pair you control; we only hold the public key.

From a web page on your own site, echo and DNS answer the browser when it sends a permission, whatever the site's origin (CORS). The address card, fraud score and risk score have no CORS: call them from your server, or from an app with a permission.

  1. Generate a P-256 key pair. Keep private.pem on your server.
  2. Register the public key in the dashboard under Permission keys. Its key id (kid) is shown there. Private keys are refused.
  3. Sign a permission on your server for each end user and hand it to their browser, which sends it as Authorization: Bearer <permission>.
bash
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out private.pem
openssl pkey -in private.pem -pubout -out public.pem

The header needs alg ES256 and a kid of one of your active keys. Claims:

ClaimRule
issYour client id.
audmyipcheck.net.
subYour own identifier for the end user, 1 to 128 characters. It is not interpreted and appears in the usage rows.
jtiUnique id of this permission, 1 to 64 characters of A-Z a-z 0-9 _ -.
iat, expBoth required. exp - iat is at most 300 seconds. A clock skew of 30 seconds is tolerated.
scopeNon-empty list of echo, dns, risk, card, fraud_score.
maxWhole number from 1 to 50: how many responses this permission may produce.

A response counts against max only when it is delivered; rejected requests do not use it.

node.js (jose)
import { readFileSync } from 'node:fs';
import { randomUUID } from 'node:crypto';
import { SignJWT, importPKCS8 } from 'jose';

const privateKey = await importPKCS8(readFileSync('private.pem', 'utf8'), 'ES256');

export async function signPermission(userId) {
  return new SignJWT({ scope: ['echo', 'risk'], max: 3 })
    .setProtectedHeader({ alg: 'ES256', kid: 'YOUR_KEY_ID' })
    .setIssuer('your-client-id')
    .setAudience('myipcheck.net')
    .setSubject(userId)
    .setJti(randomUUID())
    .setIssuedAt()
    .setExpirationTime('2m')
    .sign(privateKey);
}
python (PyJWT)
import time
import uuid

import jwt  # pip install pyjwt cryptography

with open("private.pem") as f:
    private_key = f.read()


def sign_permission(user_id: str) -> str:
    now = int(time.time())
    claims = {
        "iss": "your-client-id",
        "aud": "myipcheck.net",
        "sub": user_id,
        "jti": uuid.uuid4().hex,
        "iat": now,
        "exp": now + 120,
        "scope": ["echo", "risk"],
        "max": 3,
    }
    return jwt.encode(claims, private_key, algorithm="ES256", headers={"kid": "YOUR_KEY_ID"})

Operations

Each delivered response is charged in credits at your client’s price. A server key can call every operation; a permission needs the matching scope.

GET /v1/card?ip=…

One merged answer about an address: where it is and how precisely, which network it belongs to, whether it is on a list its owner publishes, who registered the block, and how often it was checked on myipcheck.net. Scope card. ip must be a global unicast address without a zone. Every delivered card is charged at your card price, a card served from cache included; a failed request is not charged.

bash
curl "https://probe.myipcheck.net/v1/card?ip=198.51.100.20" \
  -H "Authorization: Bearer sk_YOUR_SERVER_KEY"
json
{
  "format": 1,
  "checked_at": "2026-10-26T13:46:56Z",
  "address": "198.51.100.20",
  "version": 4,
  "complete": true,
  "location": {
    "precision": "city",
    "city": "Atlanta",
    "region": "Georgia",
    "country": "United States",
    "country_code": "US",
    "postal": "30303",
    "accuracy_km": 10,
    "coordinates": { "lat": 33.74831, "lon": -84.39111 },
    "confirmed_by_operator": false,
    "anycast": false,
    "time_zone": "America/New_York",
    "utc_offset": "-04:00"
  },
  "network": {
    "type": "mobile",
    "owner_list": null,
    "provider": "Example Mobile",
    "asn": 64510,
    "as_name": "EXAMPLE-MOBILE",
    "routed_prefix": "198.51.100.0/24",
    "hostname": null,
    "registered_to": "Example Mobile",
    "block_since": "2012-09-18",
    "abuse_email": "abuse@example.net",
    "mobile_network": { "name": "Example Mobile", "mcc": "310", "mnc": ["260"] }
  },
  "history": {
    "days": 30,
    "other_visitors": 23,
    "subnet": "198.51.100.0/24",
    "subnet_visitors": 118,
    "subnet_addresses": 41,
    "by_day": [{ "date": "2026-10-25", "visitors": 2 }]
  }
}

New fields may appear over time; ignore the ones you do not know. A value no source knows is null.

FieldMeaning
formatVersion of the card format, now 1.
checked_atWhen the card was assembled, RFC 3339 in UTC. A card from cache keeps the time it was assembled.
address, versionThe address and its IP version, 4 or 6.
completetrue when every location source answered; false when one did not answer within 3 seconds.
locationnull only when no source knows the address.
location.precisionThe level at which the location is honest. city when the city is reliable; region when it is not, with a wider accuracy_km; country for anycast addresses or when only the country is known. A region location has no city or postal; a country location also has no region, coordinates or accuracy_km.
location.city, region, country, country_code, postalNames in English; country_code is ISO 3166-1 alpha-2.
location.accuracy_kmHow many kilometres the centre may be off, a whole number.
location.coordinates{lat, lon} of the centre.
location.confirmed_by_operatorThe city matches the network owner’s own geofeed (RFC 8805).
location.anycastThe address is served from many places, so there is no city.
location.time_zone, utc_offsetIANA zone and its offset when the card was assembled, ±HH:MM.
network.typeFrom a list the network’s owner publishes: privacy_relay, tor_exit, cloud, or hosting for a Google address. Otherwise the address’s usage type: mobile, isp, isp_mobile, hosting, business, organization, government, military, education, library, cdn, search_engine, reserved. null when unknown.
network.owner_listThe published list that gave type: apple_private_relay, tor, aws, gcp, oracle_cloud, azure, digitalocean, linode, github, fastly, google_services; null when the address is on none.
network.provider, asn, as_name, routed_prefix, hostnameThe provider, AS number (a number), AS name, the announced prefix and the reverse DNS name (null when it only repeats the address).
network.registered_to, block_since, abuse_emailFrom RDAP: the holder of the block, the date the block was registered (YYYY-MM-DD) and the abuse contact. Without RDAP, the abuse contact a location source gives.
network.mobile_network{name, mcc, mnc[]} for a mobile operator; mnc lists its codes.
historyChecks of this address on myipcheck.net in the last days days: other_visitors of this address, its subnet with subnet_visitors and subnet_addresses, and by_day with only the days that had visitors, newest first. These are plain counters. null when the history is not available.

history is counted afresh for every response. An incomplete card is completed on a later request.

GET /v1/fraud-score?ip=…&max_age=…

What six independent fraud score services say about an address: IPGeolocation, ProxyCheck, IP2Location, Scamalytics, IPQualityScore and MaxMind. Each answers on its own scale, with the levels its owner publishes, and the answer counts how many of them agree. Scores are never averaged. Scope fraud_score. ip must be a global unicast address without a zone.

max_age (optional, whole seconds from 0 to 86400, default 86400) is the oldest answer you accept. When the ready answer, or the answer of any service it was built from, is older, the services are asked again; max_age=0 asks all of them again. Every delivered answer is charged at your fraud_score price, an answer from cache included; a failed request is not charged.

bash
curl "https://probe.myipcheck.net/v1/fraud-score?ip=198.51.100.23&max_age=3600" \
  -H "Authorization: Bearer sk_YOUR_SERVER_KEY"

A shortened answer: a real one always lists all six services, each with every signal and fact it reports.

json
{
  "format": 1,
  "checked_at": "2026-10-26T13:46:56Z",
  "address": "198.51.100.23",
  "status": "partial",
  "verdict": { "level": "mixed", "high": 2, "medium": 1, "low": 2 },
  "detections": [
    { "signal": "vpn", "detected_by": 2, "of": 5 },
    { "signal": "tor", "detected_by": 0, "of": 6 }
  ],
  "known": [{ "list": "oracle_cloud" }],
  "sources": [
    {
      "source": "ipgeolocation",
      "name": "IPGeolocation",
      "score": 80,
      "scale": { "max": 100, "unit": "points" },
      "level": "high",
      "signals": { "vpn": true, "proxy": false, "tor": false },
      "details": { "vpn_providers": [], "vpn_confidence": 70, "vpn_last_seen": null, "relay_provider": null, "cloud_provider": "Oracle Cloud Infrastructure", "bot": null }
    },
    {
      "source": "maxmind",
      "name": "MaxMind",
      "score": 2.75,
      "scale": { "max": 99, "unit": "percent" },
      "level": "low",
      "signals": { "anonymous": true, "hosting": true, "relay": false },
      "details": { "user_type": "hosting", "overall_risk_score": 1.96 }
    },
    { "source": "scamalytics", "name": "Scamalytics", "status": "no_answer" }
  ]
}

New fields may appear over time; ignore the ones you do not know.

FieldMeaning
formatVersion of the format, now 1.
checked_atThe oldest of the services’ answers the response was built from, RFC 3339 in UTC.
addressThe address.
statuscomplete when all six services answered; partial when at least one did not answer or was late.
verdict.levelA vote, not an average. high when at least half of the voting services say high risk; low or medium when more than half say so; otherwise mixed; none when no service votes.
verdict.high, medium, lowHow many services said high, medium and low risk (the red, yellow and green bands of their levels).
detections[]Every signal at least one service reports, negatives included: signal, detected_by (how many services see it) and of (how many report that signal at all). In the order vpn, proxy, residential, tor, relay, anonymous, hosting, bot, scraper, spam, attacker, compromised, blacklisted, satellite.
known[]What a list published by the network’s owner says for certain, before any score: {list} is tor, else apple_private_relay, else a cloud id (aws, gcp, oracle_cloud, azure, digitalocean, linode, github, fastly). An empty array when there is no match.
sources[]In the order ipgeolocation, proxycheck, ip2location, scamalytics, ipqs, maxmind; name is the service’s own name.
sources[].score, scaleThe service’s score (a number or null) and its scale: max and unit (points or percent).
sources[].levelThe level by the thresholds below; null when there is no score.
sources[].signalsThe signals this service reported, true or false. A missing signal means the service does not report it, not that it is absent. Scamalytics’ vpn and proxy do not vote and are not counted in detections: they come from the same data as ip2location, which already counts them.
sources[].detailsThe service’s facts: VPN and proxy providers, confidence, when last seen, operator, user type and so on. Which facts appear depends on the service. For maxmind, overall_risk_score is MaxMind’s overall risk score, on the same percent scale.
{source, name, status}status: "no_answer": the service did not answer, answered without its part, is switched off or was late.

Levels of each service, from the thresholds its owner documents. A score below a band’s upper limit belongs to that band.

sourceScaleLevels
ipqs0 to 100, pointslow below 75 (green); suspicious 75 to 84 (yellow); high 85 and up (red)
maxmind0.01 to 99, percentlow below 5 (green); review 5 to 49.99 (yellow); high 50 and up (red)
proxycheck0 to 100, pointslow below 26 (green); medium 26 to 50 (yellow); high 51 to 75 and very_high 76 and up (red)
ipgeolocation0 to 100, pointslow below 20 (green); medium 20 to 44 (yellow); elevated 45 to 79 and high 80 and up (red)
scamalytics0 to 100, pointslow below 20 (green); medium 20 to 59 (yellow); high 60 to 89 and very_high 90 and up (red)
ip2location0 to 99, pointslow below 50 (green); review 50 to 79 (yellow); high 80 and up (red). IP2Location publishes no levels; these are the thresholds from an example on its blog.

A response takes up to 10 seconds. A service that has not answered by then is no_answer, and a later request asks it again. When no service answers, there is no response: 503 FRAUD_SCORE_UNAVAILABLE, and nothing is charged.

GET /v1/risk/{source}?ip=…

Scores how far an IP address can be trusted. It is delivered through risk sources: each source has its own id (rs_…), name and price in credits, and is enabled for your client individually. Your sources are listed with ready URLs under Risk sources in the dashboard. Scope risk. Repeat lookups of the same IP through the same source within 24 hours are free and marked cached; pass max_age (seconds) to require a fresher assessment, which is charged. A failed assessment is not charged. Risk scoring is offered for fraud prevention, account security and traffic quality, under the integration agreement.

FieldMeaning
score0 to 100; higher means riskier.
reasonsReason codes in lowercase, such as anonymous_ip or hosting_provider. The set depends on the source; treat codes you do not know as informational. Empty when there is no particular reason.
sourceThe risk source that answered.
cachedWhether the answer came from your own earlier assessment of the same address through the same source. Such a repeat costs 0 credits.

Query parameter max_age (optional, whole seconds from 0 to 86400) sets the oldest cached assessment you accept: an older one is replaced by a fresh, charged assessment, and max_age=0 always fetches fresh. Answers are never shared between clients. The parameter also works on /v1/risk and on /v1/echo?risk=1.

bash
curl "https://probe.myipcheck.net/v1/risk/rs_YOUR_SOURCE?ip=203.0.113.7" \
  -H "Authorization: Bearer sk_YOUR_SERVER_KEY"
json
{
  "ip": "203.0.113.7",
  "source": "rs_k3m9x2ab",
  "score": 2,
  "reasons": ["anonymous_ip"],
  "cached": false,
  "fetched_at": "2026-10-24T12:00:00Z"
}

GET /v1/echo

Measures the connection that makes the request: address, approximate location and network, and the TCP/IP, TLS and HTTP fingerprints. Scope echo. Call it from the end user’s own browser with a permission to measure their connection.

bash
curl https://probe.myipcheck.net/v1/echo \
  -H "Authorization: Bearer $PERMISSION"
json
{
  "ip": "203.0.113.7",
  "port": 48120,
  "family": 4,
  "time": "2026-10-24T12:00:00Z",
  "check_id": "01J00000000000000000000000",
  "geo": {
    "country": "DE",
    "country_name": "Germany",
    "region": "Hesse",
    "city": "Frankfurt",
    "timezone": "Europe/Berlin",
    "asn": 64500,
    "as_org": "Example Networks"
  },
  "tcp": {
    "captured": true,
    "ttl": 51,
    "hops": 13,
    "mss": 1460,
    "window": 64240,
    "mtu": 1500,
    "mtu_class": "ethernet",
    "os_guess": { "name": "Linux/Android", "family": "linux", "confidence": "high" },
    "rtt_ms": 52.4
  },
  "tls": {
    "version": "TLS 1.3",
    "cipher_suite": "TLS_AES_128_GCM_SHA256",
    "alpn": "h2",
    "ja3_hash": "0123456789abcdef0123456789abcdef",
    "ja4": "t13d1516h2_8daaf6152771_02713d6af862"
  },
  "http": { "proto": "HTTP/2.0", "method": "GET", "path": "/v1/echo", "headers": { "user-agent": "Mozilla/5.0" } }
}

GET /v1/dns/{token}

Reports which DNS resolvers resolved a unique name for this lookup. First resolve <token>.chk.myipcheck.net, then ask here; wait (milliseconds, up to 5000) holds the request until a resolver is seen. Scope dns. You are charged only when resolvers were found.

bash
curl "https://probe.myipcheck.net/v1/dns/a1b2c3d4e5f6a7b8c9d0?wait=1500" \
  -H "Authorization: Bearer sk_YOUR_SERVER_KEY"
json
{
  "token": "a1b2c3d4e5f6a7b8c9d0",
  "found": true,
  "ttl_s": 60,
  "resolvers": [
    {
      "ip": "198.51.100.53",
      "family": 4,
      "ecs": "203.0.113.0/24",
      "qtypes": ["A", "AAAA"],
      "count": 2,
      "geo": { "country": "DE", "asn": 64501, "as_org": "Example DNS" },
      "public": false,
      "kind": "other"
    }
  ]
}

WebRTC (STUN) stun:probe.myipcheck.net:3478

A STUN endpoint (RFC 5389 Binding, UDP port 3478, IPv4 and IPv6) that tells a browser or app which public address its UDP traffic leaves from. Use it as the only ICE server of a RTCPeerConnection, then compare the address of the srflx candidate with the ip of /v1/echo from the same browser: a different address is a WebRTC leak, and a private or .local host candidate shows what the browser exposes locally. STUN carries no credentials, so this endpoint needs no key and is not charged; it is limited per address. The comparison itself runs with your permission or key, through echo.

javascript
const pc = new RTCPeerConnection({ iceServers: [{ urls: 'stun:probe.myipcheck.net:3478' }] });
pc.createDataChannel('probe');
pc.onicecandidate = (e) => {
  if (e.candidate && e.candidate.type === 'srflx') console.log('UDP leaves from', e.candidate.address);
};
await pc.setLocalDescription(await pc.createOffer());

Balance and usage

The balance is prepaid and counted in credits. A request is charged when its response is delivered. In usage rows, op is echo, dns, risk, card or fraud_score, and units is the number of credits charged. cached is true for an answer served from cache: a repeat risk lookup is free (0 units), while a card or fraud score from cache is charged at its price. Every request is listed, charged or not. When the balance does not cover an operation the answer is 402 CLIENT_BALANCE_EXHAUSTED. Both routes need a server key.

GET /v1/client/balance

bash
curl https://probe.myipcheck.net/v1/client/balance \
  -H "Authorization: Bearer sk_YOUR_SERVER_KEY"
json
{ "client": "acme", "balance": 48250, "updatedAt": "2026-10-24T09:12:31.204Z" }

GET /v1/client/usage

Query: since (RFC 3339), limit (1 to 1000, default 100) and after (cursor). Rows are in ascending order; pass next as after to continue. sub and jti are empty for requests made with a server key.

bash
curl "https://probe.myipcheck.net/v1/client/usage?since=2026-10-01T00:00:00Z&limit=100" \
  -H "Authorization: Bearer sk_YOUR_SERVER_KEY"
json
{
  "client": "acme",
  "usage": [
    { "id": 1042, "at": "2026-10-24T09:12:31.204Z", "sub": "", "jti": "", "op": "echo", "units": 1, "cached": false },
    { "id": 1043, "at": "2026-10-24T09:12:40.870Z", "sub": "user-8841", "jti": "7d1f0a52", "op": "risk", "source": "rs_k3m9x2ab", "units": 5, "cached": false },
    { "id": 1044, "at": "2026-10-24T09:13:02.115Z", "sub": "user-8841", "jti": "7d1f0a52", "op": "risk", "source": "rs_k3m9x2ab", "units": 0, "cached": true },
    { "id": 1045, "at": "2026-10-24T09:14:19.532Z", "sub": "", "jti": "", "op": "fraud_score", "units": 4, "cached": true }
  ],
  "next": "1045"
}

Errors

Errors are JSON, {"code": "…", "message": "…"}. Branch on code, not on the message.

HTTPCodeMeaning
400INVALID_IPThe ip parameter is missing or is not a global unicast address; private, loopback and zoned addresses are refused.
400INVALID_MAX_AGEmax_age is not a whole number of seconds from 0 to 86400.
400INVALID_LIMIT, INVALID_SINCE, INVALID_CURSORA usage query parameter is not valid.
401AUTH_REQUIREDNo credentials, or an unknown or revoked server key.
401PERMISSION_INVALIDThe permission has a bad signature, an unknown client or key, a revoked key, the wrong audience, has expired, or breaks the claim rules.
402CLIENT_BALANCE_EXHAUSTEDThe prepaid balance does not cover the operation. Nothing was charged.
403CLIENT_INACTIVEThe client is switched off.
403PERMISSION_SCOPEThe permission does not include this operation.
403PERMISSION_EXHAUSTEDThe permission has already produced max responses.
403SERVER_KEY_REQUIREDBalance and usage need a server key, not a permission.
403SOURCE_NOT_ENABLEDThe risk source exists but is not enabled for your client.
403SOURCE_INACTIVEThe risk source is switched off.
404SOURCE_NOT_FOUNDThere is no risk source with this id.
405METHOD_NOT_ALLOWEDHEAD is not accepted with client credentials on paid routes; use GET.
429RATE_LIMITEDToo many requests. Wait for the seconds in Retry-After.
503SERVICE_BUSYThe service is busy. Retry after Retry-After.
503CARD_UNAVAILABLEThe address card could not be produced. Nothing was charged.
503FRAUD_SCORE_UNAVAILABLENo fraud score service answered, or the operation is unavailable. Nothing was charged.
503RISK_UNAVAILABLEThe risk assessment could not be produced. Nothing was charged.
503CLIENT_UNAVAILABLEClient accounts are temporarily unavailable, or the charge could not be recorded. Nothing was charged.