Copied!
API

IPWhois.net API documentation

One key for three JSON APIs: IP geolocation and network data, IP blacklist status and MAC vendor lookup. Plain HTTPS requests, CORS enabled, no SDK needed. The same databases that power this site, read with index-only queries, so a lookup takes a few milliseconds.

  • 1,000 free requests a day per API
  • IPv4 and IPv6
  • Batch lookups on paid plans
  • No external calls

Overview

Base URL: https://ipwhois.net/api/v1. Every endpoint returns JSON with Content-Type: application/json. Add ?pretty to any request for indented output. All responses carry Access-Control-Allow-Origin: *, so the API can be called from browser code (restrict such keys to your websites, see Authentication).

EndpointWhat it doesPlanCounted as
GET /ip/{ip}IP data: location, timezone, ASN, ISP and moreAll1 IP lookup
GET /ipThe same for the IP the request comes fromAll1 IP lookup
POST /ip/batchMany IPs in one requestPaid1 per IP
GET /blacklist/{ip}Blacklist status, threat type and confidenceAll1 blacklist check
POST /blacklist/batchMany IPs in one requestPaid1 per IP
GET /mac/{mac}MAC vendor and address flagsAll1 MAC lookup
GET /mac/search?q=OUI prefixes registered to a companyPaid1 MAC lookup
GET /usagePlan and quota of the keyAllNot counted

A request to the base URL without a path returns this list as JSON.

Authentication

Every endpoint needs an API key. Create a free account and open the dashboard: the first key is created for you. Keys look like ipw_ followed by 40 hex characters. Send the key in one of three ways, checked in this order:

Three ways to send the key
# Header (recommended)
curl https://ipwhois.net/api/v1/ip/8.8.8.8 -H "X-API-Key: YOUR_API_KEY"

# Bearer token
curl https://ipwhois.net/api/v1/ip/8.8.8.8 -H "Authorization: Bearer YOUR_API_KEY"

# Query string (for quick tests; keys end up in logs this way)
curl "https://ipwhois.net/api/v1/ip/8.8.8.8?key=YOUR_API_KEY"

A request without a key gets HTTP 401 missing_key; a malformed or revoked key gets 401 invalid_key.

Keys used in browser code

A key that ships in JavaScript is visible to everyone. On the dashboard, edit the key and list the websites it may be used from (example.com, *.example.com). The API then accepts the key only when the request's Origin or Referer is on one of those hosts; everything else gets HTTP 403 origin_not_allowed. Keys without a website list work from anywhere, so keep them on the server.

You can roll a key from the dashboard at any time: a new secret replaces the old one instantly, name and website list stay.

Plans and quotas

The free plan gives each API its own daily allowance and returns the basic field set. A paid plan opens every field of all three APIs and pools one monthly quota across them: every lookup counts as one, whichever API it goes to, and a batch of 50 addresses counts as 50. Prices are shown without VAT; full details on the pricing page.

PlanPriceLookupsRate limitBatchKeysFields
Free
non-commercial
€0 1,000 IP, 1,000 blacklist and 1,000 MAC a day 2/s no 1 basic
Starter €9/mo 250,000 a month, all APIs 5/s 100 3 full
Pro €29/mo 1,500,000 a month, all APIs 15/s 100 5 full
Business €79/mo 6,000,000 a month, all APIs 30/s 1,000 10 full

Free quotas reset at 00:00 UTC. Monthly quotas reset on the first day of the month, 00:00 UTC. An expired paid plan falls back to the free plan; the keys keep working.

Rate limits and quota headers

Each plan has a per-second limit (see the table above). Requests over it are answered with HTTP 429 rate_limited and a Retry-After: 1 header. When the daily or monthly quota is used up, the API answers HTTP 429 quota_exceeded until the reset. Rejected requests are not counted against the quota, but they are shown as "rejected" on the dashboard and in /usage.

Every successful response carries these headers (also exposed to browser code through CORS):

