ipinfo.app  ·  Successor to ip2asn.ipinfo.app

Atlas API

IP geolocation, ASN lookups, and BGP prefix resolution.
Base URL: https://atlas.ipinfo.app  ·  CORS enabled  ·  No auth required  ·  Data: iptoasn.com

Free No API key IPv4 + IPv6 Hostname resolution BGP prefix data RPKI + IRR
// API v2 — JSON responses
GET /api/v2/ip/:ip_or_hostname

Full geolocation and network lookup for an IPv4 address, IPv6 address, or resolvable hostname. Returns country, continent, ASN, organisation name, and — as of v0.1 — the most-specific announced BGP prefix covering the IP. Cached for 7 days.

Response fields
FieldTypeDescription
ipstringResolved IP address. May differ from input when a hostname is provided.
country_codestringISO 3166-1 alpha-2 code, e.g. "AU".
countrystringFull country name, e.g. "Australia".
country_flagstringUnicode flag emoji, e.g. "🇦🇺".
continentstringFull continent name, e.g. "Oceania".
as_numbernumberASN as an integer, e.g. 13335.
as_descriptionstringOrganisation name from the routing registry, e.g. "CLOUDFLARENET".
cidrnewstring|nullMost-specific announced BGP prefix containing this IP, e.g. "1.1.1.0/24". null when the ASN has no prefix entries in the database.
errornull|stringNull on success. Error message on failure.
Example requests
curl https://atlas.ipinfo.app/api/v2/ip/1.1.1.1 curl https://atlas.ipinfo.app/api/v2/ip/2606:4700::1111 curl https://atlas.ipinfo.app/api/v2/ip/cloudflare.com
Example response — 200 OK
{ "ip": "1.1.1.1", "country_code": "AU", "country": "Australia", "country_flag": "🇦🇺", "continent": "Oceania", "as_number": 13335, "as_description": "CLOUDFLARENET", "cidr": "1.1.1.0/24", "error": null }
Error response — 404 Not Found
{ "ip": "127.0.0.1", // private/reserved addresses are not in the database "country_code": null, "country": null, "country_flag": null, "continent": null, "as_number": null, "as_description": null, "cidr": null, "error": "IP address not found in database." }
GET /api/v2/asn/:as_number

Returns all CIDR prefixes announced by an ASN, annotated with country and continent data. Now also includes pre-computed announcement size stats so consumers don't need to iterate the full prefix list. :as_number accepts a bare integer or an AS-prefixed string (e.g. 13335 or AS13335). Cached for 24 hours.

Response fields
FieldTypeDescription
as_numbernumberThe ASN as an integer.
as_descriptionstringOrganisation name from the registry.
ipv4_countnewnumberTotal IPv4 host addresses across all announced prefixes (sum of 2^(32−prefixLen)).
ipv6_prefix_countnewnumberNumber of IPv6 prefix entries. IPv6 address space is too large to represent as a single sum.
cidrsobject[]Array of prefix objects.
cidrs[].cidrstringCIDR notation, e.g. "1.1.1.0/24".
cidrs[].country_codestringISO 3166-1 alpha-2 code for this prefix's allocation.
cidrs[].countrystringFull country name for this prefix.
cidrs[].continentstringFull continent name for this prefix.
errornull|stringNull on success.
Example requests
curl https://atlas.ipinfo.app/api/v2/asn/AS13335 curl https://atlas.ipinfo.app/api/v2/asn/13335
Example response — 200 OK
{ "as_number": 13335, "as_description": "CLOUDFLARENET", "ipv4_count": 1572864, // total IPv4 addresses announced "ipv6_prefix_count": 4, // number of IPv6 prefix entries "cidrs": [ { "cidr": "1.1.1.0/24", "country_code": "AU", "country": "Australia", "continent": "Oceania" } // ... more prefixes ], "error": null }
Error response — 404 Not Found
{ "as_number": 999999, "as_description": null, "ipv4_count": 0, "ipv6_prefix_count": 0, "cidrs": [], "error": "AS number not found or has no associated CIDRs in the database." }
GET /api/v2/hop/:ipnew

Traceroute hop annotation — returns the reverse-DNS record, the ASN details, the internet exchange the address sits on if any, and a best-effort "PoP" (point of presence) match. An address inside a peering LAN is at that exchange by definition, so it places the hop outright; otherwise the PoP is derived from parsing the PTR against known carrier naming conventions (Hurricane Electric, Cogent, GTT, Zayo, Lumen/Level3, NTT). When no carrier pattern fits, a tier-2 "scatter" pass looks for 3-letter IATA hub codes and known city names anywhere in the hostname. Supports IPv4 and IPv6. Auto-generated IPv6 PTRs (e.g. residential Comcast nibble-hex labels) are detected and stripped before matching. Cached for 24 hours; rate-limited to 300 requests/minute per client IP.

Response fields
FieldTypeDescription
ipstringThe IP as supplied in the URL.
ip_version4 | 6 | nullAddress family. null only on 400 errors.
ptrstring|nullReverse-DNS hostname. null when no PTR is published or lookup timed out (2s).
asnobject|nullASN metadata (same shape as /api/v2/ip). null when the IP is not in the routing table.
asn.numbernumberAS number as integer.
asn.namestringOrganisation name from the registry.
asn.country_codestringISO 3166-1 alpha-2 country code.
ixobject|nullThe internet exchange whose peering LAN contains this address, or null — which is the answer for almost every address. The full record, including every LAN and member, is at /api/v2/ix/ip/:ip.
ix.name / ix.city / ix.countrystring|nullExchange name and location. country is PCH's country name ("Germany"), not a code — unlike every other country field here.
ix.prefixstringThe peering LAN that matched.
ix.memberobject|nullThe network whose port holds this exact address, when PCH has it on file. null means not recorded, never "probably".
ix.source / ix.licensestring|nullAttribution for the exchange data (Packet Clearing House, CC BY-NC-SA 3.0). Render these wherever you render ix.
popobject|nullMatched PoP annotation, or null when nothing matched.
pop.iatastringCanonical 3-letter IATA code for the PoP city. null on an exchange match when the exchange has no IATA code.
pop.citystringCity name.
pop.countrystringISO alpha-2 country code.
pop.lat / pop.lonnumberAirport coordinates (proxy for PoP location).
pop.matchedstringOrigin of the match: ix:<id>, carrier:<suffix> or scatter:<token>.
pop.confidence"high"|"medium"|"low"high = peering-LAN match or carrier pattern; medium = scatter hit whose country agrees with the ASN country; low = scatter hit only.
ptr_style"normal"|"auto"|"auto-prefix"|nullauto = PTR was auto-generated (no hint); auto-prefix = we stripped the auto label and still matched the suffix.
errornull|stringNull on success. Error message on 400 (invalid IP) or 429 (rate-limited).
Example requests
curl https://atlas.ipinfo.app/api/v2/hop/184.105.213.157 curl https://atlas.ipinfo.app/api/v2/hop/2001:470:0:68::2 curl https://atlas.ipinfo.app/api/v2/hop/154.54.30.173
Example response — 200 OK (carrier hit, high confidence)
{ "ip": "184.105.213.157", "ip_version": 4, "ptr": "100ge15-1.core1.lax1.he.net", "asn": { "number": 6939, "name": "HURRICANE", "country_code": "US", "country": "United States", "country_flag": "🇺🇸", "continent": "North America" }, "pop": { "iata": "LAX", "city": "Los Angeles", "country": "US", "lat": 33.9425, "lon": -118.408, "matched": "carrier:he.net", "confidence": "high" }, "ptr_style": "normal", "error": null }
Example response — 200 OK (no match)
{ "ip": "8.8.8.8", // google — generic PTR, no PoP hint "ip_version": 4, "ptr": "dns.google", "asn": { /* ... */ }, "pop": null, "ptr_style": "normal", "error": null }
Error response — 429 Too Many Requests
{ "ip": "...", "error": "Rate limit exceeded. Try again in 60 seconds." }
GET /api/v2/routing/snapshotnew

Provenance for the topology dataset this instance is serving. Every other routing response embeds the same block, so a caller can always state what it is showing and when it was observed. Fetch this once to decide whether the routing endpoints are worth calling at all: 404 means no topology is loaded here, which is a normal state — a database image built before the routing tables existed, or a web image newer than its database image mid-rollout — and not an error. Cached for 1 hour.

Response fields
FieldTypeDescription
rib_tsstringISO 8601 timestamp of the RIB dump the data was parsed from — when the internet looked like this, not when it was loaded.
collectorsstringComma-separated collectors that contributed, e.g. "route-views2,rrc00".
peer_countnumberDistinct collector peers whose AS_PATHs went into the snapshot. This is the denominator for every observations and peers_seen figure elsewhere.
route_entriesnumberRoute entries parsed across all collectors.
Example request
curl https://atlas.ipinfo.app/api/v2/routing/snapshot
Example response — 200 OK
{ "rib_ts": "2026-08-05T02:00:00.000Z", "collectors": "route-views2,rrc00", "peer_count": 74, "route_entries": 73320063 }
Response — 404 Not Found (no topology loaded)
{ "error": "no routing snapshot loaded" }
GET /api/v2/routing/asn/:as_numbernew

Observed BGP topology for an autonomous system: the networks that carry its traffic, the networks it carries traffic for, and every AS seen adjacent to it in a routing path. Derived from bulk MRT routing tables (RouteViews route-views2 and RIPE RIS rrc00), which is the only way to answer "who provides transit to this network". Related networks come back named, joined in-database, so rendering a peering table costs one request rather than one lookup per row. Accepts 15169 or AS15169. Cached for 1 hour.

Everything here is an observation. These are AS_PATHs seen by a specific set of collector peers at a specific time, so every response embeds a snapshot block and callers are expected to show it. Two claims the data does not support: adjacency is not peering (two ASes next to each other in a path may be in a transit, settlement-free peering or customer relationship, and routing data cannot tell them apart, which is why the field is neighbours), and upstreams are inferred from the AS immediately preceding the origin, so for a tier-1 that buys transit from nobody it surfaces that network's peers instead. The response carries inferred: true so this cannot be missed.

Query parameters
ParameterDefaultDescription
limit100Caps each of upstreams, downstreams and neighbours independently. Clamped to 500. The *_count fields are unaffected, so a truncated list is always detectable.
Response fields
FieldTypeDescription
as_numbernumberThe AS as supplied in the URL, normalised to an integer.
namestring|nullOperator name. null when the AS is observed in paths but absent from the name dataset.
prefixes_v4 / prefixes_v6numberDistinct prefixes observed originating from this AS, per address family.
addresses_v4numberAddresses covered by those v4 prefixes. Summed from the observed prefix list, not from the registry or this service's own per-ASN size fields, which disagree with observed BGP in both directions. There is deliberately no addresses_v6: the figure exceeds any integer type worth carrying and nobody reasons about it, so IPv6 is counted in prefixes.
degree_v4 / degree_v6numberDistinct adjacent ASes per address family.
upstream_countnumberTotal upstreams, before limit is applied.
downstream_countnumberTotal downstreams, before limit is applied.
upstreamsarrayNetworks seen immediately before this AS where it is the origin, most-observed first. Inferred — see above.
downstreamsarrayThe inverse: networks for which this AS appears as the upstream.
neighboursarrayEvery adjacent AS in either direction, most-observed first.
upstreams[].as_numbernumberThe related AS.
upstreams[].namestring|nullOperator name for the related AS, already resolved.
upstreams[].af4 | 6Address family the adjacency was observed in. An AS pair adjacent over both appears twice.
upstreams[].observationsnumberHow many collector peers saw this adjacency. Weight it against snapshot.peer_count; a 1-of-74 adjacency is not the same claim as 68-of-74.
inferredtrueAlways present and always true. Do not render these relationships as fact.
snapshotobjectProvenance block, same shape as /api/v2/routing/snapshot.
Example requests
curl https://atlas.ipinfo.app/api/v2/routing/asn/AS15169 curl https://atlas.ipinfo.app/api/v2/routing/asn/15169 curl "https://atlas.ipinfo.app/api/v2/routing/asn/AS15169?limit=10"
Example response — 200 OK
{ "as_number": 15169, "name": "Google LLC", "prefixes_v4": 1227, "prefixes_v6": 176, "addresses_v4": 2615808, "degree_v4": 302, "degree_v6": 118, "upstream_count": 21, "downstream_count": 37, "upstreams": [ { "as_number": 6453, "name": "TATA COMMUNICATIONS (AMERICA) INC", "af": 4, "observations": 41 }, { "as_number": 6939, "name": "Hurricane Electric LLC", "af": 4, "observations": 35 } ], "downstreams": [ { "as_number": 396982, "name": "Google LLC", "af": 4, "observations": 69 }, { "as_number": 36492, "name": "Google, LLC", "af": 4, "observations": 33 } ], "neighbours": [ /* same shape, both directions, most-observed first */ ], "inferred": true, "snapshot": { "rib_ts": "2026-08-05T02:00:00.000Z", "collectors": "route-views2,rrc00", "peer_count": 74, "route_entries": 73320063 } }
Response — 404 Not Found (AS not observed)
{ // allocated but not announcing, or announced only where our collectors cannot see "error": "AS not seen in any observed path", "as_number": 64496, "snapshot": { /* ... */ } }
Error response — 400 Bad Request
{ "error": "invalid AS number" }
GET /api/v2/routing/ip/:ipnew

How the internet actually reaches one address: every announcement covering it, most specific first, each with the AS originating it and how many collector peers saw that origin. This is strict BGP origin, which is a different and stricter question than "which ASN is this IP registered to" that /api/v2/ip/:ip answers. IPv4 and IPv6. Cached for 1 hour.

routed: false is a real answer, not a 404 — the address is allocated but not reachable, or announced only where our collectors cannot see. moas: true means the most specific prefix is announced by more than one AS: legitimate multi-homing or anycast, or a hijack. It is reported, never adjudicated. Note that two different prefixes covering one address is ordinary deaggregation and does not set moas. A leaked default route (0.0.0.0/0, ::/0) covers every address and is never listed.

Response fields
FieldTypeDescription
ipstringThe address as supplied in the URL.
routedbooleanfalse when no observed prefix covers the address.
coveringarrayCovering announcements, longest prefix first, then most-observed. Capped at 8.
covering[].prefixstringThe announced CIDR block.
covering[].origin_asnnumberAS at the end of the AS_PATH for that prefix.
covering[].namestring|nullOperator name for the origin, already resolved.
covering[].peers_seennumberCollector peers that saw this prefix from this origin. Weigh against snapshot.peer_count.
covering[].rpki_statestring|nullPrecomputed RPKI state of this announcement: valid | invalid | not-found, as on /api/v2/rpki/validate. null when this instance has no RPKI data.
covering[].rpki_reasonstring|nullas | length when invalid; null otherwise.
covering[].irr_statestring|nullPrecomputed IRR state: registered | covered | mismatch | missing, as on /api/v2/irr/snapshot. null when this instance has no IRR data.
moasbooleanMore than one origin on the most specific covering prefix.
origin_countnumberDistinct origins on that most specific prefix. 0 when unrouted.
snapshotobjectProvenance block, same shape as /api/v2/routing/snapshot.
Example requests
curl https://atlas.ipinfo.app/api/v2/routing/ip/8.8.8.8 curl https://atlas.ipinfo.app/api/v2/routing/ip/2001:4860:4860::8888
Example response — 200 OK
{ "ip": "8.8.8.8", "routed": true, "covering": [ { "prefix": "8.8.8.0/24", "origin_asn": 15169, "name": "Google LLC", "peers_seen": 74, "rpki_state": "valid", "rpki_reason": null, "irr_state": "registered" }, { "prefix": "8.0.0.0/12", "origin_asn": 3356, "name": "Level 3 Parent, LLC", "peers_seen": 40, "rpki_state": "not-found", "rpki_reason": null, "irr_state": "missing" } // ... ], "moas": false, // one origin on the most specific prefix — covering routes are not MOAS "origin_count": 1, "snapshot": { /* ... */ } }
Example response — 200 OK (unrouted)
{ "ip": "192.0.2.1", "routed": false, "covering": [], "moas": false, "origin_count": 0, "snapshot": { /* ... */ } }
Error response — 400 Bad Request
{ "error": "invalid IP address" }
GET /api/v2/routing/prefix/:cidrnew

Everything observed at, above and below one prefix: who originates it, which shorter announcements cover it, and what is announced inside it. IPv4 and IPv6; the slash can be sent literally or as %2F. Host bits are zeroed before lookup and the canonical form is echoed back, so 8.8.8.8/24 answers as 8.8.8.0/24 rather than as a miss. Cached for 1 hour.

routed: false is a real answer, not a 404 — nothing was seen announcing that exact prefix, though covering or more-specific routes may still be listed. More than one entry in origins is the same MOAS signal as on /api/v2/routing/ip/:ip: multi-homing, anycast or a hijack, reported, never adjudicated. A leaked default route covers everything and says nothing, so 0.0.0.0/0 and ::/0 are never listed as less-specifics.

Every row in origins, less_specifics and more_specifics.rows also carries its precomputed rpki_state, rpki_reason and irr_state, exactly as on /api/v2/routing/ip/:ip, so a prefix page needs no per-row RPKI or IRR lookups. A null state means that dataset is not loaded on this instance, never "bad".

Response fields
FieldTypeDescription
prefixstringThe canonical prefix that was looked up.
routedbooleanfalse when no collector peer saw this exact prefix announced.
originsarrayOrigin ASes of the exact prefix, most-observed first. Each has origin_asn, name, peers_seen, rpki_state, rpki_reason and irr_state; the prefix itself is not repeated.
less_specificsarrayStrictly covering announcements, most specific first, each with prefix, origin_asn, name and peers_seen. Capped at 20, which is the whole covering chain in practice.
more_specifics.countnumberEvery announcement strictly inside the prefix, uncapped.
more_specifics.rowsarrayThe first 200 of those in address order, same shape as less_specifics. Compare against count to detect truncation.
snapshotobjectProvenance block, same shape as /api/v2/routing/snapshot.
Example requests
curl https://atlas.ipinfo.app/api/v2/routing/prefix/8.8.8.0/24 curl https://atlas.ipinfo.app/api/v2/routing/prefix/8.0.0.0/9 curl https://atlas.ipinfo.app/api/v2/routing/prefix/2001:4860::/32
Example response — 200 OK
{ "prefix": "8.8.8.0/24", "routed": true, "origins": [ { "origin_asn": 15169, "name": "Google LLC", "peers_seen": 44, "rpki_state": "valid", "rpki_reason": null, "irr_state": "registered" } ], // every row below carries the same three state fields; elided here "less_specifics": [ { "prefix": "8.0.0.0/12", "origin_asn": 3356, "name": "Level 3 Parent, LLC", "peers_seen": 40 }, { "prefix": "8.0.0.0/9", "origin_asn": 3356, "name": "Level 3 Parent, LLC", "peers_seen": 40 } ], // for 8.0.0.0/9 this is { "count": 1684, "rows": [ /* first 200 */ ] } "more_specifics": { "count": 0, "rows": [] }, "snapshot": { /* ... */ } }
Error response — 400 Bad Request
{ "error": "invalid prefix" }
GET /api/v2/routing/topnew

The largest networks by one measure, from the same per-AS figures /api/v2/routing/asn/:as returns. Rows come back named, so a leaderboard is one request. Cached for 1 hour.

Every figure is what the collectors observed, not what a registry allocated: addresses counts IPv4 space seen announced, and upstreams / downstreams rank by the same inferred relationships as the per-AS endpoint, so a tier-1's upstream count is really its peers.

Query parameters
ParameterDefaultDescription
byaddressesOne of addresses (IPv4 addresses, ties broken by prefix count), prefixes (v4 + v6), upstreams, downstreams or degree (adjacent ASes, v4 + v6). Anything else is a 400 naming the options.
limit100Rows returned, clamped to 1–500. There is no offset.
Response fields
FieldTypeDescription
bystringThe measure applied.
rowsarrayLargest first, AS number as the final tie-break so the order is stable.
rows[].as_number / namenumber / string|nullThe AS and its operator name.
rows[].prefixes_v4 / prefixes_v6 / addresses_v4numberObserved origination, as on /api/v2/routing/asn/:as.
rows[].degree_v4 / degree_v6numberDistinct adjacent ASes per address family.
rows[].upstream_count / downstream_countnumberInferred relationship counts.
snapshotobjectProvenance block, same shape as /api/v2/routing/snapshot.
Example requests
curl https://atlas.ipinfo.app/api/v2/routing/top curl "https://atlas.ipinfo.app/api/v2/routing/top?by=downstreams&limit=25"
Example response — 200 OK
{ "by": "addresses", "rows": [ { "as_number": 749, "name": "United States Department of Defense (DoD)", "prefixes_v4": 3133, "prefixes_v6": 0, "addresses_v4": 227464192, "degree_v4": 1, "degree_v6": 0, "upstream_count": 1, "downstream_count": 0 }, { "as_number": 16509, "name": "Amazon.com, Inc.", "prefixes_v4": 18688, "prefixes_v6": 5950, "addresses_v4": 218110208, "degree_v4": 179, "degree_v6": 132, "upstream_count": 95, "downstream_count": 123 } // ... up to limit ], "snapshot": { /* ... */ } }
Error response — 400 Bad Request
{ "error": "by must be one of: addresses, prefixes, upstreams, downstreams, degree" }
GET /api/v2/routing/countrynew

Registration and routing totals for every country that has at least one delegated AS number, largest IPv4 footprint first. Cached for 1 hour.

Country here is where the AS number is registered, not where the addresses are used. It comes from the RIRs' delegated statistics (the same data as the RDAP endpoints), and every prefix an AS originates is counted under that AS's registration country — a US-registered global CDN counts once, for the US, wherever its space is actually announced or used. Label it "registered in", never "located in". Registry pseudo-codes such as EU appear with name: null, because the registries really do file AS numbers under them.

Needs both the routing and the registry dataset: a 404 carries "no routing snapshot loaded" or "no registry dataset loaded" to say which is missing.

Response fields
FieldTypeDescription
countriesarrayOne row per registration country, addresses_v4 descending.
countries[].ccstringRegistry country code, upper-case.
countries[].namestring|nullCountry name; null for registry pseudo-codes.
countries[].asns_registerednumberAS numbers delegated to the country — every number in every delegated range, routed or not.
countries[].asns_routednumberOf those, how many were seen originating at least one prefix.
countries[].prefixes_v4 / prefixes_v6 / addresses_v4numberObserved origination of those routed ASes, summed.
snapshotobjectRouting provenance block, same shape as /api/v2/routing/snapshot.
Example request
curl https://atlas.ipinfo.app/api/v2/routing/country
Example response — 200 OK
{ "countries": [ { "cc": "US", "name": "United States of America", "asns_registered": 32219, "asns_routed": 18684, "prefixes_v4": 303612, "prefixes_v6": 56302, "addresses_v4": 1736988552 }, { "cc": "CN", "name": "China", "asns_registered": 6623, "asns_routed": 5182, "prefixes_v4": 74016, "prefixes_v6": 39219, "addresses_v4": 450689920 } // ... 242 rows ], "snapshot": { /* ... */ } }
Response — 404 Not Found (a dataset is missing)
{ "error": "no registry dataset loaded" }
GET /api/v2/routing/country/:ccnew

The routed networks registered in one country, largest IPv4 footprint first, with that country's totals row. :cc is a two-letter code, case-insensitive. The same registration-country caveat as /api/v2/routing/country applies: this lists networks whose AS number was delegated in the country, not networks that operate there. Cached for 1 hour.

A real country with nothing registered is a 200 with zeros, not a 404 — that is an answer. 404 "unknown country" means the code names no country at all: not two letters, or neither an ISO code nor present in any registry's statistics.

Query parameters
ParameterDefaultDescription
limit100Rows returned, clamped to 1–500.
offset0Rows to skip, clamped to 0–1,000,000. Page until offset reaches total.
Response fields
FieldTypeDescription
ccstringThe code, upper-cased.
namestring|nullCountry name; null for registry pseudo-codes.
totalsobjectasns_registered, asns_routed, prefixes_v4, prefixes_v6, addresses_v4 — the country's row from /api/v2/routing/country.
totalnumberThe true length of the list rows pages through (equal to totals.asns_routed).
rowsarrayRouted ASes, each with as_number, name, prefixes_v4, prefixes_v6, addresses_v4, upstream_count and downstream_count.
snapshotobjectRouting provenance block, same shape as /api/v2/routing/snapshot.
Example requests
curl https://atlas.ipinfo.app/api/v2/routing/country/NL curl "https://atlas.ipinfo.app/api/v2/routing/country/nl?limit=50&offset=50"
Example response — 200 OK
{ "cc": "NL", "name": "Netherlands", "totals": { "asns_registered": 1608, "asns_routed": 1100, "prefixes_v4": 15369, "prefixes_v6": 3909, "addresses_v4": 61243245 }, "total": 1100, "rows": [ { "as_number": 20940, "name": "Akamai International B.V.", "prefixes_v4": 3951, "prefixes_v6": 760, "addresses_v4": 15610818, "upstream_count": 179, "downstream_count": 14 }, { "as_number": 33915, "name": "Vodafone Libertel B.V.", "prefixes_v4": 342, "prefixes_v6": 31, "addresses_v4": 12032256, "upstream_count": 1, "downstream_count": 27 } // ... 100 per page ], "snapshot": { /* ... */ } }
Response — 404 Not Found (no such country)
{ "error": "unknown country", "cc": "ZZ" }
GET /api/v2/threat/:ipalpha
Alpha. The FBI IC3 threat feed is an early-stage feature — coverage (which advisories are parsed) and the response shape may still change without notice. Treat it as experimental provenance, not a stable contract, and don't build automated blocking solely on it yet.

FBI IC3 malicious-IP reputation. Answers "is this IP listed in an FBI IC3 Cybersecurity Advisory (a 'flash')?" from the rolling last year of advisories at ic3.gov/CSA, alongside the geo/ASN summary — one call for where/whose an IP is and whether the FBI has named it. A clean IP returns 200 with ic3.listed = false (not 404). This is provenance, not a verdict: advisory IPs age and get reassigned, and advisories occasionally list victim/sinkhole IPs — weigh it as one signal, never a standalone block. Cached 24h.

Response fields
FieldTypeDescription
ipstringThe IP as supplied.
country / country_code / country_flag / continentstring|nullGeo summary (same source as /api/v2/ip); null when the IP isn't in the DB.
as_number / as_descriptionnumber|string|nullOwning ASN.
ic3.listedbooleanTrue if the IP matches one or more advisories.
ic3.countnumberNumber of matching advisory rows.
ic3.advisories[]arrayEach: advisory_id, title, pub_date, source_url (the advisory PDF), cidr (the listed entry).
errornull|stringNull on success; message on 400 (invalid IP).
Companion routes
RouteReturns
/api/v2/threat/listPlain text — every listed IP, one per line (hosts bare, ranges as CIDR). Firewall URL-table / external-connector feed.
/api/v2/threat/asn/:asJSON { as_number, ic3_count, iocs[] } — listed IOCs attributed to an ASN, each ioc carrying the advisory that named it (advisory_id, title, pub_date, source_url).
Example requests
curl https://atlas.ipinfo.app/api/v2/threat/45.148.10.212 curl https://atlas.ipinfo.app/api/v2/threat/list curl https://atlas.ipinfo.app/api/v2/threat/asn/AS12345
Example response — 200 OK (listed)
{ "ip": "45.148.10.212", "country_code": "NL", "as_number": 12345, "as_description": "Example BV", "ic3": { "listed": true, "count": 1, "advisories": [ { "advisory_id": "260702", "title": "Cyber Criminal Group TeamPCP", "pub_date": "2026-07-02", "source_url": "https://www.ic3.gov/CSA/2026/260702.pdf", "cidr": "45.148.10.212" } ] }, "error": null }
GET /api/v2/description/:as_number

A curated, plain-language "what is this network" TLDR for well-known ASNs — what it's used for, its location, and notable / interesting facts. Accepts 13335 or AS13335. AI-generated, best-effort context (see the disclaimer field): not authoritative and carries no guarantee of accuracy. body_markdown is a small markdown subset meant for client-side rendering. Only a curated set of ASNs has an entry — any other returns 404 (a normal answer, not an error). The source files are public and editable via source_url (the co-op repo). Cached 24h.

Response fields
FieldTypeDescription
as_numbernumberThe ASN, as an integer.
namestring|nullOperator name.
locationstring|nullPrimary geography, or Global.
tags[]arrayShort lowercase labels (e.g. cdn, transit).
generatedstringProvenance of the text — currently always ai.
body_markdownstringThe description body, a small markdown subset (headings, lists, bold/italic, links).
disclaimerstringFixed AI-generated / no-accuracy-guarantee notice.
source_urlstringLink to the source markdown in the public co-op repo.
errornull|stringNull on success; message on 400 (invalid AS) / 404 (no entry).
Example request
curl https://atlas.ipinfo.app/api/v2/description/AS13335
Example response — 200 OK
{ "as_number": 13335, "name": "Cloudflare, Inc.", "location": "Global", "tags": ["cdn", "dns", "security"], "generated": "ai", "body_markdown": "Cloudflare runs one of the world's largest edge networks...", "disclaimer": "This description is AI-generated...", "source_url": "https://gitlab.com/cmunroe/co-op/-/blob/main/asn/details/AS13335.md", "error": null }
GET /api/v2/ix/snapshotnew

Provenance and licence for the internet-exchange dataset this instance is serving. Every other /api/v2/ix/* response embeds the same block. Fetch this once to decide whether the exchange endpoints are worth calling at all: 404 means no exchange data is loaded here, which is a normal state on a database image built before these tables existed, and not an error. Cached for 1 hour.

Attribution is required, not optional. The data is Packet Clearing House's, under CC BY-NC-SA 3.0: free to redistribute non-commercially, with attribution, under the same terms. That is why source and license are fields in the response rather than a footnote here — if you render this data, render those too.

Response fields
FieldTypeDescription
collected_atstringISO 8601 timestamp of the collection run, not of the image build.
sourcestringUpstream dataset. Display it.
licensestringLicence the data is redistributed under. Display it.
exchangesnumberExchanges in the directory, including planned, deprecated and defunct ones.
prefixesnumberPeering LANs across all exchanges, both address families.
membersnumberIndividual member addresses recorded on those LANs.
Example request
curl https://atlas.ipinfo.app/api/v2/ix/snapshot
Example response — 200 OK
{ "collected_at": "2026-08-06T15:12:39.005Z", "source": "Packet Clearing House (https://www.pch.net/ixp/data)", "license": "CC BY-NC-SA 3.0", "exchanges": 1327, "prefixes": 1940, "members": 93127 }
Response — 404 Not Found (no exchange data loaded)
{ "error": "no exchange snapshot loaded" }
GET /api/v2/ix/listnew

The exchange directory, biggest first, optionally narrowed to a region and/or a country, with a region facet for building a picker. Every directory entry is included whatever its status — planned, deprecated and defunct exchanges too — so filter on that field if you only want live ones. Cached for 1 hour.

Attribution travels in snapshot. This is Packet Clearing House data under CC BY-NC-SA 3.0, exactly as described on /api/v2/ix/snapshot; render snapshot.source and snapshot.license wherever you render the list. members is what PCH has on file, not what is live, so a zero is not evidence of an empty exchange.

Query parameters
ParameterDefaultDescription
region—Exact region name, case-insensitive, e.g. Europe. Take the values from regions.
country—Exact country name, case-insensitive, e.g. Germany — the upstream publishes no codes. Either filter over 64 characters is a 400.
limit100Rows returned, clamped to 1–500.
offset0Rows to skip, clamped to 0–100,000.
Response fields
FieldTypeDescription
totalnumberExchanges matching the filters, before paging.
regionsarray{ region, count } per region, under the country filter but not the region filter, so a picker does not collapse to the region already chosen.
rowsarrayExchanges, most members first, then by name and id so paging never repeats or skips a row.
rows[].idnumberPacket Clearing House exchange id — the :id for /api/v2/ix/:id.
rows[].name / city / country / region / iatastring|nullDirectory entry. country is a name, not a code.
rows[].statusstringExchange status, same values as /api/v2/ix/:id.
rows[].membersnumberDistinct networks on file. Not the same figure as member_count on /api/v2/ix/:id, which counts member addresses (a dual-stack member is two).
rows[].portsnumberConnected ports as recorded upstream.
rows[].trafficnumberReported peak traffic in bits per second (traffic_bps on /api/v2/ix/:id). 0 where unreported, which is common.
snapshotobjectProvenance and licence block, same shape as /api/v2/ix/snapshot.
Example requests
curl https://atlas.ipinfo.app/api/v2/ix/list curl "https://atlas.ipinfo.app/api/v2/ix/list?region=Europe&country=Germany&limit=20"
Example response — 200 OK
{ "total": 1342, "regions": [ { "region": "Europe", "count": 467 }, { "region": "Asia-Pacific", "count": 311 } // ... one per region ], "rows": [ { "id": 30, "name": "IX.br (PTT.br) São Paulo", "city": "São Paulo", "country": "Brazil", "region": "Latin America", "iata": "GRU", "status": "Active", "members": 2543, "ports": 2705, "traffic": 0 }, { "id": 83, "name": "DE-CIX Frankfurt", "city": "Frankfurt", "country": "Germany", "region": "Europe", "iata": "FRA", "status": "Active", "members": 1177, "ports": 1074, "traffic": 8601697793 } // ... up to limit ], "snapshot": { "collected_at": "2026-09-27T14:41:51.116Z", "source": "Packet Clearing House (https://www.pch.net/ixp/data)", "license": "CC BY-NC-SA 3.0", "exchanges": 1342, "prefixes": 1971, "members": 97329 } }
Error response — 400 Bad Request
{ "error": "region and country must be at most 64 characters" }
GET /api/v2/ix/ip/:ipnew

Is this address sitting on a peering LAN, and whose port is it? This is the endpoint worth building on: a traceroute hop inside a peering LAN is at that exchange, which locates it far more reliably than an rDNS guess — the exchange's coordinates apply to the interface, not to the network that owns it. IPv4 and IPv6. Cached for 1 hour.

on_ix: false is the ordinary answer, not a 404 — most addresses are not on an exchange. ix.member names the network holding the port when Packet Clearing House has that exact address on file, and is null otherwise; it is an exact-address match, so there is no half-answer to misread. Deprecated LANs are matched deliberately — a hop on a retired peering LAN still crossed that exchange — and ix.prefix_status says so.

ix is singular, and sometimes that is one of two true answers. Packet Clearing House records a handful of fabrics under more than one exchange id — 206.72.210.0/23 is listed by both 373 and 2450, which are the same Los Angeles exchange — so also_recorded_as sits beside ix and names every other exchange whose active LAN also covers the address. It is [] for an unambiguous address, which is nearly all of them. ix itself is unchanged; nothing that worked before moves.

Anything covering the address counts here, not only an identical LAN: a carve-out registered by a different exchange is a competing claim on where the address lives, and that is precisely what the field exists to expose. Compare related on /api/v2/ix/:id, which asks the narrower question of whether two entries are the same fabric.

Response fields
FieldTypeDescription
ipstringThe address as supplied in the URL.
on_ixbooleanfalse when no peering LAN covers the address.
ixobject|nullThe matched exchange, or null. Most specific LAN wins where blocks overlap.
ix.prefixstringThe peering LAN that matched.
ix.prefix_statusstringActive, Deprecated, Unknown or Defunct — the LAN's status, not the exchange's.
ix.participantsnumberPorts on that LAN as recorded upstream.
ix.lat / ix.lonnumber|nullExchange coordinates. This is what makes the match useful for geolocating a hop.
ix.iatastring|nullNearest airport code, for ~25% of exchanges. Joins onto the same PoP vocabulary /api/v2/hop/:ip uses.
ix.memberobject|nullThe network occupying the address, when recorded.
ix.member.as_numbernumberAS holding the port.
ix.member.namestring|nullOperator name, resolved in-database from the routing dataset, falling back to the upstream string.
ix.member.rdnsstring|nullPTR recorded upstream. Compare against the live PTR from /api/v2/hop/:ip.
ix.member.peering_policystring|nullOpen, Selective, Restrictive — as declared by the member.
also_recorded_asarrayOther exchanges whose active LAN also covers this address. [] when the address is unambiguous. Always present.
also_recorded_as[].pch_idnumberThe other exchange's id. Named pch_id, not id, because it identifies a different record from ix.
also_recorded_as[].name / city / country / status / ports—The other exchange's own directory entry.
also_recorded_as[].member_countnumberMember addresses on that record. The size gap is usually how you tell the fuller entry from the thinner one.
also_recorded_as[].prefix / prefix_status / participants—The covering LAN that made it a match, as recorded against that exchange.
also_recorded_as[].pch_urlstringDeep link to the other exchange upstream.
snapshotobjectProvenance block, same shape as /api/v2/ix/snapshot.
Example requests
curl https://atlas.ipinfo.app/api/v2/ix/ip/80.249.208.247 curl https://atlas.ipinfo.app/api/v2/ix/ip/2001:7f8:1::a501:5169:1 # a LAN the directory records twice curl https://atlas.ipinfo.app/api/v2/ix/ip/206.72.210.1
Example response — 200 OK (on an exchange)
{ "ip": "80.249.208.247", "on_ix": true, "ix": { "id": 160, "name": "AMS-IX Amsterdam", "city": "Amsterdam", "country": "Netherlands", "iata": "AMS", "lat": 52.3, "lon": 4.77, "prefix": "80.249.208.0/21", "prefix_status": "Active", "participants": 893, "member": { "as_number": 15169, "name": "Google LLC", "ip": "80.249.208.247", "rdns": "core1.ams.net.google.com", "peering_policy": "Open" } }, "also_recorded_as": [], "snapshot": { /* ... */ } }
Example response — 200 OK (a LAN recorded under two exchange ids)
{ "ip": "206.72.210.1", "on_ix": true, "ix": { "id": 373, "name": "Any2 California", "city": "Los Angeles", "prefix": "206.72.210.0/23", "prefix_status": "Active", "member": null }, // the same fabric, under the exchange's current branding "also_recorded_as": [ { "pch_id": 2450, "name": "Coresite - Any2 West", "city": "Los Angeles", "country": "United States", "status": "Active", "ports": 344, "member_count": 586, "prefix": "206.72.210.0/23", "prefix_status": "Active", "participants": 344, "pch_url": "https://www.pch.net/ixp/details/2450" } ], "snapshot": { /* ... */ } }
Example response — 200 OK (not on an exchange)
{ "ip": "8.8.8.8", "on_ix": false, "ix": null, "also_recorded_as": [], "snapshot": { /* ... */ } }
Error response — 400 Bad Request
{ "error": "invalid IP address" }
GET /api/v2/ix/asn/:as_numbernew

Every internet exchange a network is present on, with the address it holds there. One entry per exchange even for a dual-stack member on several ports. Pairs naturally with /api/v2/routing/asn/:as: that says who a network exchanges traffic with, this says where it does so. Accepts 15169 or AS15169. Cached for 24 hours.

An empty list is a real answer. Membership here is what Packet Clearing House has recorded, not what is live: roughly half the exchanges in the directory have no membership on file at all, and a network may also peer entirely privately. Absence is not evidence of absence.

Query parameters
ParameterDefaultDescription
limit200Maximum exchanges returned. Clamped to 500.
Response fields
FieldTypeDescription
as_numbernumberThe AS as supplied in the URL, normalised to an integer.
countnumberExchanges returned, after limit.
exchangesarrayExchange summaries, each with the member's own ip, rdns and peering_policy at that exchange.
snapshotobjectProvenance block, same shape as /api/v2/ix/snapshot.
Example requests
curl https://atlas.ipinfo.app/api/v2/ix/asn/AS13335 curl "https://atlas.ipinfo.app/api/v2/ix/asn/15169?limit=10"
Example response — 200 OK
{ "as_number": 13335, "count": 245, "exchanges": [ { "id": 160, "name": "AMS-IX Amsterdam", "city": "Amsterdam", "country": "Netherlands", "iata": "AMS", "ip": "80.249.208.6", "rdns": "cloudflare.ams-ix.net", "peering_policy": "Open" } ], "snapshot": { /* ... */ } }
Error response — 400 Bad Request
{ "error": "invalid AS number" }
GET /api/v2/ix/:idnew

One exchange in full: where it is, its peering LANs across both address families, and the networks on them. Members come back named and ordered by their own connectivity, joined in-database against the routing dataset — so rendering a members table costs one request rather than one lookup per row, and the default page is the networks worth naming. The :id is Packet Clearing House's own exchange id, which pch_url links back to. Cached for 1 hour.

Deprecated and defunct LANs are included with their own status, because a hop on a retired peering LAN still identifies the exchange it crossed. Filter on that field if you only want live ones.

One fabric is sometimes two directory entries. Packet Clearing House occasionally carries the same physical exchange under two ids, with nothing upstream linking them: 373 “Any2 California” and 2450 “Coresite - Any2 West” are both in Los Angeles, both advertise the identical active LANs 206.72.210.0/23 and 2001:504:13::/64, and 2450's 586 member addresses are a strict subset of 373's 718. related names the other entries. It is [] for 1,296 of the 1,327 exchanges in the directory, so treat a non-empty one as a signal, not noise: counting both ids as separate exchanges inflates any total you derive.

Matched on the peering LAN, never on the name. A peering LAN is one L2 broadcast domain, so two entries advertising the same active prefix are the same fabric — a fact about the network, not a guess. Names are the opposite of a signal here: “Any2 California” and “Coresite - Any2 West” share no words, while the genuinely separate Any2 metros (Denver, Chicago, New York, each on its own LAN) share plenty. Deprecated prefixes are excluded because a retired block can be reassigned, and the prefixes must be identical rather than overlapping, because a carve-out of a larger block is a different claim.

We cross-link, we do not merge. Merging would mean electing an authoritative record, which throws away the retired-LAN history only 373 carries, and every member port is recorded against a specific id, so rewriting it would lose provenance. Both records stay exactly as published and point at each other; deciding which one to show is yours. Relations are direct neighbours, not transitive clusters — exchange 712 shares an IPv4 LAN with 2402 and an IPv6 LAN with 2382, so it lists both while 2382 lists only 712.

Query parameters
ParameterDefaultDescription
limit100Maximum member addresses. Clamped to 1000 — the largest exchange has over 6,000. member_count is unaffected, so truncation is always detectable.
members—Set to 0 to skip the member query and return the exchange and its LANs only. members comes back as [] rather than being dropped, so the response shape never changes.
q—Search the members, max 64 characters. An AS number (15169 or AS15169) matches that ASN exactly; anything else is a case-insensitive substring over network name, rDNS and address. limit still applies, to the matches.
Response fields
FieldTypeDescription
idnumberPacket Clearing House exchange id.
namestringExchange name, e.g. "AMS-IX Amsterdam".
city / country / regionstring|nullLocation. country is a name, not a code — the upstream publishes no code.
iatastring|nullNearest airport code, present for ~25% of exchanges.
lat / lonnumber|nullExchange coordinates.
websitestring|nullThe exchange's own site.
statusstringActive, Planned, Unknown, Deprecated, Defunct or Not an exchange. Every directory entry is served so an id always resolves to something explicable.
portsnumberConnected ports as recorded upstream.
traffic_bpsnumberReported peak traffic in bits per second. 0 where unreported, which is common.
updatedstring|nullWhen the upstream record was last touched, YYYY-MM-DD.
pch_urlstringDeep link to the upstream page for this exchange.
member_countnumberTotal member addresses, before limit and q.
member_querystring|nullThe member search applied, or null when members is unfiltered.
prefixesarrayPeering LANs, IPv4 first. Each has prefix, af (4 or 6), status and participants.
membersarrayNetworks on those LANs, most-connected first. Each has as_number, name, ip, rdns and peering_policy.
relatedarrayOther directory entries advertising an identical active LAN — the same fabric under another id. [] for all but 31 of the 1,327 exchanges. Always present, and unaffected by ?members=0.
related[].pch_idnumberThe other exchange's id. Named pch_id, not id, because it identifies a different record from the one you asked for.
related[].name / city / country / status / ports—The other exchange's own directory entry, as published.
related[].member_countnumberMember addresses on that record. Uncapped, so the size gap against this exchange's member_count tells you which entry is the fuller one.
related[].shared_prefixesarrayThe peering LANs both entries advertise as active. Sorted, so the array is stable between requests.
related[].pch_urlstringDeep link to the other exchange upstream.
snapshotobjectProvenance block, same shape as /api/v2/ix/snapshot.
Example requests
curl https://atlas.ipinfo.app/api/v2/ix/160 curl "https://atlas.ipinfo.app/api/v2/ix/160?limit=10" curl "https://atlas.ipinfo.app/api/v2/ix/160?members=0" # the duplicate-entry case, from either side curl "https://atlas.ipinfo.app/api/v2/ix/373?members=0" curl "https://atlas.ipinfo.app/api/v2/ix/2450?members=0"
Example response — 200 OK
{ "id": 160, "name": "AMS-IX Amsterdam", "city": "Amsterdam", "country": "Netherlands", "region": "Europe", "iata": "AMS", "lat": 52.3, "lon": 4.77, "website": "https://www.ams-ix.net/ams", "status": "Active", "ports": 893, "pch_url": "https://www.pch.net/ixp/details/160", "member_count": 2206, "prefixes": [ { "prefix": "80.249.208.0/21", "af": 4, "status": "Active", "participants": 893 }, { "prefix": "195.69.144.0/22", "af": 4, "status": "Deprecated", "participants": 0 }, { "prefix": "2001:7f8:1::/64", "af": 6, "status": "Active", "participants": 892 } ], // no other directory entry advertises these LANs "related": [], "members": [ { "as_number": 6939, "name": "Hurricane Electric LLC", "ip": "80.249.209.150", "rdns": "amsix-400gbps.core1.ams1.he.net", "peering_policy": "Open" }, { "as_number": 15169, "name": "Google LLC", "ip": "80.249.209.100", "rdns": "core2.ams.net.google.com", "peering_policy": "Open" } ], "snapshot": { /* ... */ } }
Example response — 200 OK (one fabric, two ids)
# GET /api/v2/ix/373?members=0 { "id": 373, "name": "Any2 California", "city": "Los Angeles", "status": "Active", "ports": 322, // 2009 — the record predates the CoreSite rebrand "updated": "2009-06-22", "member_count": 718, "prefixes": [ { "prefix": "206.72.210.0/23", "af": 4, "status": "Active", "participants": 322 }, // history only this record carries — one reason we do not merge { "prefix": "206.223.143.0/24", "af": 4, "status": "Deprecated", "participants": 449 }, { "prefix": "2001:504:13::/64", "af": 6, "status": "Active", "participants": 301 } ], "related": [ { "pch_id": 2450, "name": "Coresite - Any2 West", "city": "Los Angeles", "country": "United States", "status": "Active", "ports": 344, "member_count": 586, "shared_prefixes": ["2001:504:13::/64", "206.72.210.0/23"], "pch_url": "https://www.pch.net/ixp/details/2450" } ], "members": [], "snapshot": { /* ... */ } } # and symmetrically, GET /api/v2/ix/2450 → related[0].pch_id == 373
Response — 404 Not Found (unknown exchange)
{ "error": "unknown exchange", "id": 999999, "snapshot": { /* ... */ } }
Error response — 400 Bad Request
{ "error": "invalid exchange id" }
// API v2 — RDAP (registry allocations)
GET /api/v2/rdap/snapshotnew

Provenance for the registry-allocation dataset this instance is serving. Every other /api/v2/rdap/* response embeds the same block. Fetch this once to decide whether the registry endpoints are worth calling at all: 404 means no registry data is loaded here, which is a normal state on a database image built before these tables existed, and not an error. Cached for 1 hour.

Nothing here speaks RDAP. These endpoints answer the subset of questions an RDAP lookup is normally used for, out of a local table built from the five RIRs' daily delegated-extended statistics files. The point is that a consumer can stop making five third-party requests per page view.

Response fields
FieldTypeDescription
collected_atstringISO 8601 timestamp of the collection run, not of the image build.
sourcestringUpstream dataset description.
source_urlsobjectThe five files, keyed by registry. There is no single licence covering all of them, so the per-registry URL is the provenance — check each registry's own conditions of use before redistributing.
registriesobjectPer-registry serial, generation date and row counts. This is how you tell a fresh answer from one built on a registry that quietly stopped republishing.
alloc_rowsnumberAddress allocations loaded, both families.
asn_rowsnumberAS-number allocation ranges loaded.
whoisobject|nullProvenance for the bulk-whois name dataset (RIPE NCC, APNIC, AFRINIC RPSL dumps): registries, per-dump dumps counts and Last-Modified, rows, and rows_gated (placeholder objects removed at build — see rdap/ip). null when this image has no name data.
Example request
curl https://atlas.ipinfo.app/api/v2/rdap/snapshot
GET /api/v2/rdap/ip/:ipnew

Which registry holds this address, in which country, since when, and under what status. IPv4 or IPv6. Cached for 1 hour.

Read the fields_absent array before rendering. The delegated files carry no network name, organisation name or abuse contact, so those fields are not merely null here — they are not in the dataset. Rendering their absence as a fact about the network ("this network has no abuse contact") would be false. The array names them explicitly on every response so there is no need to guess.

Names come from two sources, and the response says which. whois is the registry object from the free bulk RPSL dumps of RIPE NCC, APNIC and AFRINIC, baked into this instance and always answered locally. ARIN and LACNIC publish no anonymous bulk dump, so their space is named only by detail, the lazily-filled RDAP cache (a cold address gets detail: null and a background fetch). netname is the convenience pick — bulk record first, RDAP cache second — with source naming which. fields_absent narrows to whatever is actually present; the bulk dumps never carry an abuse contact.

Two limits on the bulk record. It is the most specific object within what is loaded: end-user assignments smaller than /24 (IPv4) or /48 (IPv6) — some 5.4 million objects, mostly /29s — are left out, so an address inside one gets the covering LIR object instead. And placeholder objects every dump carries for space the registry does not manage (IANA-BLK over 0.0.0.0/0, APNIC's RIPE-CIDR-BLOCK over 193/8, and so on) are removed at build time by requiring both ends of an object to fall in one holder's delegation at the same registry.

status: "available" or "reserved" is a real answer, meaning the registries positively record that nobody holds this space. That is different from found: false, which means the address falls outside what the RIRs publish at all — IANA special-purpose space, roughly 14% of IPv4 (multicast 224/4, reserved 240/4, 0/8, 127/8 and friends). Both are 200s; a 404 is reserved for "no dataset loaded".

Response fields
FieldTypeDescription
querystringThe address as parsed.
foundbooleanWhether any delegated-statistics record covers it.
allocation.prefixstringThe covering CIDR as the registry publishes it.
allocation.rirstringarin | ripencc | apnic | lacnic | afrinic.
allocation.ccstringISO alpha-2, or null when not recorded.
allocation.statusstringallocated | assigned | available | reserved.
allocation.allocatedstringDelegation date (YYYY-MM-DD), or null.
allocation.opaque_idstringHolder id. Unique within its own registry only — always pair it with rir.
allocation.delegatedbooleanTrue for allocated/assigned; false for space nobody holds.
netnameobject|null{ value, source, start_address, end_address } — the network name, with source whois-bulk or rdap-cache and the range of the object it came from. null when neither source has one.
whoisobject|nullBulk RPSL record: found, covered (whether this address's registry publishes a dump), record (start_address, end_address, rir, netname, country, status, org_handle, organisation, last_changed), a note explaining any miss, and source. null when this image has no name data.
detailobject|nullRDAP-cache record (name, organisation, abuse contact), when one has been fetched. Unchanged meaning: always the RDAP cache, never the bulk record.
fields_absentarrayRDAP fields this response cannot supply. See above.
sourceobjectThe snapshot block.
Example request
curl https://atlas.ipinfo.app/api/v2/rdap/ip/8.8.8.8 curl https://atlas.ipinfo.app/api/v2/rdap/ip/2a00:8c40:f0c0::1
Example response — 200 OK
{ "query": "8.8.8.8", "found": true, "allocation": { "prefix": "8.8.8.0/24", "rir": "arin", "cc": "US", "status": "allocated", "allocated": "2023-12-28", /* ... */ } }
Example response — 200 OK (bulk-whois name)
{ "query": "2a00:8c40:f0c0::1", "allocation": { "prefix": "2a00:8c40::/32", "rir": "ripencc", /* ... */ }, "netname": { "value": "MOZILLA-FIREFOX-VPN", "source": "whois-bulk", "start_address": "2a00:8c40:f000::", "end_address": "2a00:8c40:ffff:ffff:ffff:ffff:ffff:ffff" }, "whois": { "found": true, "covered": true, "record": { "netname": "MOZILLA-FIREFOX-VPN", "status": "ALLOCATED-BY-LIR", "country": "US", /* ... */ } }, "detail": null, "fields_absent": ["organisation", "abuse_contact"] }
Error response — 400 Bad Request
{ "error": "invalid IP address" }
GET /api/v2/rdap/prefix/:cidrnew

The registry record covering an entire CIDR block. Same response shape as /api/v2/rdap/ip/:ip, including netname and whois — where the bulk record, too, must contain the whole block. Cached for 1 hour.

This is not the same question as asking about the block's first address, and the difference bites. A block can span several allocations and be covered by none of them: 8.8.0.0/16 contains 12 separate registry records, so looking up 8.8.0.0 answers 8.8.8.0/22 with every appearance of confidence. This endpoint requires containment of the whole block and returns found: false when no single record covers it, which is the truthful answer.

Host bits are tolerated: 8.8.8.8/24 is read as the /24 containing that address.

Example requests
curl https://atlas.ipinfo.app/api/v2/rdap/prefix/8.8.8.0/24 curl https://atlas.ipinfo.app/api/v2/rdap/prefix/2606:4700::/32
Error response — 400 Bad Request
{ "error": "invalid CIDR prefix" }
GET /api/v2/rdap/asn/:as_numbernew

The registry record covering an AS number. Accepts 15169 or AS15169. Cached for 1 hour.

Allocations are stored as closed ranges, so the response carries as_start and as_end rather than a single number — registries hand out 32-bit AS numbers in blocks, and the range is the record that actually exists. A single assignment simply has both ends equal.

Example request
curl https://atlas.ipinfo.app/api/v2/rdap/asn/AS15169
Example response — 200 OK
{ "query": 15169, "found": true, "allocation": { "as_start": 15169, "as_end": 15169, "rir": "arin", "cc": "US", "allocated": "2000-03-30", /* ... */ } }
GET /api/v2/rdap/holder/:rir/:opaque_idnew

Every other prefix and AS number the same holder has, taken from the opaque_id on any allocation response. This is the one field the delegated files give that RDAP cannot cheaply be asked for in bulk: it groups a registrant's resources without a name ever being involved. ?limit= caps each list (default 100, max 1000); the counts stay uncapped. Cached for 1 hour.

The registry is part of the key, not decoration. An opaque id is unique within one registry only — LACNIC publishes small integers, ARIN hex digests, RIPE UUIDs — and nothing coordinates them, so two registries can emit the same string for unrelated organisations. An endpoint keyed on the id alone would silently merge them, which is why this one will not accept it.

Response fields
FieldTypeDescription
prefixesarrayAddress allocations held, up to limit.
asnsarrayAS-number ranges held, up to limit.
prefix_countnumberTotal held, uncapped.
asn_countnumberTotal held, uncapped.
Example request
curl https://atlas.ipinfo.app/api/v2/rdap/holder/arin/9d99e3f7d38d1b8026f2ebbea4017c9f
Error response — 400 Bad Request
{ "error": "invalid holder id" }
// API v2 — RPKI (route origin validation)
GET /api/v2/rpki/snapshotnew

Provenance and global counts for the RPKI dataset this instance is serving. Every other /api/v2/rpki/* response embeds the same block. Fetch this once to decide whether the RPKI endpoints are worth calling at all: 404 means no RPKI data is loaded here, which is a normal state on a database image built without it, and not an error. Cached for 1 hour.

Validity is a statement about one snapshot. The VRPs are rpki-client's validated output as of generated_at, and the route states are RFC 6811 origin validation precomputed at build time for every announcement in the loaded routing snapshot — not re-evaluated per request. ROAs are issued and revoked continuously, so show the date beside any badge you render. not_found — no ROA covers the route — is the normal state for a large share of the table (about a quarter of it at the time of writing) and is not a fault.

Response fields
FieldTypeDescription
generated_atstringISO 8601 time rpki-client built the VRP set — how fresh the validation is, not when it was downloaded.
sourcestringThe export the VRPs were read from.
vrpsnumberValidated ROA payloads loaded, after de-duplication.
aspasnumberASPA records loaded. Small by design — see /api/v2/rpki/asn/:as.
by_taobjectVRPs per trust anchor: afrinic, apnic, arin, lacnic, ripencc.
routesobject|nullObserved announcements per state: valid, invalid, not_found. null — not zeros — when the build had no routing data to validate, so "not computed" never reads as "no invalid routes".
Example request
curl https://atlas.ipinfo.app/api/v2/rpki/snapshot
Example response — 200 OK
{ "generated_at": "2026-09-27T15:00:29.000Z", "source": "https://console.rpki-client.org/vrps.json", "vrps": 1014198, "aspas": 3269, "by_ta": { "arin": 272956, "apnic": 298532, "lacnic": 45169, "afrinic": 31877, "ripencc": 365664 }, "routes": { "valid": 1037459, "invalid": 6000, "not_found": 390082 } }
Response — 404 Not Found (no RPKI data loaded)
{ "error": "no RPKI snapshot loaded" }
GET /api/v2/rpki/validate?prefix=:cidr&asn=:as_numbernew

RFC 6811 route-origin validation of any (prefix, origin) pair against the loaded VRP set — an announcement that exists or one you are only planning, which is what makes it useful for "would this be accepted?" before it is made. Evaluated live against the VRPs, unlike the precomputed states on the other RPKI endpoints. Cached for 1 hour.

valid: a covering VRP names this origin and allows this length. invalid: covered, but not valid — reason length when a VRP names the right origin but the route is more specific than its max_length, otherwise as (including routes covered only by an AS0 ROA, which authorises nobody). not-found: no VRP covers the prefix at all; that is the normal state for much of the table and not an error. An invalid is a mismatch with what the address holder published, not proof of a hijack — stale or overly tight ROAs are the usual cause.

Query parameters
ParameterDefaultDescription
prefix—Required. IPv4 or IPv6 CIDR; host bits are zeroed and the canonical form echoed back. A bare address is read as a host route (/32 or /128).
asn—Required. Origin to test, 13335 or AS13335. 0 is refused: AS0 is a ROA marker, never a real origin.
Response fields
FieldTypeDescription
prefixstringThe canonical prefix validated.
asnnumberThe origin validated.
statestringvalid | invalid | not-found.
reasonstring|nulllength | as when invalid; null otherwise.
vrpsarrayCovering VRPs, most specific first and this origin's first within a length, each { prefix, max_length, asn, ta }. Capped at 50; the verdict is computed over all of them, so the cap never changes state.
snapshotobjectProvenance block, same shape as /api/v2/rpki/snapshot.
Example requests
curl "https://atlas.ipinfo.app/api/v2/rpki/validate?prefix=1.1.1.0/24&asn=13335" curl "https://atlas.ipinfo.app/api/v2/rpki/validate?prefix=1.1.1.0/25&asn=AS13335" # too specific curl "https://atlas.ipinfo.app/api/v2/rpki/validate?prefix=1.1.1.0/24&asn=15169" # wrong origin
Example response — 200 OK (valid)
{ "prefix": "1.1.1.0/24", "asn": 13335, "state": "valid", "reason": null, "vrps": [ { "prefix": "1.1.1.0/24", "max_length": 24, "asn": 13335, "ta": "apnic" } ], "snapshot": { /* ... */ } }
Example response — 200 OK (invalid, and not-found)
// prefix=1.1.1.0/25&asn=13335 — right origin, longer than max_length 24 { "prefix": "1.1.1.0/25", "asn": 13335, "state": "invalid", "reason": "length", "vrps": [ /* the same /24 VRP */ ], "snapshot": { /* ... */ } } // prefix=192.0.2.0/24&asn=64500 — no ROA covers it; normal, not a fault { "prefix": "192.0.2.0/24", "asn": 64500, "state": "not-found", "reason": null, "vrps": [], "snapshot": { /* ... */ } }
Error response — 400 Bad Request
{ "error": "invalid or missing prefix" } // or "invalid or missing asn"
GET /api/v2/rpki/vrps?prefix=:cidrnew

Every VRP covering a prefix, whatever AS it names — "which ROAs govern this space, for whom, and how specific may they go", without guessing an origin for /api/v2/rpki/validate first. An empty list means no ROA covers the prefix, so any announcement of it is not-found. Cached for 1 hour.

A VRP naming AS 0 is an AS0 ROA: the holder is saying nobody may originate that space, so it invalidates any route it covers that no other VRP authorises.

Query parameters
ParameterDefaultDescription
prefix—Required. IPv4 or IPv6 CIDR; host bits are zeroed and the canonical form echoed back. A bare address is read as a host route.
Response fields
FieldTypeDescription
prefixstringThe canonical prefix looked up.
vrpsarrayCovering VRPs, most specific first, then by AS, each { prefix, max_length, asn, ta }. Capped at 200.
countnumberEvery covering VRP, uncapped. Compare against the length of vrps to detect truncation.
snapshotobjectProvenance block, same shape as /api/v2/rpki/snapshot.
Example requests
curl "https://atlas.ipinfo.app/api/v2/rpki/vrps?prefix=151.244.248.0/24" curl "https://atlas.ipinfo.app/api/v2/rpki/vrps?prefix=2606:4700::/32" # no ROA covers it: []
Example response — 200 OK
{ "prefix": "151.244.248.0/24", "vrps": [ { "prefix": "151.244.248.0/24", "max_length": 24, "asn": 834, "ta": "ripencc" }, { "prefix": "151.244.248.0/23", "max_length": 24, "asn": 834, "ta": "ripencc" }, { "prefix": "151.244.128.0/17", "max_length": 24, "asn": 199925, "ta": "ripencc" } ], "count": 3, "snapshot": { /* ... */ } }
Error response — 400 Bad Request
{ "error": "invalid or missing prefix" }
GET /api/v2/rpki/asn/:as_number?limit=:nnew

Everything RPKI says about one AS: the precomputed state of each announcement it originates, the ROAs that name it, and its ASPA record. Accepts 13335 or AS13335. An AS that publishes nothing is a 200 with zeros and empty lists — "this network publishes no RPKI" is the answer, not a miss. Cached for 1 hour.

ASPA is new and rare — aspa: null means nothing. Only a few thousand networks have published an Autonomous System Provider Authorization at all, so its absence is not a misconfiguration and must not be rendered as one. When present, providers lists the upstreams the AS has authorised; a single provider of 0 is the AS declaring that it has no providers.

prefixes and roas.rows are each capped at limit (default and maximum 5,000); summary.total and roas.count are the true counts beside them, so truncation is always detectable. prefixes lists problems first — invalid, then not-found, then valid — so a cap never hides the routes that need attention. Note the spelling: the per-route state is not-found while the summary key is not_found.

Query parameters
ParameterDefaultDescription
limit5000Cap on prefixes and roas.rows, 0-50000; above 50000 is clamped. Ask for more than the default when building a router filter: 16 networks have more than 5,000 ROAs. 0 skips both lists and returns only summary, roas.count and aspa — the cheap form for a badge or a count. Non-numeric is a 400.
Response fields
FieldTypeDescription
as_numbernumberThe AS as supplied, normalised to an integer.
summaryobjectvalid, invalid, not_found and total across every announcement this AS originates. Uncapped.
prefixesarrayAnnouncements, invalid first, then not-found, then valid, each group by prefix. Each { prefix, state, reason, peers_seen }; state and reason as on /api/v2/rpki/validate.
roas.countnumberVRPs naming this AS, uncapped.
roas.rowsarrayThose VRPs by prefix, each { prefix, max_length, ta }.
aspaobject|null{ providers: [{ asn, name }] }, or null when the AS has no ASPA record — the common case.
snapshotobjectProvenance block, same shape as /api/v2/rpki/snapshot.
Example requests
curl https://atlas.ipinfo.app/api/v2/rpki/asn/AS8283 curl https://atlas.ipinfo.app/api/v2/rpki/asn/13335 curl "https://atlas.ipinfo.app/api/v2/rpki/asn/13335?limit=0" # summary and counts only
Example response — 200 OK
{ "as_number": 8283, "summary": { "valid": 4, "invalid": 0, "not_found": 0, "total": 4 }, "prefixes": [ { "prefix": "91.208.34.0/24", "state": "valid", "reason": null, "peers_seen": 39 }, { "prefix": "94.142.240.0/21", "state": "valid", "reason": null, "peers_seen": 39 } // ... 4 in total ], "roas": { "count": 17, "rows": [ { "prefix": "91.208.34.0/24", "max_length": 24, "ta": "ripencc" }, { "prefix": "94.142.240.0/21", "max_length": 21, "ta": "ripencc" } // ... ] }, "aspa": { "providers": [ { "asn": 24785, "name": "Broadband Hosting B.V." }, { "asn": 38930, "name": "LeaseWeb Network B.V." } // ... 5 in total ] }, "snapshot": { /* ... */ } }
Example response — 200 OK (ASPA declaring no providers)
{ "as_number": 1299, /* ... */ "aspa": { "providers": [ { "asn": 0, "name": null } ] } }
Error response — 400 Bad Request
{ "error": "invalid AS number" } // or "invalid limit"
GET /api/v2/rpki/invalidnew

Every RPKI-invalid announcement in the loaded routing snapshot, most widely seen first — the routes actually propagating despite failing validation rank highest, and the ones most networks already filter sink to the bottom. States are the build-time precompute described on /api/v2/rpki/snapshot. Cached for 1 hour.

An entry here is a mismatch, not an accusation. It means the announcement disagrees with the ROAs its address holder published at snapshot.generated_at; stale ROAs and traffic-engineering more-specifics beyond max_length are far more common causes than hijacks. That is why the list carries its snapshot.

Query parameters
ParameterDefaultDescription
limit100Rows returned. Above 500 is clamped to 500; 0 or anything non-numeric is a 400.
offset0Rows to skip. Non-numeric is a 400.
Response fields
FieldTypeDescription
totalnumberInvalid announcements in the whole table, before paging.
rowsarrayOrdered by peers_seen descending, then prefix and origin, so paging is stable.
rows[].prefix / origin_asnstring / numberThe announcement.
rows[].namestring|nullOperator name for the origin, already resolved.
rows[].reasonstringas (origin not authorised) or length (authorised origin, too specific).
rows[].peers_seennumberCollector peers that saw it. Weigh against the routing snapshot.peer_count.
snapshotobjectProvenance block, same shape as /api/v2/rpki/snapshot.
Example requests
curl https://atlas.ipinfo.app/api/v2/rpki/invalid curl "https://atlas.ipinfo.app/api/v2/rpki/invalid?limit=500&offset=500"
Example response — 200 OK
{ "total": 6000, "rows": [ { "prefix": "23.163.8.0/24", "origin_asn": 211759, "name": "Miku Network Technology Limited", "reason": "as", "peers_seen": 40 }, { "prefix": "23.163.12.0/24", "origin_asn": 10753, "name": "Level 3 Parent, LLC", "reason": "as", "peers_seen": 40 } // ... up to limit ], "snapshot": { /* ... */ } }
Error response — 400 Bad Request
{ "error": "invalid limit" } // or "invalid offset"
// API v2 — IRR (routing registry objects)
GET /api/v2/irr/snapshotnew

Provenance and headline counts for the Internet Routing Registry dataset: route objects and as-sets from eight registries — RADB, RIPE, ARIN, APNIC, LACNIC, AFRINIC, ALTDB and NTTCOM. Every other /api/v2/irr/* response embeds the same block. 404 means no IRR data is loaded here, a normal state and not an error. Cached for 1 hour.

A route object is a claim, not an authorisation. IRR data is self-asserted: anyone with an account on a registry like RADB can register an object for any prefix. registered means an object exists — the thing IRR-based prefix filters are built from — not that the announcement is legitimate. RPKI is the dataset for that question.

Each observed announcement gets one precomputed state, first match wins: registered (an object with the same prefix and origin, in any registry) > covered (a less-specific object with the same origin) > mismatch (objects for the exact prefix, but only with other origins) > missing (none of those).

Response fields
FieldTypeDescription
generated_atstringISO 8601 time the collector assembled the dumps.
sourcesobjectRoute objects (route + route6) per registry.
routesnumberRoute objects loaded across all registries.
as_setsnumberas-set objects loaded.
route_statesobject|nullObserved announcements per state (registered, covered, mismatch, missing). null when the build had no routing data to compare against.
registriesobjectPer registry: dump serial (may be null) and last_modified. This is how you spot a registry that quietly stopped publishing.
failedobjectOptional registries that failed this build, keyed by name. {} when all loaded.
Example request
curl https://atlas.ipinfo.app/api/v2/irr/snapshot
Example response — 200 OK
{ "generated_at": "2026-09-27T14:46:59.891Z", "sources": { "APNIC": 1945006, "RADB": 1344792, "RIPE": 582665, "NTTCOM": 287302, "ARIN": 200158, "AFRINIC": 128005, "ALTDB": 36281, "LACNIC": 20234 }, "routes": 4544310, "as_sets": 59739, "route_states": { "registered": 990474, "covered": 307024, "mismatch": 13774, "missing": 122269 }, "registries": { "RADB": { "serial": "6241566", "last_modified": "2026-09-26T18:22:13.000Z" }, "AFRINIC": { "serial": null, "last_modified": "2026-09-27T00:06:19.000Z" } // ... one per registry }, "failed": {} }
Response — 404 Not Found (no IRR data loaded)
{ "error": "no IRR snapshot loaded" }
GET /api/v2/irr/prefix/:cidrnew

Route objects registered for a prefix: every object for the exact prefix in any registry, the less-specific objects covering it, and how many more-specific objects sit inside it. The prefix length is required (a bare address is a 400); host bits are zeroed and the canonical prefix echoed back. Empty lists are a real answer, not a 404. Cached for 24 hours.

Two objects for one prefix and origin in different registries — or objects for different origins — are normal: nothing reconciles the registries, and stale objects are rarely cleaned up. Show source, mnt_by and last_modified so a reader can judge each claim.

Response fields
FieldTypeDescription
prefixstringThe canonical prefix looked up.
exactarrayEvery route object for exactly this prefix, by origin then registry. Uncapped.
coveringarrayLess-specific route objects, most specific first. Capped at 50.
covering_countnumberEvery less-specific route object, uncapped — compare against the length of covering to detect truncation.
more_specific_countnumberRoute objects strictly inside the prefix. A count only; query a more-specific prefix to see them.
exact[] / covering[]objectprefix, origin (number), source (registry), descr, mnt_by and last_modified (YYYY-MM-DD) — all as registered, any of the text fields may be null.
snapshotobjectProvenance block, same shape as /api/v2/irr/snapshot.
Example requests
curl https://atlas.ipinfo.app/api/v2/irr/prefix/1.1.1.0/24 curl https://atlas.ipinfo.app/api/v2/irr/prefix/2606:4700::/32
Example response — 200 OK
{ "prefix": "1.1.1.0/24", "exact": [ { "prefix": "1.1.1.0/24", "origin": 13335, "source": "APNIC", "descr": "APNIC Research and Development", "mnt_by": "MAINT-APNICRANDNET", "last_modified": "2023-04-26" }, // the same claim again, filed in another registry by someone else { "prefix": "1.1.1.0/24", "origin": 13335, "source": "RADB", "descr": "QRATOR via EMIX", "mnt_by": "MAINT-AS8966", "last_modified": "2023-11-13" } ], "covering": [], "covering_count": 0, "more_specific_count": 0, "snapshot": { /* ... */ } }
Error response — 400 Bad Request
{ "error": "invalid CIDR prefix" }
GET /api/v2/irr/asn/:as_number?limit=:nnew

The IRR view of one origin AS: the precomputed state of every announcement it originates, every route object naming it as origin (flagged by whether it is actually announced), and the as-sets that list it directly. Accepts 13335 or AS13335. An AS with nothing registered is a 200 with zeros. Cached for 1 hour.

prefixes and route_objects.rows are capped at limit (default and maximum 5,000), as_sets at 5,000; summary.total, route_objects.count and as_sets_count are the true counts beside them. Large networks exceed these caps — AS13335 has over 56,000 route objects naming it — so both lists put problems first: prefixes runs mismatch, missing, covered, registered, and route_objects.rows lists objects that are not announced (often stale) before announced ones.

Query parameters
ParameterDefaultDescription
limit5000Cap on prefixes and route_objects.rows, 0-50000; above 50000 is clamped. 0 skips both lists and returns the summary, counts and as_sets. Non-numeric is a 400.
Response fields
FieldTypeDescription
as_numbernumberThe AS as supplied, normalised to an integer.
summaryobjectregistered, covered, mismatch, missing and total over every announcement this AS originates. Uncapped.
prefixesarrayAnnouncements, mismatch first, then missing, covered, registered, each group by prefix. Each { prefix, state }; states as defined on /api/v2/irr/snapshot.
route_objects.countnumberRoute objects with this origin across all registries, uncapped.
route_objects.not_announcednumberHow many of those this AS was not seen originating, uncapped.
route_objects.rowsarrayNot-announced objects first, then by prefix. Each { prefix, source, descr, mnt_by, last_modified, announced }. announced: false is an object for a route this AS was not seen originating — often stale.
as_setsarray{ name, source } for every as-set listing this AS as a direct member, capped at 5,000. Not recursive: a set that includes it only through a nested set is not here.
as_sets_countnumberSets listing this AS directly, uncapped.
snapshotobjectProvenance block, same shape as /api/v2/irr/snapshot.
Example requests
curl https://atlas.ipinfo.app/api/v2/irr/asn/AS8283 curl "https://atlas.ipinfo.app/api/v2/irr/asn/13335?limit=200" curl "https://atlas.ipinfo.app/api/v2/irr/asn/13335?limit=0" # summary and counts only
Example response — 200 OK
{ "as_number": 8283, "summary": { "registered": 4, "covered": 0, "mismatch": 0, "missing": 0, "total": 4 }, "prefixes": [ { "prefix": "185.52.224.0/22", "state": "registered" }, { "prefix": "2a02:898::/32", "state": "registered" } // ... 4 in total ], "route_objects": { "count": 6, "not_announced": 2, "rows": [ { "prefix": "203.56.44.0/24", "source": "APNIC", "descr": "Cryptsoft Pty Ltd", "mnt_by": "MAINT-CRYPTSOFTPTYLTD-AU", "last_modified": "2019-05-02", "announced": false }, // ... the other not-announced object, then the announced ones { "prefix": "94.142.240.0/21", "source": "RIPE", "descr": "Netwerkvereniging Coloclue, Amsterdam, Netherlands", "mnt_by": "COLOCLUE-MNT", "last_modified": "2009-01-14", "announced": true } // ... ] }, "as_sets": [ { "name": "AS-AMS-IX-RS-SETS-FALCON", "source": "RIPE" }, { "name": "AS-AS260-PEERS", "source": "RIPE" } // ... ], "as_sets_count": 46, "snapshot": { /* ... */ } }
Error response — 400 Bad Request
{ "error": "invalid AS number" } // or "invalid limit"
GET /api/v2/irr/as-set/:name?asn_limit=:nnew

An as-set's objects in every registry that has one by that name, and its recursive expansion to AS numbers — the list a bgpq4-style filter would be built from. Names are case-insensitive and may be hierarchical (AS8283:AS-COLOCLUE); at least one component must be an AS- set name, so AS13335 or RS-FOO is a 400. Cached for 1 hour.

The expansion is bounded and says so. Real as-sets nest deeply and loop, so the walk stops at depth 6 below the root, 2,000 sets visited, or 50,000 ASNs, and sets truncated: true whenever any bound cut it short — the big transit sets hit these. Never present a truncated expansion as complete. Members are merged across every registry holding a set of that name, which is what bgpq4 does when pointed at all sources — but the same name in two registries can belong to two different operators, so check objects when that matters. Expansions are cached per server process, so repeat requests for a big set are cheap.

Query parameters
ParameterDefaultDescription
asn_limit50000Cap on the inline expanded.asns list only, 0-50000; above is clamped. expanded.count and truncated still describe the whole expansion. Non-numeric is a 400.
Response fields
FieldTypeDescription
namestringThe set name, upper-cased.
foundbooleantrue on a 200.
objectsarrayOne per registry holding the set, each { source, descr, members }. members is the raw list: AS numbers and nested set names.
expanded.asnsnumber[]Every AS number reached, ascending, up to asn_limit.
expanded.countnumberAS numbers the expansion reached (within its bounds), whatever asn_limit trimmed from asns.
expanded.sets_visitednumberSets looked up, including the root.
expanded.depthnumberDeepest nesting level reached below the root.
expanded.truncatedbooleantrue when a depth, set or ASN bound stopped the walk. count is then a lower bound.
snapshotobjectProvenance block, same shape as /api/v2/irr/snapshot.
Example requests
curl https://atlas.ipinfo.app/api/v2/irr/as-set/AS8283:AS-COLOCLUE curl https://atlas.ipinfo.app/api/v2/irr/as-set/as-cloudflare curl https://atlas.ipinfo.app/api/v2/irr/as-set/AS-RETN # large enough to be truncated curl "https://atlas.ipinfo.app/api/v2/irr/as-set/AS-HURRICANE?asn_limit=100" # count stays 25657
Example response — 200 OK
{ "name": "AS8283:AS-COLOCLUE", "found": true, "objects": [ { "source": "RIPE", "descr": "Netwerkvereniging Coloclue", "members": ["AS8283", "AS-PEERING-TESTBED", "AS-RVDM", "AS8298:AS-IPNG" /* ... 57 in total */] } ], "expanded": { "asns": [112, 234, 267 /* ... */], "count": 134, "sets_visited": 70, "depth": 4, "truncated": false }, "snapshot": { /* ... */ } }
Example response — 200 OK (bounded)
// GET /api/v2/irr/as-set/AS-RETN — the 2,000-set bound stopped the walk { "name": "AS-RETN", "found": true, "objects": [ /* ... */ ], "expanded": { "asns": [ /* ... */ ], "count": 29981, "sets_visited": 2000, "depth": 2, "truncated": true }, "snapshot": { /* ... */ } }
Response — 404 Not Found (no such set)
{ "error": "as-set not found", "name": "AS-NOSUCHSET", "found": false, "snapshot": { /* ... */ } }
Error response — 400 Bad Request
{ "error": "invalid as-set name" }
GET /api/v2/irr/as-set/:name/prefixesnew

The bgpq4-style prefix list for an as-set: expand it exactly as /api/v2/irr/as-set/:name does (same bounds, same cache), then every distinct route / route6 prefix whose origin is in the expansion, IPv4 first then IPv6, each in address order. This is the list an IRR-based filter would accept for the set — self-asserted claims, not a statement of legitimacy. Cached for 1 hour.

truncated: true means the filter is incomplete — either the expansion hit a bound (asns.truncated) or limit cut the list (count is larger than the rows returned). Big transit sets run to millions of prefixes: AS-HURRICANE expands to 25,657 ASNs and 1.8 million distinct prefixes.

Query parameters
ParameterDefaultDescription
afall4, 6 or all. Anything else is a 400.
limit100000Prefixes returned, 0-200000; above is clamped. 0 returns only the counts. Non-numeric is a 400.
Response fields
FieldTypeDescription
namestringThe set name, upper-cased.
foundbooleantrue on a 200.
asns.countnumberAS numbers in the expansion.
asns.truncatedbooleanA depth, set or ASN bound stopped the expansion.
prefixesarray{ prefix } per distinct prefix, IPv4 then IPv6, in address order, up to limit.
countnumberDistinct prefixes in total for this af, uncapped.
truncatedbooleanasns.truncated, or count exceeds the rows returned.
sources_usedstring[]Registries whose route objects contributed at least one prefix.
snapshotobjectProvenance block, same shape as /api/v2/irr/snapshot.
Example requests
curl https://atlas.ipinfo.app/api/v2/irr/as-set/AS8283:AS-COLOCLUE/prefixes curl "https://atlas.ipinfo.app/api/v2/irr/as-set/AS-CLOUDFLARE/prefixes?af=6" curl "https://atlas.ipinfo.app/api/v2/irr/as-set/AS-HURRICANE/prefixes?limit=0" # counts only
Example response — 200 OK
// GET /api/v2/irr/as-set/AS8283:AS-COLOCLUE/prefixes?af=4&limit=3 { "name": "AS8283:AS-COLOCLUE", "found": true, "asns": { "count": 134, "truncated": false }, "prefixes": [ { "prefix": "5.56.133.0/24" }, { "prefix": "5.175.204.0/24" }, { "prefix": "23.128.24.0/24" } ], "count": 367, "truncated": true, // limit=3 cut the list "sources_used": ["ALTDB", "APNIC", "ARIN", "LACNIC", "NTTCOM", "RADB", "RIPE"], "snapshot": { /* ... */ } }
Response — 404 Not Found (no such set)
{ "error": "as-set not found", "name": "AS-NOSUCHSET", "found": false, "snapshot": { /* ... */ } }
Error response — 400 Bad Request
{ "error": "af must be one of: 4, 6, all" } // or "invalid as-set name", "invalid limit"
// API v2 — Operations
GET /api/v2/datasetsnew

Which baked datasets the database behind this instance actually has: for each dataset, whether every table it needs is present, with approximate row counts, plus when Postgres last started and the database size. The snapshot endpoints report provenance; this reports presence. A dataset endpoint answering 503 "tables missing" points here. Never cached.

Response fields
FieldTypeDescription
database.started_atstringWhen Postgres started. A time that keeps moving is a restart loop.
database.size_bytesnumberSize of the database.
datasets.<name>.completebooleanEvery table the dataset needs exists. Names: geo, ic3, routing, registry, ix, rpki, irr.
datasets.<name>.tablesobjectPer table: present and rows — a catalogue estimate, -1 if never analysed, null if absent.
Example request
curl https://atlas.ipinfo.app/api/v2/datasets
Example response — 200 OK
{ "database": { "started_at": "2026-09-27T19:08:08.586Z", "size_bytes": 2297697983 }, "datasets": { "rpki": { "complete": true, "tables": { "rpki_vrp": { "present": true, "rows": 1014198 } // ... } } // geo, ic3, routing, registry, ix, irr } }
// API v1 — Plain text responses
GET /api/v1/country/:ip_or_hostname

Returns the full country name as a plain text string. Ideal for shell pipelines. Accepts IPv4, IPv6, or any resolvable hostname. Cached for 7 days.

Example
curl https://atlas.ipinfo.app/api/v1/country/1.1.1.1 Australia
GET /api/v1/asn/:ip_or_hostname

Returns the bare ASN number as plain text — no "AS" prefix.

Example
curl https://atlas.ipinfo.app/api/v1/asn/1.1.1.1 13335
GET /api/v1/continent/:ip_or_hostname

Returns the full continent name as plain text.

Example
curl https://atlas.ipinfo.app/api/v1/continent/1.1.1.1 Oceania
// Support This Project

Atlas is free and unmetered. If you're using it in something cool, consider supporting continued development.

♥ Donate