API reference

Versioned JSON lookups and the existing API with five output formats. Download the machine-readable contract for client generation.

Quickstart

Look up an address with a GET request. Sections we know nothing about are left out of the body rather than returned empty, so check for a key before reading into it.

curl
curl 'https://ip-api.dev/api?q=8.8.8.8&pretty=1&key=YOUR_KEY'

Versioned JSON API

The v1 routes always return JSON and share the existing authentication and daily quota rules. Send your key as Authorization: Bearer, or use the legacy key query parameter. The web host also permits keyless reduced IP lookups; the accelerator host requires a key.

FieldTypeDescription
/v1?q=8.8.8.8GETQuery form, accepting IPv4, IPv6, or an AS-prefixed ASN.
/v1/lookup/8.8.8.8GETExplicit IP lookup. URL-encode IPv6 path segments.
/v1/lookupGETSelf-IP lookup, using the trusted client connection address. This is an IP lookup, not account metadata.
/v1/asn/15169GETASN organization and announced IPv4/IPv6 prefixes. Use a decimal number without AS.
/v1/bulkPOSTOne to 1,000 inputs as {"ips": [...]} or one address per text/plain line. Requires a key; returns results in input order, including per-item errors.

OpenAPI 3.1 contract · Full response JSON Schema · Error JSON Schema

Missing sections are omitted; unresolved fields within known sections are null. Clients should ignore additional fields. Additive fields are compatible within v1; removing a field or changing its type, meaning, or null behavior requires a new URL version. Any deprecation will be announced here with Deprecation and Sunset response headers before retirement. No endpoints are deprecated in this release, and the unversioned routes retain their existing behavior.

All v1 endpoints require an API key and stop at the daily per-key plan allowance. Legacy web routes retain their prepaid credit behavior. GET /v1/me reports the key ID, current plan, and quota scope, used, limit, remaining, UTC day, and reset epoch. It remains available at quota exhaustion and does not charge quota. Successful and 429 responses expose RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset (seconds until midnight UTC), plus X-RateLimit equivalents whose reset uses Unix epoch seconds. A 429 also returns Retry-After. Limits apply per key, matching the current metering.

Account metadata identifies its metering source and normal delay: database snapshots can lag resident requests by the 20-second flush; resident snapshots can lag externally metered requests by the 60-second reconciliation interval. New keys, revocations and plan changes normally reach the resident server within a 60-second poll plus a bounded 10-second read. If refreshes fail, resident keys expire after 120 seconds and requests fail closed. The web server checks keys directly in the database. Missing, malformed, unknown and revoked v1 keys return 401; valid exhausted keys return 429 on lookups. Query keys take precedence over Bearer tokens, and the Bearer scheme is case-insensitive.

Responses use Cache-Control: no-store, including self-IP results. Lite has a reserved schema in the contract and will be available in a later release; it is not an active endpoint.

Endpoint and parameters

GET https://ip-api.dev/api

FieldTypeDescription
qstring, requiredThe IPv4 address, IPv6 address, or AS number to look up. AS numbers are written as AS13335 or as13335. A CIDR mask is rejected — the endpoint looks up single addresses.
keystring, optionalYour API key. Without it the request is counted against the keyless daily quota and answered with the reduced response body.
pretty1, optionalSet to 1 to indent JSON with two spaces. Ignored by the csv, text, html and toon formats.
formatjson | csv | text | html | toon, optionalExplicit response format for clients that cannot conveniently set Accept. Overrides the Accept header. Omit both to receive JSON.

Output formats

Request the canonical /api endpoint and select its representation with the HTTP Accept header. If setting headers is inconvenient, use format; it takes precedence over Accept. With neither, the response is JSON. The format-specific paths remain convenient fixed aliases.

AliasContent-TypeNotes
/apiapplication/jsonThe default. Nested objects are preserved.
/api/csvtext/csvOne header row plus one row per lookup. Nested keys are flattened with dots.
/api/texttext/plainOne key=value line per field. Bulk results are separated by a blank line.
/api/htmltext/htmlA plain two-column table per lookup.
/api/toontext/toonLossless, typed TOON v4 for token-efficient LLM and agent input.

CSV, text and HTML flatten nested fields into dotted keys such as location.country, and arrays are joined with "; ", since an entry such as a phone number may hold spaces. TOON preserves the JSON structure and primitive types; uniform bulk arrays use its compact tabular form.

