Most IP-risk vendors hand you a PDF and a support ticket. IP99 by ThreatHunter publishes an OpenAPI 3.1 document at a stable URL you can import the same way you import any other public API: Postman, Insomnia, openapi-generator, or a GPT Action. One file describes the free profile, the quota endpoint, and the professional Pulse path β so your clients, types, and agent tools stay in sync with the live service instead of a blog post you copied six months ago.
This is a practical import walkthrough. Every curl below runs without an API key. Live samples were captured on 2026-10-31 (UTC).
curl -s https://ip99.com/openapi.json | head -c 200
As of today the document advertises OpenAPI 3.1.0, title IP99 IP risk and geolocation API, version 1.0.0, server https://ip99.com, and three paths:
| Path | What it is |
|---|---|
GET /v1/ip/{ip} |
Free risk + geo + network profile (key optional) |
GET /v1/me |
Your current tier and remaining quota |
GET /pulse/v1/ip/{ip} |
Same score with per-evidence detail (professional key) |
Access-Control-Allow-Origin: * is set on the OpenAPI JSON itself, so browser-based importers and GPT Action validators can fetch it without a CORS proxy. Security schemes in the file are exactly what the human docs say: X-API-Key header or Authorization: Bearer, never a query string (?key= is rejected with HTTP 400 key_in_query).
The free endpoint is still the one you should try first:
curl https://ip99.com/v1/ip/8.8.8.8
Captured ~2026-10-31 13:05 UTC:
{
"ip": "8.8.8.8",
"computed_at": "2026-10-31T13:05:33Z",
"evidence_state": "none",
"geo": {
"continent": "NA",
"country": "US",
"region": "California",
"city": "Mountain View",
"district": "",
"tz": "America/Los_Angeles"
},
"network": { "asn": 15169, "usage_type": "IDC" },
"risk": {
"score": 0,
"level": "none",
"signals": ["hosting"]
}
}
hosting here is a network attribute, not a risk verdict. Score 0 with evidence_state: none means "no current verifiable evidence," not "safe." Official copy is blunt about that; your generated client should expose evidence_state as a first-class field, not bury it under "extra."
https://ip99.com/openapi.json. lookupIP (GET /v1/ip/{ip}), set ip to 8.8.8.8, send. No Authorization needed for anonymous quota. apiKey and map it to the bearer scheme when you create a free key in the console. Fields do Anonymous allowance today (from GET /v1/me): 500 lookups/day per client IP, 60/min, resetting at midnight Asia/Shanghai. Sign in and self-create a key for a higher daily bucket; overseas self-serve Pro / Pro+ buys volume, not a different score. Per-item evidence rows, share_tier, and ?at= history sit on Pulse and need a professional key.
Any OpenAPI 3.1 generator that understands 3.1 will work. Example with the official CLI (install once, then pin the URL):
npx @openapitools/openapi-generator-cli generate \
-i https://ip99.com/openapi.json \
-g typescript-fetch \
-o ./ip99-client
Or Python:
openapi-generator-cli generate \
-i https://ip99.com/openapi.json \
-g python \
-o ./ip99-py
What you should assert in a smoke test after generation:
evidence_state enum: active | stale | none. risk.score is documented (and treated in app code) as risk.signals is an array of slugs; hosting / mobile describe the network and are not, by themselves, a ban reason.{ "error": { "code", "message" } } β branch on error.code, never on the prose in message.
A second live call, using IP99's documented demo address (mask the last octet of real customer IPs in tickets):
curl https://ip99.com/v1/ip/27.22.66.200
Same window:
{
"ip": "27.22.66.200",
"computed_at": "2026-10-31T13:05:57Z",
"evidence_state": "active",
"geo": {
"country": "CN",
"region": "Hubei",
"city": "Xiangyang",
"district": "Yicheng",
"tz": "Asia/Shanghai"
},
"network": { "asn": 4134, "usage_type": "DYN" },
"risk": {
"score": 7,
"level": "low",
"signals": ["dialup_pool", "hijacked_proxy", "proxy"]
}
}
Residential usage_type: DYN plus proxy-class signals is exactly why "ban all IDC ASN ranges" fails: the interesting exits often sit on home broadband. Official triage order: read evidence_state first, then signals, then score/level β and prefer step-up verification over hard-banning a whole building when the address may be shared.
Custom GPTs / Actions that accept an OpenAPI URL can point at the same file. Practical checklist:
X-API-Key./v1/me) unless the agent truly has a professional Pulse key. Leaving Pulse in the Action without a key only produces 403 professional_required noise.computed_at alongside the score. A stale or empty evidence state with score 0 must not be summarized as "clean IP."
If you want agents without a custom GPT, IP99 also exposes the same free profile as an MCP tool ip_risk_lookup at https://ip99.com/mcp β same fields as /v1/ip/{ip}. That path is covered in an earlier post; the OpenAPI file remains the right source of truth for HTTP clients and codegen.
Every successful lookup can carry daily quota headers (X-Daily-Limit, X-Daily-Remaining, X-Daily-Reset). Prefer those (or GET /v1/me) over hard-coding numbers from an old article β allowances can change, and the response is authoritative for your tier.
Do not cache risk verdicts across sessions: the product thesis is that every answer is a judgement that expires. computed_at is part of what makes a decision auditable. Pulse's ?at= replay exists for chargeback-style "what did we know then?" questions; the free /v1/ip endpoint answers now only.
Also out of scope for this import guide (and for honest marketing): inventing customer logos, accuracy percentages, funding claims, or equating IP99's free profile with ThreatHunter's full enterprise datasets. IP99 is a ThreatHunter product; the free JSON you get from /v1/ip is exactly what the OpenAPI schema for that path describes β nothing more.
/v1/ip/8.8.8.8 with no key.
Human reference: https://ip99.com/api. Product home: https://ip99.com.