Everything you need to integrate OpenIPApi into your application. Base URL: https://api.openipapi.com (versioned as /v1).
threat_score and VPN/proxy/Tor flags are inputs to your decision logic. Combine them with account, payment, device and behavior signals — never use them as the sole basis for blocking. See Data Methodology for details.
All API requests must include your API key in the X-API-Key request header. You can find your API key in the console.
X-API-Key: oip_your_api_key_here
All API requests are made to the following base URL over HTTPS:
https://api.openipapi.com
Machine-readable spec (OpenAPI 3.1): /openapi.yaml.
All responses are JSON. Successful responses return HTTP 200.
Error responses return an appropriate 4xx or 5xx status code with an error field.
{
"error": "invalid_api_key",
"message": "The provided API key is invalid or has been revoked.",
"status": 401
}
Look up geolocation, network, and threat data for a single IP address.
https://api.openipapi.com/v1/lookup/{ip}
| Parameter | Type | Description |
|---|---|---|
| ip | path | IPv4 or IPv6 address to look up. |
| fields | query (optional) | Comma-separated list of top-level fields to return: geo,network,threat,abuse. |
| date | query (optional) | Historical lookup — return the snapshot for this IP on the given date (YYYY-MM-DD). Up to 365 days back. Requires Pro+. Returns a no_snapshot error if no data is available for that date. |
| Field | Description | Plan |
|---|---|---|
| ip | The queried IP address. | All |
| type | IPv4 or IPv6. | All |
| geo.country_code | 2-letter ISO 3166-1 country code. | All |
| geo.country | Full country name. | All |
| geo.region | Region / state name. | All |
| geo.region_code | Region / state code. | All |
| geo.city | City name. | All |
| geo.postal_code | Postal / ZIP code. | All |
| geo.latitude | Latitude (decimal degrees). | All |
| geo.longitude | Longitude (decimal degrees). | All |
| geo.timezone | IANA timezone identifier (e.g. Europe/Berlin). | All |
| geo.is_sanctioned | True if the country is on OFAC, EU, or UN sanction lists. | All |
| geo.sanction_lists | Array of applicable sanction programs, e.g. ["OFAC","EU"]. | All |
| network.asn | Autonomous System Number. | Starter+ |
| network.as_name | AS organisation name. | Starter+ |
| network.as_domain | AS organisation domain. | Starter+ |
| network.isp | Internet Service Provider name. | All |
| network.organization | Network organisation name. | All |
| network.connection_type | residential, datacenter, mobile, education, or government. | All |
| threat.is_vpn | True if the IP is a known VPN endpoint. | Pro+ |
| threat.is_proxy | True if the IP is a known open proxy. | Pro+ |
| threat.is_tor | True if the IP is a Tor exit node. | Pro+ |
| threat.is_relay | True if the IP is an anonymous relay. | Pro+ |
| threat.is_hosting | True if the IP belongs to a hosting provider. | Pro+ |
| threat.threat_score | Composite risk score 0–100. A signal, not a final verdict — combine with account, payment, device and behavior data. | Pro+ |
| threat.threat_categories | Array of threat category strings. | Pro+ |
| threat.bot_type | Verified bot identifier (e.g. "googlebot", "bingbot") or null. Verified via reverse-DNS + forward confirmation. | All |
| abuse.contact_email | Abuse contact email from WHOIS data. | Pro+ |
$ curl https://api.openipapi.com/v1/lookup/185.220.101.45 \
-H "X-API-Key: oip_your_api_key_here"
{
"ip": "185.220.101.45",
"type": "IPv4",
"geo": {
"country_code": "DE",
"country": "Germany",
"region": "Hessen",
"region_code": "HE",
"city": "Frankfurt am Main",
"postal_code": "60313",
"latitude": 50.1109,
"longitude": 8.6821,
"timezone": "Europe/Berlin",
"is_sanctioned": false,
"sanction_lists": []
},
"network": {
"asn": 205100,
"as_name": "F3 Netze e.V.",
"as_domain": "f3netze.de",
"isp": "F3 Netze",
"organization": "F3 Netze e.V.",
"connection_type": "datacenter"
},
"threat": {
"is_vpn": true,
"is_proxy": false,
"is_tor": true,
"is_relay": false,
"is_hosting": true,
"threat_score": 85,
"threat_categories": ["tor_exit_node", "known_abuser"],
"bot_type": null
},
"abuse": {
"contact_email": "abuse@f3netze.de"
}
}
Look up multiple IP addresses in a single request. Batch size limits depend on your plan.
https://api.openipapi.com/v1/lookup/batch
plan_required error. | Plan | Max IPs per request |
|---|---|
| Starter | 100 |
| Pro | 500 |
| Business | 1,000 |
| Enterprise | 5,000 |
{
"ips": [
"185.220.101.45",
"8.8.8.8",
"1.1.1.1"
]
}
Returns a wrapped array of full lookup objects, in the same order as the request.
{
"results": [
{ "ip": "185.220.101.45", /* full lookup object */ },
{ "ip": "8.8.8.8", /* full lookup object */ },
{ "ip": "1.1.1.1", /* full lookup object */ }
],
"count": 3
}
Returns geolocation and threat data for the IP address making the request. Useful for "What is my IP?" features.
https://api.openipapi.com/v1/me
The response is identical to GET /v1/lookup/{ip} with the caller's IP pre-filled.
$ curl https://api.openipapi.com/v1/me \
-H "X-API-Key: oip_your_api_key_here"
Retrieve detailed information about an Autonomous System Number. Requires Starter plan or higher.
https://api.openipapi.com/v1/asn/{asn}
The asn parameter accepts both 13335 and AS13335 formats.
$ curl https://api.openipapi.com/v1/asn/13335 \
-H "X-API-Key: oip_your_api_key_here"
{
"asn": 13335,
"as_name": "Cloudflare, Inc.",
"as_domain": "cloudflare.com",
"organization": "Cloudflare, Inc.",
"country_code": "US",
"ip_ranges_count": 1284,
"total_ips": 2359296
}
Validate an IP address and determine its type. Does not consume lookup quota.
https://api.openipapi.com/v1/validate/{ip}
{
"ip": "185.220.101.45",
"valid": true,
"type": "IPv4",
"private": false,
"bogon": false
}
Composite fraud score (0–100) with risk level and actionable recommendation. Combines VPN, proxy, Tor, datacenter, and historical-abuse signals. Starter plan or above
https://api.openipapi.com/v1/fraud/{ip}
{
"ip": "185.220.101.45",
"fraud_score": 92,
"risk_level": "critical",
"signals": {
"is_vpn": true,
"is_proxy": false,
"is_tor": true,
"is_datacenter": true,
"is_known_attacker": true,
"high_risk_country": false
},
"recommendation": "block"
}
| Score | Risk level | Recommendation | Typical action |
|---|---|---|---|
| 0–30 | low | allow | Let the request through |
| 31–60 | medium | review | Log for manual review |
| 61–85 | high | challenge | Require CAPTCHA / 2FA / step-up auth |
| 86–100 | critical | block | Reject and alert |
Real-time network probe data from our 60+ active probing nodes: open TCP ports, service banners, reverse DNS, full TLS certificate detail (subject, issuer, SANs, validity), and detected service categories. Pro plan or above
https://api.openipapi.com/v1/probe/{ip}
"stale": true.{
"ip": "185.220.101.45",
"reachable": true,
"last_probed": "2026-04-18T14:23:11+00:00",
"age_hours": 2.3,
"stale": false,
"refresh_queued": false,
"probe_count_24h": 14,
"probed_from_nodes": 8,
"open_ports": [
{ "port": 22, "service": "ssh", "banner": "SSH-2.0-OpenSSH_8.9p1" },
{ "port": 80, "service": "http", "banner": "nginx/1.24.0" },
{ "port": 443, "service": "https", "banner": null },
{ "port": 9001, "service": "tor-relay", "banner": null }
],
"reverse_dns": "tor-exit.f3netze.de",
"tls": {
"subject_cn": "*.f3netze.de",
"issuer": "Let's Encrypt",
"valid_from": "2026-02-10T00:00:00Z",
"valid_to": "2026-05-10T00:00:00Z",
"sans": ["f3netze.de", "*.f3netze.de"],
"fingerprint": "7e:4f:...:b2:e1"
},
"banners": {
"22": "SSH-2.0-OpenSSH_8.9p1",
"80": "nginx/1.24.0"
},
"detected_services": {
"is_tor_relay": true,
"is_ssh_open": true,
"is_web_server": true,
"is_vpn": false,
"is_proxy": false
}
}
22 (ssh), 80 / 8080 (http), 443 / 8443 (https), 1080 (socks5), 3128 / 8888 (http-proxy), 9001 / 9030 (tor-relay / tor-dir), 9050 / 9150 (tor-socks), 1194 (openvpn), 4500 (ipsec-nat-t), 51820 (wireguard).
Identifies whether an IP belongs to a known residential-proxy pool, commercial VPN, or datacenter range. Unlike generic is_proxy flags, this endpoint attributes the IP to the specific provider (Bright Data, Oxylabs, NordVPN, etc.) — critical for fraud teams that need to allow some providers and block others. Proxy Intel add-on
https://api.openipapi.com/v1/proxy-attribution/{ip}
{
"ip": "2.56.16.42",
"detected": true,
"primary_provider": {
"provider": "unknown_vpn",
"display_name": "Unknown VPN",
"network_type": "vpn",
"confidence": 0.75
},
"networks": [
{
"provider": "unknown_vpn",
"network_type": "vpn",
"confidence": 0.75,
"source": "x4bnet-vpn",
"cidr": "2.56.16.0/22"
},
{
"provider": "unknown_datacenter",
"network_type": "datacenter",
"confidence": 0.75,
"source": "x4bnet-datacenter"
}
],
"recommendation": "treat_as_vpn"
}
residential — residential proxy pools (Bright Data, Oxylabs, Smartproxy, IPRoyal, Hola, Honeygain, EarnApp, Peer2Profit, SOAX)vpn — commercial VPN providers (NordVPN, ExpressVPN, Surfshark, Mullvad, ProtonVPN, PIA, IPVanish, CyberGhost)datacenter — hosting providers frequently used for commercial proxy exit (AWS, DigitalOcean, OVH, Hetzner, etc.)tor — Tor exit / relay nodesmobile — mobile carrier proxy pools| Recommendation | Meaning |
|---|---|
| no_proxy_detected | IP not matched against any known pool — treat as normal traffic. |
| treat_as_commercial_proxy | IP is in a residential proxy pool — likely malicious on non-scraping sites. |
| treat_as_vpn | IP is in a commercial VPN range — apply VPN policy. |
| treat_as_datacenter | IP is from a datacenter range — not a real residential user. |
| block_or_challenge | Tor exit — block or require strong authentication. |
Returns current billing period usage statistics for your account.
https://api.openipapi.com/v1/account/usage
{
"plan": "Pro",
"period_start": "2026-03-01",
"period_end": "2026-03-31",
"lookups_used": 184320,
"lookups_limit": 500000,
"lookups_remaining": 315680,
"reset_at": "2026-04-01T00:00:00Z"
}
Lists MMDB databases available for your plan. Drop-in MaxMind .mmdb replacements for offline / edge use — works with any MaxMind DB reader (PHP, Go, Python, Node.js, Rust, Java).
https://api.openipapi.com/v1/database/list
{
"plan": "Business",
"sources": [
{
"source": "country",
"filename": "geo-whois-asn-country.mmdb",
"size_bytes": 8225621,
"updated_at": "2026-04-18T02:00:00+00:00",
"etag": "\"fa2b851f9155838b\"",
"download_url": "https://api.openipapi.com/v1/database/download/country"
}
]
}
Streams the raw MMDB file. Supports ETag +
If-None-Match for conditional GETs (304 Not Modified when the file is unchanged). Rate limit: 10 downloads per source per day.
https://api.openipapi.com/v1/database/download/{source}
| Source | Contents | License | Required Plan |
|---|---|---|---|
| country | Country code + ASN | CC0 | Pro |
| asn | ASN + organization | CC0 | Business |
| city-geolite2 | City, region, lat/lon, timezone (GeoLite2) | MaxMind GeoLite2 EULA | Business |
| city-dbip | City (alternative to GeoLite2) | DB-IP Lite | Enterprise |
# Download latest MMDB
$ curl -H "X-API-Key: YOUR_KEY" \
-o asn.mmdb \
https://api.openipapi.com/v1/database/download/asn
# Use with maxmind-db/reader
$ composer require maxmind-db/reader
<?php
use MaxMind\Db\Reader;
$reader = new Reader('asn.mmdb');
$record = $reader->get('8.8.8.8');
// ['autonomous_system_number' => 15169,
// 'autonomous_system_organization' => 'Google LLC']
Every /v1/lookup response includes two fields in the geo block that tell you whether the IP's country is subject to international sanctions.
| Field | Description |
|---|---|
| geo.is_sanctioned | true if the country appears on any monitored sanction list; false otherwise. |
| geo.sanction_lists | Array of sanction program identifiers that apply: OFAC, EU, UN, UK. Empty array [] when not sanctioned. |
Covered sanction lists: OFAC (US), EU, UN Security Council, UK OFSI. Countries currently flagged include Iran, North Korea, Russia, Belarus, Syria, Cuba, Venezuela, Sudan, and others. The list is maintained and updated as programs change.
"geo": {
"country_code": "IR",
"country": "Iran",
...
"is_sanctioned": true,
"sanction_lists": ["OFAC", "EU", "UN"]
}
OpenIPApi performs double-reverse DNS verification to identify legitimate crawlers. The result is available in threat.bot_type on every lookup.
| Field | Values |
|---|---|
| threat.bot_type |
null (not a verified bot) or one of: googlebot,
google_special_crawl,
bingbot,
applebot,
yandexbot,
duckduckbot,
facebookbot,
semrushbot,
ahrefsbot,
mj12bot
|
Verification method: (1) PTR lookup on the IP → hostname. (2) Hostname must match the bot's domain pattern (e.g. *.googlebot.com). (3) Forward DNS on the hostname must resolve back to the original IP. Results are cached for 24 hours.
"threat": {
"is_vpn": false,
"is_proxy": false,
"is_tor": false,
"threat_score": 0,
"bot_type": "googlebot"
}
Add a ?date=YYYY-MM-DD parameter to any single IP lookup to retrieve the snapshot from that date.
https://api.openipapi.com/v1/lookup/{ip}?date=2026-01-15
$ curl "https://api.openipapi.com/v1/lookup/185.220.101.45?date=2026-01-15" \
-H "X-API-Key: oip_your_api_key_here"
{
"ip": "185.220.101.45",
"snapshot_date": "2026-01-15",
"is_historical": true,
"data": {
/* standard lookup response */
}
}
{
"error": "No historical snapshot available for this IP on 2026-01-15",
"code": "no_snapshot"
}
| Code | HTTP status | Description |
|---|---|---|
| invalid_api_key | 401 | The API key is missing, malformed, or has been revoked. |
| limit_exceeded | 429 | You have used all lookups in your current billing period. |
| plan_required | 403 | The requested feature is not available on your current plan. |
| invalid_ip | 400 | The provided IP address is not a valid IPv4 or IPv6 address. |
| not_found | 404 | No data found for the requested resource (e.g., unknown ASN). |
| no_snapshot | 404 | No historical snapshot exists for this IP on the requested date. |
| date_out_of_range | 400 | Historical lookup date is more than 365 days in the past. |
| invalid_date | 400 | The date parameter is missing, malformed, or in the future. |
| plan_upgrade_required | 403 | The requested feature requires a higher plan tier. |
| rate_limited | 429 | Too many requests in a short window. Back off and retry. |
| internal_error | 500 | An unexpected server error occurred. Contact support if this persists. |
In addition to monthly lookup quotas, requests are rate limited per API key using a sliding window. When exceeded, the API returns HTTP 429 with a Retry-After header.
| Plan | Requests / minute | Lookups / month |
|---|---|---|
| Free | 30 | 10,000 |
| Starter | 120 | 100,000 |
| Pro | 300 | 500,000 |
| Business | 600 | 2,000,000 |
| Enterprise | Custom | Unlimited |
Additional limits: /v1/validate (unauthenticated) caps at 60 req/min per caller IP; /v1/me at 30 req/min. MMDB downloads are capped at 10/day per source per account. Rate-limit responses include Retry-After.
Webhooks let you receive real-time HTTP POST notifications when the status of an IP you're watching changes. Configure them in the console → Webhooks, or through the dashboard UI — no API call required.
| Event | Fires when |
|---|---|
| vpn_detected | A watched IP is newly detected as a VPN endpoint. |
| tor_detected | A watched IP appears on a Tor exit node list or probed as a Tor relay. |
| proxy_detected | A watched IP is detected as an open or SOCKS proxy. |
| high_threat | Threat score crosses your configured threshold (50–95). |
POST https://your-endpoint.example.com/webhook
Content-Type: application/json
User-Agent: OpenIPApi-Webhook/1.0
X-OpenIPApi-Event: high_threat
X-OpenIPApi-Signature: sha256=8c7f1a...b2e
{
"event": "high_threat",
"delivered_at": "2026-04-18T12:34:56Z",
"webhook_id": 142,
"data": {
"ip": "185.220.101.45",
"threat_score": 92,
"previous_score": 45,
"is_tor": true,
"is_vpn": true
}
}
Every webhook is signed using HMAC-SHA256 of the raw JSON body with your webhook secret. The signature is sent in the X-OpenIPApi-Signature header as sha256=<hex>.
Always verify before acting on the payload:
PHP
$body = file_get_contents('php://input');
$hdr = $_SERVER['HTTP_X_OPENIPAPI_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac(
'sha256', $body, $webhookSecret
);
if (!hash_equals($expected, $hdr)) {
http_response_code(401);
exit;
}
// Safe to process $body now
Node.js (Express)
const crypto = require('crypto');
app.post('/webhook',
express.raw({ type: 'application/json' }),
(req, res) => {
const hdr = req.headers['x-openipapi-signature'];
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(req.body).digest('hex');
if (expected !== hdr) return res.status(401).end();
// Process JSON.parse(req.body)
});
Your endpoint must respond within 10 seconds with HTTP 2xx to acknowledge receipt. Any other status (or timeout) triggers exponential back-off retries: 1 min, 5 min, 30 min, 2 h, 12 h. After the final attempt the webhook is marked as failed and a dashboard alert is raised.
The console → Webhooks keeps the last 100 delivery attempts per account, showing the event type, HTTP status code, and the response body returned by your endpoint. You can replay any previous delivery to re-send the original signed payload, and use the built-in Signature Debugger to verify HMAC-SHA256 signatures client-side without sharing your secret with any server.
Replayed deliveries include an additional X-OpenIPApi-Replay: 1 header so your endpoint can distinguish them from live events.
$ curl https://api.openipapi.com/v1/lookup/8.8.8.8 \
-H "X-API-Key: oip_your_api_key_here"
$ curl -X POST https://api.openipapi.com/v1/lookup/batch \
-H "X-API-Key: oip_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"ips": ["8.8.8.8", "1.1.1.1", "185.220.101.45"]}'
$ curl https://api.openipapi.com/v1/me \
-H "X-API-Key: oip_your_api_key_here"
const response = await fetch('https://api.openipapi.com/v1/lookup/8.8.8.8', {
headers: {
'X-API-Key': 'oip_your_api_key_here'
}
});
const data = await response.json();
console.log(data.geo.country); // "United States"
console.log(data.threat.is_vpn); // false
console.log(data.threat.threat_score); // 0
const response = await fetch('https://api.openipapi.com/v1/lookup/batch', {
method: 'POST',
headers: {
'X-API-Key': 'oip_your_api_key_here',
'Content-Type': 'application/json'
},
body: JSON.stringify({
ips: ['8.8.8.8', '1.1.1.1', '185.220.101.45']
})
});
const { results } = await response.json();
for (const info of results) {
console.log(info.ip, info.geo.city, info.threat.threat_score);
}
import requests
API_KEY = "oip_your_api_key_here"
headers = {"X-API-Key": API_KEY}
r = requests.get(
"https://api.openipapi.com/v1/lookup/8.8.8.8",
headers=headers
)
r.raise_for_status()
data = r.json()
print(data["geo"]["country"]) # United States
print(data["threat"]["is_vpn"]) # False
print(data["threat"]["threat_score"]) # 0
import requests
API_KEY = "oip_your_api_key_here"
headers = {
"X-API-Key": API_KEY,
"Content-Type": "application/json"
}
payload = {
"ips": ["8.8.8.8", "1.1.1.1", "185.220.101.45"]
}
r = requests.post(
"https://api.openipapi.com/v1/lookup/batch",
json=payload,
headers=headers
)
r.raise_for_status()
for info in r.json()["results"]:
print(info["ip"], info["geo"]["city"], info["threat"]["threat_score"])