Accept negotiation
curl 'https://ip-api.dev/api?q=8.8.8.8'   -H 'Accept: text/toon'
Query-parameter convenience
curl 'https://ip-api.dev/api?q=8.8.8.8&format=toon'
GET /api/csv?q=8.8.8.8
ip,is_bogon,is_datacenter,is_anycast,is_tor,is_proxy,is_vpn,is_relay,is_anonymous,is_abuser,is_mobile,is_satellite,is_crawler,organization.name,asn.number,asn.name,location.country_code,location.latitude,location.longitude,elapsed_ms
8.8.8.8,false,true,false,false,false,false,false,false,false,false,false,false,Google LLC,15169,Google LLC,US,,,60.643
TOON response
ip: 8.8.8.8
network:
  cidr: 8.8.8.0/24
  rir: arin
asn:
  number: 15169
  name: Google LLC
services: []
location:
  country_code: US
  precision: region
is_vpn: false
elapsed_ms: 1.234

Keys and rate limits

Pass your key as the key query parameter. There is no header-based authentication.

  • No key: 100 requests per UTC day, answered with the reduced response body described below. IPv4 callers are counted per address. IPv6 callers are counted per prefix, because a single subscriber holds many addresses: 100 per /64, 200 per /56, and 300 per /48.
  • Free key: 1,000 requests per day and the full response body.
  • Paid plans raise the monthly allowance — see pricing.
  • The counter resets at 00:00 UTC. Going over returns 429.

Bulk lookups

POST to the same path with a JSON body to look up between 1 and 1,000 addresses at once. Bulk requires a key, always returns the full response body, and answers with an array in the order you sent.

curl
curl -X POST 'https://ip-api.dev/api?key=YOUR_KEY&pretty=1' \
  -H 'Content-Type: application/json' \
  -d '{"ips": ["1.1.1.1", "8.8.8.8", "9.9.9.9"]}'
200 application/json
[
  { "ip": "1.1.1.1", "network": { "rir": "apnic", ... }, "asn": { ... }, "elapsed_ms": 61.2 },
  { "ip": "8.8.8.8", "network": { "rir": "arin", ... }, "asn": { ... }, "elapsed_ms": 58.9 },
  { "ip": "not-an-ip", "error": "Invalid IP Address or AS Number", "elapsed_ms": 0.01 }
]

Entries that are not valid addresses come back as an object with an error field instead of failing the whole request. AS numbers are not accepted in bulk bodies. Quota is charged up front for valid IP entries, including entries that fail or time out; invalid entries are free. Duplicate IPs count separately. Bodies are limited to 128 KiB (413 when exceeded) and each entry to 64 characters. You may also send one address per line with Content-Type: text/plain. Body reads have a 30-second timeout (408). Lookup processing has a separate 30-second deadline; unfinished entries return an error alongside completed results.

Response fields

These are the fields in the full response body, returned when you send a key.

Top level