Response headers
X-Plan: free
X-RateLimit-Limit: 2
X-Quota-Limit: 1000
X-Quota-Remaining: 999
X-Quota-Reset: 1790380800
HeaderMeaning
X-PlanPlan code of the key: free, starter, pro or business
X-RateLimit-LimitRequests per second allowed on the plan
X-Quota-LimitQuota of the current period: the daily allowance of this API on the free plan, the monthly pool on paid plans
X-Quota-RemainingLookups left in the period after this request
X-Quota-ResetUnix time (UTC) when the period resets

The same numbers are repeated in the meta object of every metered response, so you can read them without touching headers.

Response format

Successful responses have "success": true, the echoed input (ip or mac), the data grouped in objects, and a meta object with your plan, field level and quota:

meta, present on every metered response
"meta": {
  "plan": "free",
  "fields": "basic",
  "quota": {
    "limit": 1000,
    "used": 2,
    "remaining": 998,
    "period": "day",
    "resets_at": "2026-09-26T00:00:00+00:00"
  }
}

Fields with no data are null, never missing, so your code can rely on the shape. Booleans are real booleans, numbers are numbers.

Trimming the response with fields

Add ?fields= with a comma-separated list of paths to keep only what you need. Dots select nested keys. success, ip, mac, version and meta are always included; unknown paths are ignored. The filter also applies to each result of a batch.

GET /ip/1.1.1.1?fields=location.country,network.isp
{
  "success": true,
  "ip": "1.1.1.1",
  "version": 4,
  "location": { "country": "Australia" },
  "network": { "isp": "Cloudflare" },
  "meta": { "plan": "free", "fields": "basic", "quota": { "limit": 1000, "used": 3, "remaining": 997, "period": "day", "resets_at": "2026-09-26T00:00:00+00:00" } }
}

IP lookup

GET/api/v1/ip/{ip}all plans
GET/api/v1/ipall plans

Location, timezone and network data for any public IPv4 or IPv6 address. Without an address the API looks up the IP the request comes from, which is handy for "what is my IP" features. Coordinates come with an accuracy radius; the geolocation reflects where the network block is registered and used, not the physical position of one device.

GET /ip/8.8.8.8 on the free plan (basic fields)
{
  "success": true,
  "ip": "8.8.8.8",
  "version": 4,
  "location": {
    "continent": "North America",
    "continent_code": "NA",
    "country": "United States",
    "country_code": "US",
    "region": "California",
    "city": "Mountain View",
    "postal_code": "94035",
    "latitude": 37.38605,
    "longitude": -122.08385,
    "accuracy_radius_km": 1000,
    "is_eu": false
  },
  "timezone": { "id": "America/Los_Angeles" },
  "network": {
    "asn": 15169,
    "isp": "Google Cloud",
    "organization": "Google LLC",
    "domain": "cloud.google.com",
    "connection_type": "hosting"
  },
  "meta": {
    "plan": "free",
    "fields": "basic",
    "quota": { "limit": 1000, "used": 2, "remaining": 998, "period": "day", "resets_at": "2026-09-26T00:00:00+00:00" }
  }
}

Paid plans return the same objects with more keys plus country_info, security and abuse:

