Developers / agents
API docs
Everything on a report page is available as JSON: one call returns the score, proxy / VPN / Tor detection, blocklists and abuse history, per-use-case suitability, every deduction with its source, and the limits of the data. No API key, no sign-up. The machine-readable description is at /openapi.json; the site summary for models is at /en/llms.txt.
Endpoints
One report, three shapes
- GET /en/ip/{ip}
- The report as a web page, for people, search engines and AI browsing tools.
- GET /en/ip/{ip}.json
- The full report as JSON. Identical to /api/lookup?ip={ip}&lang=en — same limits, same allowance.
- GET /en/ip/{ip}.txt
- A 72-column plain-text report. No HTML or JSON parsing needed.
Examples: /en/ip/8.8.8.8 · /en/ip/8.8.8.8.json · /en/ip/8.8.8.8.txt. Drop the /en prefix for the Chinese versions. Some AI browsing environments refuse API-style URLs with query parameters; the path-style addresses above are not affected, and every report page also embeds the same JSON in script#ipure-report. GET /api/myip?format=text echoes the caller's public IP.
Limits
Who can call it, and how much
- IPs already in the database return instantly, cost nothing and can be requested by any client.
- IPs not yet in the database trigger live queries to several upstream sources. Scripts and agents get 5 such lookups per source IP per day without verification (there is also a site-wide daily total); the remainder is in the
x-open-budget-remainingheader. When it runs out the API returns 403 withcode = verification_requiredand areportUrl: open it in a browser once and the IP is in the database from then on. Site-wide, 15 unverified lookups are admitted per minute; beyond that you get 429 withcode = open_rate_limited— wait forRetry-Afterand retry. - Rate limits: 30 requests / minute; 10 first-time lookups of new IPs / minute. Over the limit returns 429.
- Freshness: stored reports carry
queriedAtand astaleflag. An IP's nature rarely changes, so a stored report is usually fine to use as is. - Bulk or scheduled checks: please get in touch via the feedback button on ipure.dev rather than scanning ranges.
Fields
What the response contains
| ip / version | Normalised address and IP version (4 / 6) |
|---|---|
| queriedAt | When the report was generated. With source = store this is the original check time; read it together with stale |
| source | fresh (live lookup) · cache (in-process) · store (database) |
| usageType | residential · mobile · business · hosting · education · government · unknown |
| nativeType | native · broadcast (registered in one country, used in another) · unknown |
| flags | isProxy / isVpn / isTor / isHosting / isMobile / isRelay / isBogon / isSearchEngineBot |
| flagAgreement | How many data sources agree on each flag. A single-source flag counts at half weight in the score |
| vpnOperator | For known commercial VPNs: name, anonymity, popularity, protocols, logging, freeAccess. Otherwise null |
| risk.purity | Purity 0-100; higher is cleaner |
| risk.level / label / verdict | Six bands: pristine · clean · neutral · suspicious · risky · dangerous, with a one-line verdict |
| risk.confidence | low / medium / high — how much data source coverage backs the score. Read score and confidence separately |
| risk.factors[] | Where every point came from: label, category, points (positive deducts, negative adds back), detail, floor (a minimum risk that cannot be offset) |
| scenarios[] | Suitability per use case: ai · social · streaming · gaming · ecommerce · email, each with score, level, levelLabel and reason. score is null when level is restricted or not_applicable |
| scenarioApplicable | false means the address is public infrastructure (a public DNS resolver, a search engine crawler), not anyone's egress IP; the reason is in scenarioNote |
| blocklists[] | Each public blocklist: listed; benign (listed but not a risk, e.g. a PBL residential declaration); unavailable (could not be queried, so unknown) |
| abuse[] | Abuse reports (score, reports) and recorded attack activity (attacks.byType: login attempts, bulk registration, vulnerability probing …) |
| exposure / sharing | Network exposure (open ports, known vulnerabilities) and sharing (devices observed on the address / in the subnet) |
| sources[] | Status of each data source for this report: ok, ms, error, skipped. A failed source is not a clean result |
| feedback | Real-world experience ratings (1-5, 5 is best) per use case from visitors who were actually using this IP. Shown once a use case has 3 ratings. Independent of the score |
| unknowns[] | What cannot be determined from an IP alone — see below. Always pass these on |
| reportUrl | The human-readable report. Cite it with any conclusion |
| lang / untranslated | English responses carry lang = en. untranslated lists any deduction ids that had no English text (normally empty) |
Limits of the data
What an IP check cannot tell you
Conclusions reflect public and partner data at query time. They are not a guarantee of account safety or platform access.
Each platform's internal risk labels
Platforms keep their own IP reputation data and ban records and do not publish them. No hit in public sources does not mean a platform has nothing on this IP.
Whether the IP is truly exclusive
Sharing is only the number of devices observed in the same subnet. It cannot show who else is using this exit right now, or what they are doing.
Which accounts this IP has been tied to
Account-to-IP links exist only inside each platform. Accounts previously banned on this IP are invisible to us.
Device, browser and behaviour
Risk systems look at IP + device fingerprint + behavioural rhythm together. A clean IP with an odd fingerprint or behaviour still gets blocked.
Whether the account profile matches the IP
When sign-up country, phone number, payment method, language or time zone disagree with the IP's location, platforms verify further. That is unrelated to IP purity.
Examples
Three ways to call it
curl
curl -s "https://ipure.dev/en/ip/8.8.8.8.json" | jq '.risk.purity, .risk.verdict, [.scenarios[] | {id, score, levelLabel}]'
curl -s "https://ipure.dev/en/ip/8.8.8.8.txt" # plain text, no parsingPython
import requests
r = requests.get("https://ipure.dev/en/ip/8.8.8.8.json", timeout=30)
r.raise_for_status()
report = r.json()
print(report["risk"]["purity"], report["risk"]["verdict"])
if not report["scenarioApplicable"]:
print(report["scenarioNote"])
for s in report["scenarios"]:
print(s["label"], s["score"], s["levelLabel"], "-", s["reason"])
print("Cannot be determined:", [u["label"] for u in report["unknowns"]])TypeScript
const res = await fetch("https://ipure.dev/en/ip/8.8.8.8.json");
if (!res.ok) throw new Error(`IPure ${res.status}`);
const report = await res.json();
const ai = report.scenarios.find((s: { id: string }) => s.id === "ai");
console.log(report.risk.purity, ai?.levelLabel, report.vpnOperator?.name ?? "not a known commercial VPN");Terminal (reports are in Chinese unless you add lang=en)
curl -L "ipure.dev/cli/8.8.8.8?lang=en&color=0"For agents
When to call it, and when not to
Good fit
“Is this IP clean?” “Can I use this proxy for ChatGPT / signing up on X / an Amazon seller account / Netflix?” “Is it a VPN or data center IP?” “Why do I keep getting flagged?” Also: checking your own egress IP before a network task, or comparing several candidate IPs.
Not a fit
DNS configuration, deployment, connectivity troubleshooting, or anything that needs a platform's internal risk data. When quoting a conclusion, pass on unknowns too, and never turn “no risk signals found” into “safe”.