FieldTypeDescription
anonymousobjectPrivacy classification: is_vpn, is_proxy (open proxies), is_tor, is_relay, is_hosting, provider_name, network_last_seen and confidence. Hosting alone does not imply anonymity. provider_name, network_last_seen and confidence are null when unknown. Tor identity takes precedence over the selected service.
is_relaybooleanTrue for iCloud Private Relay and Cloudflare WARP. The legacy is_proxy flag remains true for these services.
is_anonymousbooleanTrue when anonymous.is_vpn, is_proxy, is_tor or is_relay is true. Hosting and abuse alone do not set this flag.
ip_risknumberHow likely the address is to be abusive or anonymized, from 0.01 to 99. It combines Tor, VPN (scaled by anonymous.confidence), open proxy, privacy relay, how many abuse lists name the network, the network and AS abuse scores, and hosting. A verified search engine crawler is capped at 0.99. It says nothing about a particular transaction.
ip_risk_reasonsstring[]The signals that raised ip_risk noticeably, from TOR_EXIT, ANONYMOUS_VPN, PUBLIC_PROXY, PRIVACY_RELAY, BLOCKLISTED_IP, ABUSIVE_NETWORK, ABUSIVE_ASN and HOSTING_PROVIDER. Empty when none did.
ipstringThe address you asked about, echoed back as you sent it.
hostnamestringThe reverse DNS hostname for the address. Omitted when we have no PTR record for it.
servicesarrayVPN, proxy and platform-egress matches. Always an array; empty when none are known.
is_bogonbooleanTrue for addresses that should never appear on the public internet: private, loopback, link-local, carrier-grade NAT, documentation, multicast and reserved space. Computed from the address itself.
is_datacenterbooleanTrue when the datacenter section is present, meaning a published cloud range or a hosting-classified AS covers the address.
is_anycastbooleanTrue when measurement data shows the prefix is announced from multiple physical sites.
is_torbooleanTrue when the address is a known Tor exit node.
is_proxybooleanTrue when the address belongs to a platform egress network such as iCloud Private Relay or Cloudflare WARP, or is otherwise flagged as a proxy.
is_vpnbooleanTrue when the address is an exit node for a commercial VPN service.
is_abuserbooleanTrue when a network covering the address appears on an abuse blocklist we ingest.
is_mobilebooleanTrue when the AS originating the route for this address is classified as a mobile carrier.
is_satellitebooleanTrue when the originating AS is classified as a satellite provider.
is_crawlerbooleanTrue when a verified crawler range covers the address. The crawler object carries its name.
crawlerobjectVerified crawler details. Present only on a match; crawler.name is the bot name.
elapsed_msnumberHow long the lookup took on our side, in milliseconds, to three decimal places. Present on every response, including errors.

asn — routing

Omitted when no BGP route covers the address.

FieldTypeDescription
asn.numbernumberThe AS number.
asn.namestring | nullThe organization that holds the AS number.
asn.domainstring | nullThe AS organization's domain when a source publishes it.
asn.country_codestring | nullTwo-letter country code registered for the organization.
asn.routestringThe most specific BGP prefix covering the address that this AS originates.
asn.rirstring | nullThe registry the AS number was assigned by.
asn.typestring | nullBusiness category such as hosting, isp, education or government.
asn.activebooleanTrue when the AS currently originates at least one prefix.
asn.normalized_typestring | nullBroad category: hosting, isp, education, government or business. Legacy banking maps to business; asn.type preserves the source category.
asn.last_changedstring | nullUTC time a snapshot observed a substantive ASN identity change. IP lookups include origin changes; direct ASN lookups track metadata. Unknown before the first baseline or when live metadata is not yet captured.
asn.updated_atstring | nullDate the source ASN organization record last changed.
asn.abuse_scoreobjectFraction of announced addresses on an abuse list as numeric score (0–1) and low/medium/high level.

network — registry assignment

The most-specific whois network, or the broad RIR allocation as a fallback. Omitted only when neither is known.

FieldTypeDescription
network.cidrstringMost-specific registered network, in CIDR notation.
network.namestring | nullRegistry network name (netname), kept distinct from the organization.
network.typestring | nullHow the block was handed out, in one vocabulary for every registry: `allocation` (registry to an ISP, to hand out), `assignment` (registry to an organization, for its own use), `reallocation` (ISP to another ISP), `reassignment` (ISP to a customer) or `legacy` (before the regional registries). Null when the registry gives none of these.
network.rirstring | nullLowercase regional registry code.
network.country_codestring | nullCountry code on the registry network record.
network.rdap_urlstring | nullPublic RDAP URL for the registered network.
network.abuse_scoreobject | nullNumeric score and level for this CIDR. Null only when network is a broad allocation fallback with no scored whois record.

connection — ISP and line

Omitted when nothing is known about the operator or the line.

FieldTypeDescription
connection.ispstring | nullThe network operator: the cloud or CDN provider for a published range, otherwise the owner of the AS.
connection.connection_typeCable/DSL | Cellular | Corporate | Satellite | nullThe kind of line, from the AS's type and the network name and reverse DNS name of the address. Cloud and hosting ranges are Corporate.
connection.user_typestring | nullWho uses the address: business, cellular, college, consumer_privacy_network, content_delivery_network, government, hosting, library, military, residential, school, search_engine_spider or traveler. Null when unknown. MaxMind also has cafe and router, which we have no signal for.
connection.domainstring | nullThe registered domain of the reverse DNS name, using the ICANN Public Suffix List: comcast.net for c-73-162-0-1.hsd1.ca.comcast.net.
connection.mobile_country_codestring | nullFor a cellular address, the carrier's mobile country code (E.212), such as 310.
connection.mobile_network_codestring | nullFor a cellular address, the carrier network code, such as 260. Null for carriers that use a different code in each region, since one code would be wrong for most of their addresses.