GET /ip/8.8.8.8 on a paid plan (full fields, meta omitted)
{
  "success": true,
  "ip": "8.8.8.8",
  "version": 4,
  "location": {
    "continent": "North America",
    "continent_code": "NA",
    "country": "United States",
    "country_code": "US",
    "region": "California",
    "city": "Mountain View",
    "postal_code": "94035",
    "latitude": 37.38605,
    "longitude": -122.08385,
    "accuracy_radius_km": 1000,
    "is_eu": false
  },
  "timezone": {
    "id": "America/Los_Angeles",
    "utc_offset": "-07:00",
    "is_dst": true,
    "current_time": "2026-09-25T04:14:29-07:00"
  },
  "network": {
    "asn": 15169,
    "isp": "Google Cloud",
    "organization": "Google LLC",
    "domain": "cloud.google.com",
    "connection_type": "hosting",
    "asn_name": "GOOGLE",
    "org_domain": "cloud.google.com",
    "holding": "Alphabet Inc.",
    "holding_domain": "abc.xyz",
    "usage": "Google Public DNS",
    "hostname": "dns.google",
    "range": { "start": "8.8.8.0", "end": "8.8.8.255", "size": 256 },
    "rpki": "valid",
    "speed_mbps": { "download": 100000, "upload": 100000 }
  },
  "country_info": {
    "calling_code": "+1",
    "tld": ".us",
    "currency": { "code": "USD", "symbol": "$" },
    "languages": ["en"]
  },
  "security": {
    "is_vpn": false,
    "vpn_provider": null,
    "is_proxy": false,
    "is_tor": false,
    "is_hosting": true,
    "is_threat": false,
    "threat_type": null,
    "threat_confidence": 0,
    "threat_reports": 0,
    "threat_last_seen": null
  },
  "abuse": { "email": "[email protected]" }
}

Fields

FieldTypePlanDescription
ipstringAllThe address, IPv6 in compressed form
version4 | 6AllIP version
location.continent, continent_codestringAllContinent name and two-letter code
location.country, country_codestringAllCountry name and ISO 3166-1 alpha-2 code
location.region, city, postal_codestringAllRegion, city and postal code where known
location.latitude, longitudenumberAllCoordinates of the estimated location
location.accuracy_radius_kmnumberAllRadius around the coordinates that the location is expected to fall in
location.is_eubooleanAllCountry is an EU member
location.address, address_typestringPaidStreet address of the datacenter or office, with datacenter or office; only present when known
timezone.idstringAllIANA timezone, e.g. Europe/Berlin
timezone.utc_offset, is_dst, current_timestring, boolean, stringPaidCurrent UTC offset, daylight saving flag and local time (ISO 8601)
network.asnnumberAllAutonomous system number announcing the address
network.isp, organization, domainstringAllService provider, registered organisation and its website domain
network.connection_typestringAllresidential, cable, mobile, hosting, business, backbone, education, research, government, military, financial, healthcare, media, satellite, fixed_wireless or ixp. VPN ranges report hosting.
network.asn_namestringPaidRegistered name of the AS
network.org_domainstringPaidDomain of the organisation
network.holding, holding_domainstringPaidParent company that owns the ISP, when it is part of a group
network.usagestringPaidWhat the range is used for, e.g. Google Public DNS, FTTH customers, mobile
network.hostnamestringPaidReverse DNS name (PTR) of the address
network.rangeobjectPaidThe block the address belongs to: start, end and size for IPv4, start, end and prefix for IPv6
network.rpkistringPaidvalid, invalid, unrouted or not_found (IPv4 only, null for IPv6)
network.speed_mbpsobjectPaidAverage measured download and upload speed of the ISP, null when unknown
country_infoobjectPaidcalling_code, tld, currency.code, currency.symbol and languages of the country
security.is_vpn, vpn_providerboolean, stringPaidAddress belongs to a known VPN service, and which one
security.is_proxy, is_tor, is_hostingbooleanPaidOpen proxy, Tor exit node, datacenter or VPN range
security.is_threat, threat_type, threat_confidence, threat_reports, threat_last_seenmixedPaidListed in the IPWhois.net blacklist, with threat type, confidence 0 to 100, number of reports and time of the last one
abuse.emailstringPaidAbuse contact of the network (IPv4)

Private, reserved and unknown addresses

Private (RFC 1918), loopback, link-local, carrier-grade NAT, multicast, documentation and other reserved space is answered without a database lookup. The response is still counted and has a different shape:

GET /ip/192.168.1.1
{
  "success": true,
  "ip": "192.168.1.1",
  "version": 4,
  "reserved": true,
  "reserved_type": "private",
  "description": "Private network (RFC 1918)",
  "security": { "is_private": true, "is_cgnat": false }
}

reserved_type is one of private, loopback, link_local, cgnat, multicast, documentation, benchmark or reserved. A public address that is not in any database returns "found": false with a message. IPv6 addresses get country and network data from our own IPv6 table and city-level location from GeoLite2; city fields are null when no city is known.

