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 '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.
| Field | Type | Description |
|---|---|---|
| /v1?q=8.8.8.8 | GET | Query form, accepting IPv4, IPv6, or an AS-prefixed ASN. |
| /v1/lookup/8.8.8.8 | GET | Explicit IP lookup. URL-encode IPv6 path segments. |
| /v1/lookup | GET | Self-IP lookup, using the trusted client connection address. This is an IP lookup, not account metadata. |
| /v1/asn/15169 | GET | ASN organization and announced IPv4/IPv6 prefixes. Use a decimal number without AS. |
| /v1/bulk | POST | One 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
| Field | Type | Description |
|---|---|---|
| q | string, required | The 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. |
| key | string, optional | Your API key. Without it the request is counted against the keyless daily quota and answered with the reduced response body. |
| pretty | 1, optional | Set to 1 to indent JSON with two spaces. Ignored by the csv, text, html and toon formats. |
| format | json | csv | text | html | toon, optional | Explicit 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.
| Alias | Content-Type | Notes |
|---|---|---|
| /api | application/json | The default. Nested objects are preserved. |
| /api/csv | text/csv | One header row plus one row per lookup. Nested keys are flattened with dots. |
| /api/text | text/plain | One key=value line per field. Bulk results are separated by a blank line. |
| /api/html | text/html | A plain two-column table per lookup. |
| /api/toon | text/toon | Lossless, 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.
curl 'https://ip-api.dev/api?q=8.8.8.8' -H 'Accept: text/toon'curl 'https://ip-api.dev/api?q=8.8.8.8&format=toon'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.643ip: 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.234Keys 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 -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"]}'[
{ "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
| Field | Type | Description |
|---|---|---|
| anonymous | object | Privacy 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_relay | boolean | True for iCloud Private Relay and Cloudflare WARP. The legacy is_proxy flag remains true for these services. |
| is_anonymous | boolean | True when anonymous.is_vpn, is_proxy, is_tor or is_relay is true. Hosting and abuse alone do not set this flag. |
| ip_risk | number | How 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_reasons | string[] | 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. |
| ip | string | The address you asked about, echoed back as you sent it. |
| hostname | string | The reverse DNS hostname for the address. Omitted when we have no PTR record for it. |
| services | array | VPN, proxy and platform-egress matches. Always an array; empty when none are known. |
| is_bogon | boolean | True 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_datacenter | boolean | True when the datacenter section is present, meaning a published cloud range or a hosting-classified AS covers the address. |
| is_anycast | boolean | True when measurement data shows the prefix is announced from multiple physical sites. |
| is_tor | boolean | True when the address is a known Tor exit node. |
| is_proxy | boolean | True 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_vpn | boolean | True when the address is an exit node for a commercial VPN service. |
| is_abuser | boolean | True when a network covering the address appears on an abuse blocklist we ingest. |
| is_mobile | boolean | True when the AS originating the route for this address is classified as a mobile carrier. |
| is_satellite | boolean | True when the originating AS is classified as a satellite provider. |
| is_crawler | boolean | True when a verified crawler range covers the address. The crawler object carries its name. |
| crawler | object | Verified crawler details. Present only on a match; crawler.name is the bot name. |
| elapsed_ms | number | How 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.
| Field | Type | Description |
|---|---|---|
| asn.number | number | The AS number. |
| asn.name | string | null | The organization that holds the AS number. |
| asn.domain | string | null | The AS organization's domain when a source publishes it. |
| asn.country_code | string | null | Two-letter country code registered for the organization. |
| asn.route | string | The most specific BGP prefix covering the address that this AS originates. |
| asn.rir | string | null | The registry the AS number was assigned by. |
| asn.type | string | null | Business category such as hosting, isp, education or government. |
| asn.active | boolean | True when the AS currently originates at least one prefix. |
| asn.normalized_type | string | null | Broad category: hosting, isp, education, government or business. Legacy banking maps to business; asn.type preserves the source category. |
| asn.last_changed | string | null | UTC 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_at | string | null | Date the source ASN organization record last changed. |
| asn.abuse_score | object | Fraction 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.
| Field | Type | Description |
|---|---|---|
| network.cidr | string | Most-specific registered network, in CIDR notation. |
| network.name | string | null | Registry network name (netname), kept distinct from the organization. |
| network.type | string | null | How 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.rir | string | null | Lowercase regional registry code. |
| network.country_code | string | null | Country code on the registry network record. |
| network.rdap_url | string | null | Public RDAP URL for the registered network. |
| network.abuse_score | object | null | Numeric 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.
| Field | Type | Description |
|---|---|---|
| connection.isp | string | null | The network operator: the cloud or CDN provider for a published range, otherwise the owner of the AS. |
| connection.connection_type | Cable/DSL | Cellular | Corporate | Satellite | null | The 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_type | string | null | Who 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.domain | string | null | The 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_code | string | null | For a cellular address, the carrier's mobile country code (E.212), such as 310. |
| connection.mobile_network_code | string | null | For 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.
| Field | Type | Description |
|---|---|---|
| organization.name | string | null | Organization named on the registered network. |
| organization.domain | string | null | The organization's domain, when the registry publishes one. |
abuse_contact
Omitted when the registry publishes no abuse email.
| Field | Type | Description |
|---|---|---|
| abuse_contact.name | string | null | Name of the abuse contact or team. |
| abuse_contact.address | string | null | Postal address on the abuse record. |
| abuse_contact.country_code | string | null | The abuse contact's country, falling back to the network's country. |
| abuse_contact.email | string | The abuse email address, the first the registry lists. The section is omitted entirely when the registry publishes no email. |
| abuse_contact.emails | string[] | 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.phone | string | null | Phone number on the abuse record, the first the registry lists. |
| abuse_contact.phones | string[] | 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.
| Field | Type | Description |
|---|---|---|
| datacenter.provider | string | null | The provider name, such as Google or AWS. |
| datacenter.domain | string | null | The provider's domain. Null when the match came from AS classification rather than a published range. |
| datacenter.network | string | null | The covering network in CIDR notation. |
| datacenter.country_code | string | null | Country for the range. Null for matches from published cloud ranges, which do not carry one. |
| datacenter.region | string | null | Provider region, such as us-east4, when the range names one. |
| datacenter.city | null | Reserved. 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.
| Field | Type | Description |
|---|---|---|
| anonymous.is_vpn | boolean | True for the selected subscriber VPN match. |
| anonymous.is_proxy | boolean | True for an open proxy. Platform relays are classified separately. |
| anonymous.is_tor | boolean | True for a known Tor exit, independently of service matches. |
| anonymous.is_relay | boolean | True for iCloud Private Relay or Cloudflare WARP. |
| anonymous.is_hosting | boolean | Same as is_datacenter. Hosting alone does not set is_anonymous. |
| anonymous.provider_name | string | null | Tor Project for a Tor exit; otherwise the selected service provider, or null when unknown. |
| anonymous.network_last_seen | string | null | Date (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.confidence | number | null | How 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.
| Field | Type | Description |
|---|---|---|
| services[].kind | vpn | proxy | platform_egress | What kind of relay service the match represents. |
| services[].provider | string | null | Service name, such as NordVPN or iCloud Private Relay. |
| services[].url | string | null | The service's website. |
| services[].role | string | null | Address role: exit_node, server or relay. |
| services[].confidence | string | How the match was established: enumerated, interpolated or assumed. |
| services[].last_seen | string | ISO 8601 timestamp of the last time we saw this range. |
| services[].exit.region | string | null | The region the service advertises for this exit. |
| services[].exit.country_code | string | null | Two-letter country code for the exit. |
| services[].exit.city | string | null | City the service advertises for this exit. |
| services[].exit.latitude | number | null | Latitude the service advertises for this exit. |
| services[].exit.longitude | number | null | Longitude the service advertises for this exit. |
location — geolocation
Omitted when we cannot determine a country for the address.
| Field | Type | Description |
|---|---|---|
| location.last_changed | string | null | UTC 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.continent | string | null | Continent name, such as North America. |
| location.continent_code | string | null | Two-letter continent code, such as NA or EU. |
| location.continent_geoname_id | number | null | GeoNames identifier for the continent. |
| location.continent_names | object | The 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.country | string | null | Country name in English. |
| location.country_code | string | Two-letter ISO country code. The section is omitted entirely when we cannot determine a country. |
| location.country_geoname_id | number | null | GeoNames identifier for the country. |
| location.country_names | object | The 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.state | string | null | The subdivision name, resolved from the code the geofeed published where possible. |
| location.region_code_raw | string | null | Original publisher subdivision value before normalization or name resolution. Null when absent or the address is anycast. |
| location.region_code | string | null | ISO 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_id | number | null | GeoNames identifier for the resolved first-level subdivision. |
| location.subdivisions | array | Subdivisions 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.city | string | null | City name as the geofeed published it. |
| location.city_geoname_id | number | null | GeoNames identifier for the resolved city. |
| location.country_confidence | number | null | How 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_names | object | The 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.latitude | number | null | Latitude from the geofeed, or the centre of the matched city or region. |
| location.city_confidence | number | null | How 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_radius | number | null | Two 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.longitude | number | null | Longitude, from the same source as latitude. |
| location.zip | string | null | Postal code, when the geofeed published one. |
| location.timezone | string | null | IANA time zone name, such as America/New_York. |
| location.local_time | string | null | Current local time in that zone, as an ISO 8601 string. |
| location.local_time_unix | number | null | The same instant as a Unix timestamp in seconds. |
| location.is_dst | boolean | null | Whether daylight saving time is in effect in that zone now. |
| location.utcoffset | string | null | Current offset from UTC, such as -04:00. |
| location.is_eu_member | boolean | Whether the country is a member of the European Union. |
| location.calling_code | string | null | International dialling code for the country. |
| location.currency_code | string | null | ISO 4217 currency code for the country. |
| location.precision | street | city | region | country | Semantic resolution of the result. This does not claim a radius we cannot substantiate. |
| location.basis | operator_geofeed | active_measurement | geolocation_database | registry_allocation | Where 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_country | object | The 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.
| Field | Type | Description |
|---|---|---|
| ip | string | The address you asked about. |
| is_bogon | boolean | Same meaning as in the full response. |
| is_datacenter | boolean | Same as above. |
| is_anycast | boolean | Same as above. |
| is_tor | boolean | Same as above. |
| is_proxy | boolean | Same as above. |
| is_vpn | boolean | Same as above. |
| is_relay | boolean | Same as above. |
| is_anonymous | boolean | Same as above. |
| is_abuser | boolean | Same as above. |
| is_mobile | boolean | Same as above. |
| is_satellite | boolean | Same as above. |
| is_crawler | boolean | Same as above. |
| crawler.name | string | Present only for a verified crawler match. |
| organization.name | string | null | Present when a registered organization is known. |
| asn.number / asn.name | number / string | null | The canonical ASN identity subset. |
| location.country_code / latitude / longitude | string / number | null | The canonical geolocation subset. |
| elapsed_ms | number | Lookup 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.
{
"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.
| Status | Body | When |
|---|---|---|
| 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. OPTIONSpreflight is answered with200and allowsGET, POST, OPTIONS.- Responses are sent with
Cache-Control: no-store. Cache on your side if you need to. - Negotiated
/apiresponses includeVary: Accept, so compliant caches keep representations separate.
Examples
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)
}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.