organization — registered operator

Omitted when the registry network names no organization or domain.

FieldTypeDescription
organization.namestring | nullOrganization named on the registered network.
organization.domainstring | nullThe organization's domain, when the registry publishes one.

abuse_contact

Omitted when the registry publishes no abuse email.

FieldTypeDescription
abuse_contact.namestring | nullName of the abuse contact or team.
abuse_contact.addressstring | nullPostal address on the abuse record.
abuse_contact.country_codestring | nullThe abuse contact's country, falling back to the network's country.
abuse_contact.emailstringThe abuse email address, the first the registry lists. The section is omitted entirely when the registry publishes no email.
abuse_contact.emailsstring[]Every abuse email the registry lists, in its order, starting with `email`. For RIPE NCC and APNIC, which mark their abuse mailboxes, only those.
abuse_contact.phonestring | nullPhone number on the abuse record, the first the registry lists.
abuse_contact.phonesstring[]Every phone number on the abuse record, starting with `phone`. Empty when there is none.

datacenter — hosting

Omitted when the address is not in a published cloud range and its AS is not classified as hosting.

FieldTypeDescription
datacenter.providerstring | nullThe provider name, such as Google or AWS.
datacenter.domainstring | nullThe provider's domain. Null when the match came from AS classification rather than a published range.
datacenter.networkstring | nullThe covering network in CIDR notation.
datacenter.country_codestring | nullCountry for the range. Null for matches from published cloud ranges, which do not carry one.
datacenter.regionstring | nullProvider region, such as us-east4, when the range names one.
datacenter.citynullReserved. Always null today.

anonymous — privacy classification

The full response always includes this object. Keyless responses include the aggregate is_anonymous and is_relay flags. Hosting alone does not imply anonymity. Existing top-level flags and services retain their meanings, including is_proxy for platform relays.

FieldTypeDescription
anonymous.is_vpnbooleanTrue for the selected subscriber VPN match.
anonymous.is_proxybooleanTrue for an open proxy. Platform relays are classified separately.
anonymous.is_torbooleanTrue for a known Tor exit, independently of service matches.
anonymous.is_relaybooleanTrue for iCloud Private Relay or Cloudflare WARP.
anonymous.is_hostingbooleanSame as is_datacenter. Hosting alone does not set is_anonymous.
anonymous.provider_namestring | nullTor Project for a Tor exit; otherwise the selected service provider, or null when unknown.
anonymous.network_last_seenstring | nullDate (YYYY-MM-DD, UTC) of the newest evidence that the address is an anonymizer, from any VPN, proxy, relay or Tor listing covering it. Null when there is none.
anonymous.confidencenumber | nullHow sure we are that the address is an anonymizer, from 1 to 99. Each listing counts by its kind (Tor, relay, a VPN exit we enumerated, one we inferred from neighbors, a third-party list, an open proxy) and loses half its weight over a set number of days since it was last seen; separate sources add up. Null when no listing covers the address.

Matching service rows are ranked by confidence (enumerated, interpolated, assumed), then prefix specificity and presence of a provider name. Ties use source then provider name in ascending order. Tor takes precedence for anonymous.provider_name without suppressing the selected service flags.

VPN and Tor CSV downloads append the same privacy fields for each evidence row. They omit hosting status, which requires hosting and ASN data. When combining overlapping rows, apply the same service precedence and retain independent Tor evidence.

services — VPN, proxy and platform egress

One stable array covers all relay services. The kind enum distinguishes commercial VPNs, scanned proxies and platform egress such as iCloud Private Relay or Cloudflare WARP.

FieldTypeDescription
services[].kindvpn | proxy | platform_egressWhat kind of relay service the match represents.
services[].providerstring | nullService name, such as NordVPN or iCloud Private Relay.
services[].urlstring | nullThe service's website.
services[].rolestring | nullAddress role: exit_node, server or relay.
services[].confidencestringHow the match was established: enumerated, interpolated or assumed.
services[].last_seenstringISO 8601 timestamp of the last time we saw this range.
services[].exit.regionstring | nullThe region the service advertises for this exit.
services[].exit.country_codestring | nullTwo-letter country code for the exit.
services[].exit.citystring | nullCity the service advertises for this exit.
services[].exit.latitudenumber | nullLatitude the service advertises for this exit.
services[].exit.longitudenumber | nullLongitude the service advertises for this exit.