Batch IP lookup

POST/api/v1/ip/batchpaid plans

Send up to 100 addresses per request on Starter and Pro, 1,000 on Business (the exact number for your plan is in the table above). The body is JSON: {"ips": ["8.8.8.8", "1.1.1.1"]}. A plain JSON array or a form field ips=8.8.8.8,1.1.1.1 is accepted as well. Duplicates are removed. Each address is counted as one lookup and every result has the full field set. The fields filter applies to every result.

Request and response
curl -X POST https://ipwhois.net/api/v1/ip/batch \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ips": ["8.8.8.8", "1.1.1.1", "not-an-ip"]}'

{
  "success": true,
  "count": 3,
  "results": [
    { "success": true, "ip": "8.8.8.8", "version": 4, "location": { ... }, "timezone": { ... }, "network": { ... }, "security": { ... } },
    { "success": true, "ip": "1.1.1.1", "version": 4, "location": { ... }, "timezone": { ... }, "network": { ... }, "security": { ... } },
    { "success": false, "ip": "not-an-ip", "error": { "code": "invalid_ip", "message": "Not a valid IPv4 or IPv6 address." } }
  ],
  "meta": { "plan": "pro", "fields": "full", "quota": { "limit": 1500000, "used": 3, "remaining": 1499997, "period": "month", "resets_at": "2026-10-01T00:00:00+00:00" } }
}

Invalid addresses fail inside results and do not fail the request. A batch larger than the plan allows is rejected as a whole with HTTP 400 batch_too_large; on the free plan the endpoint answers HTTP 402 plan_required. A GET without addresses answers 405 method_not_allowed.

Blacklist check

GET/api/v1/blacklist/{ip}all plans
POST/api/v1/blacklist/batchpaid plans

Tells you whether an address is in the IPWhois.net blacklist, a database of addresses reported for brute-force attacks, spam, scanning and other abuse by Fail2Ban, CSF and manual reports. This is our own list; it does not query external DNSBLs.

GET /blacklist/193.46.255.86 (free) and a clean address
{
  "success": true,
  "ip": "193.46.255.86",
  "listed": true,
  "threat_type": "brute-force",
  "confidence": 85,
  "last_seen": "2026-09-25 10:47:05",
  "meta": { ... }
}

{ "success": true, "ip": "8.8.8.8", "listed": false, "meta": { ... } }

Paid plans add the report history:

Paid fields (reports trimmed)
{
  "success": true,
  "ip": "193.46.255.86",
  "listed": true,
  "threat_type": "brute-force",
  "confidence": 85,
  "last_seen": "2026-09-25 10:47:05",
  "total_reports": 5,
  "distinct_reporters": 3,
  "first_seen": "2026-03-28 21:27:52",
  "source": "multiple",
  "country_code": "GB",
  "isp": "Unmanaged Ltd",
  "reports": [
    { "threat_type": "brute-force", "message": "Fail2Ban sshd ban", "source": "fail2ban", "created_at": "2026-08-25 03:00:15" },
    { "threat_type": "brute-force", "message": "Fail2Ban sshd ban", "source": "fail2ban", "created_at": "2026-04-02 18:34:15" }
  ]
}
FieldTypePlanDescription
listedbooleanAllAddress is in the blacklist. When false, no other fields follow.
threat_typestringAllMain category, e.g. brute-force, spam, scanner, web-attack
confidencenumberAll0 to 100, grows with the number and spread of reports
last_seenstringAllTime of the most recent report (UTC)
total_reports, distinct_reportersnumberPaidNumber of reports and of independent reporters
first_seen, sourcestringPaidFirst report and the source of the listing
country_code, ispstringPaidCountry and provider of the address at the time of listing
reportsarrayPaidUp to 20 most recent reports with threat_type, message, source and created_at

The batch endpoint takes the same body as the IP batch ({"ips": [...]}), has the same size limits and returns count, results and meta.