location — geolocation

Omitted when we cannot determine a country for the address.

FieldTypeDescription
location.last_changedstring | nullUTC timestamp when a daily snapshot first observed a substantive change to the effective location. Refresh times, local clocks and DST do not change it. Null means the change date is unknown, including pre-history baseline rows and current values not yet captured by a snapshot.
location.continentstring | nullContinent name, such as North America.
location.continent_codestring | nullTwo-letter continent code, such as NA or EU.
location.continent_geoname_idnumber | nullGeoNames identifier for the continent.
location.continent_namesobjectThe continent name in each published language. Keyed by locale: de, en, es, fr, ja, pt-BR, ru and zh-CN. A locale is left out when GeoNames has no name in it.
location.countrystring | nullCountry name in English.
location.country_codestringTwo-letter ISO country code. The section is omitted entirely when we cannot determine a country.
location.country_geoname_idnumber | nullGeoNames identifier for the country.
location.country_namesobjectThe country name in each published language. Keyed by locale: de, en, es, fr, ja, pt-BR, ru and zh-CN. A locale is left out when GeoNames has no name in it. English is the same name as country.
location.statestring | nullThe subdivision name, resolved from the code the geofeed published where possible.
location.region_code_rawstring | nullOriginal publisher subdivision value before normalization or name resolution. Null when absent or the address is anycast.
location.region_codestring | nullISO 3166-2 subdivision suffix, such as CA for US-CA, when the source publishes or safely resolves one. Null when absent or unmappable; GeoNames numeric admin codes are never assumed to be ISO codes.
location.region_geoname_idnumber | nullGeoNames identifier for the resolved first-level subdivision.
location.subdivisionsarraySubdivisions from largest to smallest, each with code, name, geoname_id and names. The first is the state the fields above describe. A second, such as a county or district, follows when the city is known and lies in that state; its code is null because GeoNames second-level codes are not ISO codes. Empty when no subdivision is known.
location.citystring | nullCity name as the geofeed published it.
location.city_geoname_idnumber | nullGeoNames identifier for the resolved city.
location.country_confidencenumber | nullHow often an answer like this one names the right country, from 1 to 99. Null when too few addresses like it were measured. See accuracy_radius for how these are measured.
location.city_namesobjectThe resolved city name in each published language. Keyed by locale: de, en, es, fr, ja, pt-BR, ru and zh-CN. A locale is left out when GeoNames has no name in it. Empty when the city did not match a GeoNames city.
location.latitudenumber | nullLatitude from the geofeed, or the centre of the matched city or region.
location.city_confidencenumber | nullHow often an answer like this one names the right city (or a point within 25 km of the address), from 1 to 99. Null when no city is named. subdivisions[].confidence is the same for the state.
location.accuracy_radiusnumber | nullTwo in three answers like this one are within this many kilometers of the address. Measured against RIPE Atlas probes and anchors (RIPE NCC) whose locations are known, per address family, basis and precision; country-only answers get 1000. Probes lean toward Europe and technical users, so the figures fit them best. Null when there are no coordinates.
location.longitudenumber | nullLongitude, from the same source as latitude.
location.zipstring | nullPostal code, when the geofeed published one.
location.timezonestring | nullIANA time zone name, such as America/New_York.
location.local_timestring | nullCurrent local time in that zone, as an ISO 8601 string.
location.local_time_unixnumber | nullThe same instant as a Unix timestamp in seconds.
location.is_dstboolean | nullWhether daylight saving time is in effect in that zone now.
location.utcoffsetstring | nullCurrent offset from UTC, such as -04:00.
location.is_eu_memberbooleanWhether the country is a member of the European Union.
location.calling_codestring | nullInternational dialling code for the country.
location.currency_codestring | nullISO 4217 currency code for the country.
location.precisionstreet | city | region | countrySemantic resolution of the result. This does not claim a radius we cannot substantiate.
location.basisoperator_geofeed | active_measurement | geolocation_database | registry_allocationWhere the place came from: operator_geofeed is a geofeed the network operator published; active_measurement is a latency measurement (RIPE IPmap); geolocation_database is a geolocation database estimate (DB-IP Lite); registry_allocation means no geolocation source named a country, so the country comes from the registry allocation.
registered_countryobjectThe country the regional registry recorded for the network, with country_code, country, geoname_id, is_eu_member and names. It can differ from location, which is where the address is used. Omitted when the registry names no country.

Keyless response

Requests without a key get a strict subset of the same canonical structure. Field names and nesting never change between plans.

FieldTypeDescription
ipstringThe address you asked about.
is_bogonbooleanSame meaning as in the full response.
is_datacenterbooleanSame as above.
is_anycastbooleanSame as above.
is_torbooleanSame as above.
is_proxybooleanSame as above.
is_vpnbooleanSame as above.
is_relaybooleanSame as above.
is_anonymousbooleanSame as above.
is_abuserbooleanSame as above.
is_mobilebooleanSame as above.
is_satellitebooleanSame as above.
is_crawlerbooleanSame as above.
crawler.namestringPresent only for a verified crawler match.
organization.namestring | nullPresent when a registered organization is known.
asn.number / asn.namenumber / string | nullThe canonical ASN identity subset.
location.country_code / latitude / longitudestring / number | nullThe canonical geolocation subset.
elapsed_msnumberLookup time in milliseconds.

AS number lookups

Pass an AS number as q to get the organization and every prefix the AS announces. The response shape is different from an address lookup.

GET /api?q=AS13335&pretty=1
{
  "asn": {
    "number": 13335,
    "name": "Cloudflare, Inc.",
    "domain": "cloudflare.com",
    "country_code": "US",
    "route": "1.0.0.0/24",
    "rir": "arin",
    "active": true
  },
  "prefixes": {
    "ipv4": ["1.0.0.0/24", "1.1.1.0/24", "..."],
    "ipv6": ["2400:cb00::/32", "..."]
  },
  "elapsed_ms": 412.8
}

prefixes.ipv4 and prefixes.ipv6 hold the announcements. An AS we know nothing about returns a 400.

Errors

Errors use the selected response format and always carry error and elapsed_ms. A negotiation error has no selected representation, so its explanatory body is JSON.

StatusBodyWhen
400{"error":"Invalid IP Address or AS Number","elapsed_ms":0.015}q is missing, is not a valid address or AS number, carries a CIDR mask, or names an AS we have no data for.
400{"error":"Body must be {\"ips\": [...]} or text/plain with 1 to 1000 addresses (64 characters maximum per entry)","elapsed_ms":0.02}A bulk POST body that is not valid JSON, or has no ips array, or has more than 1,000 entries.
400{"error":"Invalid output format. Use json, csv, text, html, or toon","elapsed_ms":0.01}format names something the API does not provide.
401{"error":"An API key is required for bulk lookups","elapsed_ms":0.007}A bulk POST without a key.
429{"error":"Rate limit exceeded","limit":500,"elapsed_ms":8.1}The keyless daily quota for your IP is used up.
406{"error":"No acceptable output format is available","elapsed_ms":0.01}Accept excludes every representation the API provides.

CORS and caching

  • Every response carries Access-Control-Allow-Origin: *, so you can call the API straight from a browser.
  • OPTIONS preflight is answered with 200 and allows GET, POST, OPTIONS.
  • Responses are sent with Cache-Control: no-store. Cache on your side if you need to.
  • Negotiated /api responses include Vary: Accept, so compliant caches keep representations separate.

Examples

JavaScript
const response = await fetch(
  'https://ip-api.dev/api?q=8.8.8.8&key=YOUR_KEY',
)
const data = await response.json()

console.log(data.location?.country, data.asn?.name)
if (data.is_vpn || data.is_tor || data.is_abuser) {
  console.log('flagged:', data.ip)
}
Python
import requests

response = requests.get(
    "https://ip-api.dev/api",
    params={"q": "8.8.8.8", "key": "YOUR_KEY"},
    timeout=10,
)
response.raise_for_status()
data = response.json()

print(data["asn"]["name"], data["location"]["country"])

Ready to go? Get a free key or browse the IP range explorer.