MAC vendor lookup

GET/api/v1/mac/{mac}all plans
GET/api/v1/mac/search?q={company}paid plans

Resolves a MAC address or OUI prefix to the registered manufacturer, using the IEEE MA-L, MA-M and MA-S registries (the most specific block wins). Any common format works: 00:1A:2B:3C:4D:5E, 00-1A-2B-3C-4D-5E, 001A.2B3C.4D5E or 001A2B. At least the first six hex digits are required.

GET /mac/00:1A:2B:3C:4D:5E on the free plan
{
  "success": true,
  "mac": "00:1A:2B:3C:4D:5E",
  "found": true,
  "oui": "00:1A:2B",
  "normalized": "00:1A:2B:3C:4D:5E",
  "is_multicast": false,
  "is_local": false,
  "is_randomized": false,
  "vendor": { "company": "Ayecom Technology Co., Ltd.", "country_code": "TW" },
  "meta": { ... }
}
Paid fields added to vendor, plus block and device
"vendor": {
  "company": "Ayecom Technology Co., Ltd.",
  "country_code": "TW",
  "short_name": "AyecomTechno",
  "website": "ayecom.com.tw",
  "country": "Taiwan",
  "address": "No. 25, R&D Road 2, Science-Based Industrial Park Hsinchu  TW 300",
  "industry": null,
  "founded": null,
  "headquarters": null,
  "total_ouis": 1
},
"block": { "type": "MA-L", "start": "001A2B000000", "end": "001A2BFFFFFF", "size": 16777216 },
"device": { "type": null, "interface": null, "is_virtual_machine": false, "vm_platform": null },
"registry_updated": "2015/11/17"
FieldTypePlanDescription
foundbooleanAllA vendor is registered for the prefix. When false, vendor is null.
ouistringAllFirst three bytes, colon separated
normalizedstringAllFull address as AA:BB:CC:DD:EE:FF, null when only a prefix was given
is_multicastbooleanAllGroup address (I/G bit set)
is_localbooleanAllLocally administered (U/L bit set), not assigned by a manufacturer
is_randomizedbooleanAllLocally administered unicast, the pattern phones use for MAC randomisation
vendor.company, country_codestringAllRegistered company and its country
vendor.short_name, website, country, addressstringPaidShort name, website, country name and registered address
vendor.industry, founded, headquarters, total_ouismixedPaidCompany details where known and the number of prefixes registered to it
blockobjectPaidRegistry block: type (MA-L, MA-M, MA-S), start, end, size
device.type, interfacestringPaidDevice and interface type where known
device.is_virtual_machine, vm_platformboolean, stringPaidPrefix belongs to VMware, VirtualBox, Hyper-V, QEMU/KVM, Parallels, Xen or Docker
registry_updatedstringPaidDate of the registry record

Search by company

/mac/search?q= (alias vendor=) lists the prefixes registered to companies whose name contains the query, at least two characters, up to 100 results ordered by company name. It counts as one MAC lookup.

GET /mac/search?q=Ayecom
{
  "success": true,
  "query": "Ayecom",
  "count": 1,
  "results": [
    { "oui": "00:1A:2B", "company": "Ayecom Technology Co., Ltd.", "country_code": "TW", "block_type": "MA-L" }
  ],
  "meta": { ... }
}

Usage and plan of a key

GET/api/v1/usagenot counted

Returns the plan, the per-second limit and today's counters of the account the key belongs to. Free accounts see the daily quota per API, paid accounts the monthly pool. This call is free and not rate limited by quota, so you can poll it from a dashboard.

GET /usage (free plan)
{
  "success": true,
  "plan": { "code": "free", "name": "Free", "expires_at": null },
  "rate_limit_per_second": 2,
  "today": { "ip": 7, "blacklist": 1, "mac": 3, "rejected": 5 },
  "daily_quota": { "ip": 1000, "blacklist": 1000, "mac": 1000 }
}

// paid plans replace daily_quota with
"monthly_quota": { "limit": 1500000, "used": 4821, "remaining": 1495179 }

Errors

Errors use HTTP status codes and a JSON body with "success": false and an error object. The code is stable and meant for your program; the message is for people and may change.

HTTP 429
{
  "success": false,
  "error": {
    "code": "rate_limited",
    "message": "Too many requests per second (limit 2/s on the Free plan)."
  }
}
HTTPCodeWhen
400invalid_ipNot a valid IPv4 or IPv6 address
400invalid_macFewer than 6 or more than 12 hex digits
400invalid_queryMAC search with fewer than 2 characters
400empty_batchBatch request without addresses
400batch_too_largeMore addresses than the plan allows per batch
400unavailableThe blacklist or MAC database is temporarily unavailable; retry later
401missing_keyNo API key in the request
401invalid_keyMalformed, unknown or revoked key
402plan_requiredBatch or MAC search on the free plan
403origin_not_allowedKey is restricted to websites and the request came from elsewhere
403account_suspendedThe account is suspended; contact support
404not_foundUnknown endpoint
405method_not_allowedBatch called with GET and no addresses
429rate_limitedPer-second limit exceeded; Retry-After: 1 is set
429quota_exceededDaily (free) or monthly (paid) quota used up; X-Quota-Reset tells when it resets

Inside a batch, a bad address produces the same error object on that single result while the request itself succeeds.

Code samples

Replace YOUR_API_KEY with a key from the dashboard. Each sample looks up 8.8.8.8 and prints the city and ISP.

curl
curl https://ipwhois.net/api/v1/ip/8.8.8.8 \
  -H "X-API-Key: YOUR_API_KEY"

# only two fields, indented
curl "https://ipwhois.net/api/v1/ip/8.8.8.8?fields=location.city,network.isp&pretty" \
  -H "X-API-Key: YOUR_API_KEY"

Legacy endpoints

The endpoints that existed before the v1 API keep working unchanged, so nothing you built on them breaks:

  • https://ipwhois.net/api/mac/v1/lookup.php?mac=00:1A:2B:3C:4D:5E (and ?vendor= for reverse search): 100 requests per day per IP without a key.
  • https://bl.ipwhois.net/api/check.php?ip=1.2.3.4: the original blacklist check, described in the Full Blacklist API documentation.

They have their own response formats and limits. New integrations should use the v1 API described on this page: one key, one response shape, quota headers and batch support.

Attribution and licence

On the free plan, attribution is required: show a visible credit such as "IP data by IPWhois.net" linking to https://ipwhois.net wherever you display data from the API. The free plan is for testing and personal projects; commercial use needs a paid plan, which has no attribution requirement.

The IPWhois.net database is built from regional registry data, BGP routing tables, provider geofeeds and our own measurements. It also includes GeoLite2 data created by MaxMind, available from maxmind.com, and the IP2Location LITE database from lite.ip2location.com. When you redistribute or display free-plan data, keep those credits together with ours.

Need higher volumes, a database licence or a custom limit? Contact us with your expected volume.

Questions we get a lot

Is there a free plan?
Yes. A free account gets one API key with 1,000 IP lookups, 1,000 blacklist checks and 1,000 MAC lookups per day, at 2 requests per second. The free plan returns the basic field set, is meant for testing and personal projects, and requires attribution.
How do I authenticate?
Send your key in the X-API-Key header, as a Bearer token in the Authorization header, or as the ?key= query parameter. Keys are created on the dashboard at ipwhois.net/account/api and start with ipw_.
What happens when I exceed a limit?
The API answers HTTP 429 with a JSON error. The code is rate_limited when you exceed the per-second limit (retry after one second) and quota_exceeded when the daily or monthly quota is used up. Nothing is billed on top. The free quota resets at 00:00 UTC, paid quotas reset on the first day of the month.
Do the old MAC and blacklist endpoints still work?
Yes. /api/mac/v1/lookup.php and bl.ipwhois.net/api/check.php keep working unchanged. New integrations should use the v1 API, which covers IP data, blacklist and MAC lookup with one key and one response format.