SUBNET.IM

API Documentation

A JSON API for IPv4 subnet arithmetic and BGP routing intelligence. No key, no signup, no rate limit, and CORS is open — call it from a script, a terminal, or straight from a browser.

Base URL

https://subnet.im/api/v1

Version v1. Breaking changes ship as a new version prefix; this one keeps working.

Authentication

None

No key, no header, no signup. Every response sends Access-Control-Allow-Origin: *.

OpenAPI

/api/v1/openapi.json

Generated from the same definitions as this page, so it can’t drift from what you see here.

Quick start
$ curl https://subnet.im/api/v1/subnet?ip=10.0.0.0&cidr=24$ curl https://subnet.im/api/v1/ip/8.8.8.8$ curl https://subnet.im/api/v1/as/15169

Before you start

Two tiers, two failure modes

The calculator endpoints are pure arithmetic over the input you send — they read nothing, so they cannot be stale and cannot be unavailable. The routing endpoints are served from a corpus rebuilt from RouteViews MRT archives every six hours; when that corpus is unreachable they answer 503 data_unavailable rather than pretending the answer is empty.

Empty is not the same as unknown

An empty array means we looked and there is genuinely nothing there — public space that nobody announces is a real finding, and you get a 200. Only a 503 means we could not look.

Check the timestamp

Every routing response carries meta.snapshotAt. BGP moves faster than a six-hour ingest, so treat this data as a recent observation rather than the live table, and use /status if you need to alert on staleness.

Some numbers are strings

AS numbers serialise as JSON numbers (they top out at 4294967295, which is safe). Address and /64 counts do not — a large IPv6 holding runs past Number.MAX_SAFE_INTEGER, so those fields are strings and parsing them as floats will lose digits.

Observation and inference are kept apart

Path and peer counts are things we counted. The rel field on an adjacency is BGPKIT’s inferred relationship and is null where it has none. Don’t treat the two as equivalent.

Be reasonable

There is no rate limit today because there is no key to attach one to. Responses are cached at the edge for an hour, so a polite consumer costs nothing; if you need the whole corpus, ask rather than crawling it a row at a time.

Errors

Every failure has the same shape, so you can branch on error.code without parsing prose:

Error shape
{ "error": { "code": "invalid_request", "message": "\"10.0.0.256\" is not a valid IPv4 address." } }
400invalid_request

The input could not be parsed.

404not_found

No such AS in the corpus.

503data_unavailable

The routing corpus is unreachable.

Subnet calculator

Always available

Pure arithmetic over the address you pass in. These endpoints hold no state, read no database, and answer identically whether the rest of the platform is up or not.

get

/api/v1/subnet

Calculate a subnet

Network and broadcast addresses, usable host range, masks, and host counts for an IPv4 address and prefix length. Accepts either ?ip=10.0.0.0&cidr=24 or the combined ?ip=10.0.0.0/24.

Parameters

ipstring · queryrequiredIPv4 address, optionally with the prefix length attached as a.b.c.d/n.
cidrinteger · queryoptionalPrefix length, 0–32. Required unless it was supplied as part of ip.
Request
$ curl https://subnet.im/api/v1/subnet?ip=10.0.0.0&cidr=24
Response
{ "input": { "ip": "10.0.0.0", "cidr": 24 }, "network": { "networkAddress": "10.0.0.0", "broadcastAddress": "10.0.0.255", "firstUsableHost": "10.0.0.1", "lastUsableHost": "10.0.0.254", "subnetMask": "255.255.255.0", "wildcardMask": "0.0.0.255", "cidrNotation": "10.0.0.0/24" }, "counts": { "totalHosts": 256, "usableHosts": 254, "networkBits": 24, "hostBits": 8 }, "classification": { "private": true, "special": { "cidr": "10.0.0.0/8", "label": "Private use" } } }

Errors

  • 400invalid_requestip is missing or not a valid IPv4 address, or cidr is outside 0–32.

Caching · Immutable — the answer for a given input never changes.

get

/api/v1/cheat-sheet

The full CIDR table

Every prefix length from /0 to /32 with its mask, wildcard, address count and usable host count. The same data behind the site's cheat sheet.

Request
$ curl https://subnet.im/api/v1/cheat-sheet
Response
{ "rows": [ { "cidr": 32, "subnetMask": "255.255.255.255", "wildcardMask": "0.0.0.0", "totalIPs": 1, "usableHosts": 1, "networkBits": 32, "hostBits": 0 }, { "cidr": 31, "subnetMask": "255.255.255.254", "wildcardMask": "0.0.0.1", "totalIPs": 2, "usableHosts": 2, "networkBits": 31, "hostBits": 1 } ] }

Caching · Immutable.

get

/api/v1/cidr/{length}

One prefix length

A single row of the CIDR table.

Parameters

lengthinteger · pathrequiredPrefix length, 0–32.
Request
$ curl https://subnet.im/api/v1/cidr/24
Response
{ "cidr": 24, "subnetMask": "255.255.255.0", "wildcardMask": "0.0.0.255", "totalIPs": 256, "usableHosts": 254, "networkBits": 24, "hostBits": 8 }

Errors

  • 400invalid_requestThe length is not an integer between 0 and 32.

Caching · Immutable.

get

/api/v1/ip/{address}/info

Classify an address

Whether an address is valid, which family it belongs to, and whether it falls in registry-reserved space (RFC 1918 private use, loopback, CGNAT, documentation, multicast and so on). Registry facts only — no routing data, so this never depends on the corpus.

Parameters

addressstring · pathrequiredIPv4 or IPv6 address.
Request
$ curl https://subnet.im/api/v1/ip/100.64.0.1/info
Response
{ "address": "100.64.0.1", "family": 4, "valid": true, "private": false, "special": { "cidr": "100.64.0.0/10", "label": "Carrier-grade NAT", "note": "RFC 6598 shared address space, used between a subscriber and their ISP's NAT. Not routed on the public internet." } }

Errors

  • 400invalid_requestThe address or prefix could not be parsed.

Caching · Immutable.

Routing intelligence

Needs the routing corpus

Built from RouteViews MRT archives ingested every six hours. Every response carries a meta.snapshotAt so you can see exactly how old the answer is.

get

/api/v1/ip/{address}

Look up an address

The origin AS announcing the most specific prefix covering this address, its RPKI status, the registry allocation it sits in, and which collectors see it. Returns origins: [] for public space nobody announces — that is a fact, not an error.

Parameters

addressstring · pathrequiredIPv4 or IPv6 address.
Request
$ curl https://subnet.im/api/v1/ip/8.8.8.8
Response
{ "address": "8.8.8.8", "status": "routed", "origins": [ { "prefix": "8.8.8.0/24", "asn": 15169, "name": "GOOGLE", "rpki": "valid", "peersSeen": 412, "peersTotal": 419 } ], "allocation": { "prefix": "8.0.0.0/9", "rir": "arin", "country": "US" }, "collectors": [ { "name": "route-views2", "location": "Eugene, OR", "country": "US", "peersSeen": 41, "peersTotal": 43 } ], "meta": { "snapshotAt": "2026-08-12T06:00:00.000Z" } }

Errors

  • 400invalid_requestThe address or prefix could not be parsed.
  • 503data_unavailableThe routing corpus is unreachable or has never been populated.

Caching · One hour at the edge, matching the six-hour ingest cadence.

get

/api/v1/prefix/{cidr}

Prefix detail

Origins for an exact prefix (more than one means a MOAS conflict), the covering announcement if the prefix itself is not announced, ROAs, registry allocation, parent/child/sibling prefixes, and announcement history. Pass the prefix with its slash intact: /api/v1/prefix/8.8.8.0/24.

Parameters

cidrstring · pathrequiredIPv4 or IPv6 prefix in CIDR notation.
Request
$ curl https://subnet.im/api/v1/prefix/8.8.8.0/24
Response
{ "requested": "8.8.8.0/24", "origins": [ { "prefix": "8.8.8.0/24", "asn": 15169, "name": "GOOGLE", "rpki": "valid" } ], "covering": [], "allocation": { "prefix": "8.0.0.0/9", "rir": "arin", "country": "US" }, "roas": [ { "asn": 15169, "prefix": "8.8.8.0/24", "maxLength": 24 } ], "parents": [ { "prefix": "8.8.0.0/19", "asn": 15169 } ], "children": [], "siblings": [ { "prefix": "8.8.4.0/24", "asn": 15169 } ], "meta": { "snapshotAt": "2026-08-12T06:00:00.000Z" } }

Errors

  • 400invalid_requestThe prefix could not be parsed.
  • 503data_unavailableThe routing corpus is unreachable or has never been populated.

Caching · One hour at the edge.

get

/api/v1/as/{asn}

AS overview

Name, country, registry, prefix and address counts, and observed adjacencies split into upstreams, peers and downstreams.

Parameters

asninteger · pathrequiredAutonomous system number, 0–4294967295. A leading AS is accepted.
Request
$ curl https://subnet.im/api/v1/as/15169
Response
{ "asn": 15169, "name": "GOOGLE", "description": "Google LLC", "country": "US", "rir": "arin", "counts": { "v4Prefixes": 1042, "v6Prefixes": 214, "v4Addresses": "12058624", "v6Slash64s": "4294967296", "upstreams": 96, "peers": 3841, "downstreams": 42 }, "upstreams": [ { "asn": 174, "name": "COGENT-174", "pathsSeen": 1204, "rel": "p2c" } ], "meta": { "snapshotAt": "2026-08-12T06:00:00.000Z" } }

Errors

  • 400invalid_requestThe ASN is not in range.
  • 404not_foundNo such AS in the corpus.
  • 503data_unavailableThe routing corpus is unreachable or has never been populated.

Caching · One hour at the edge.

get

/api/v1/as/{asn}/prefixes

Prefixes originated by an AS

Everything this AS is seen originating, paginated.

Parameters

asninteger · pathrequiredAutonomous system number.
familyinteger · queryoptionalRestrict to one address family: 4 or 6. Omit for both.
limitinteger · queryoptionalRows per page, 1–500. Defaults to 100.
offsetinteger · queryoptionalRows to skip. Defaults to 0.
Request
$ curl https://subnet.im/api/v1/as/15169/prefixes?family=4&limit=2
Response
{ "asn": 15169, "prefixes": [ { "prefix": "8.8.4.0/24", "family": 4, "rpki": "valid", "peersSeen": 410 }, { "prefix": "8.8.8.0/24", "family": 4, "rpki": "valid", "peersSeen": 412 } ], "page": { "limit": 2, "offset": 0, "returned": 2 }, "meta": { "snapshotAt": "2026-08-12T06:00:00.000Z" } }

Errors

  • 400invalid_requestThe ASN, family, limit or offset is out of range.
  • 404not_foundNo such AS in the corpus.
  • 503data_unavailableThe routing corpus is unreachable or has never been populated.

Caching · One hour at the edge.

get

/api/v1/as/{asn}/paths

Observed AS adjacencies

The upstream graph around an AS: oriented edges with how many paths, peers and collectors observed each one. rel is BGPKIT's inferred relationship for the pair and is null where it has none — an inference, never presented as equal to the observation.

Parameters

asninteger · pathrequiredAutonomous system number.
depthinteger · queryoptionalHow many hops out to walk, 1–3. Defaults to 2.
Request
$ curl https://subnet.im/api/v1/as/15169/paths?depth=2
Response
{ "asn": 15169, "edges": [ { "upstreamAsn": 174, "downstreamAsn": 15169, "pathsSeen": 1204, "peersSeen": 88, "collectorsSeen": 14, "rel": "p2c" } ], "names": { "174": "COGENT-174", "15169": "GOOGLE" }, "meta": { "snapshotAt": "2026-08-12T06:00:00.000Z" } }

Errors

  • 400invalid_requestThe ASN or depth is out of range.
  • 404not_foundNo such AS in the corpus.
  • 503data_unavailableThe routing corpus is unreachable or has never been populated.

Caching · One hour at the edge.

get

/api/v1/unannounced

Allocated but unannounced space

Registry allocations with no covering announcement in the latest snapshot — address space that exists on paper and is absent from the routing table.

Parameters

rirstring · queryoptionalFilter by registry: arin, ripencc, apnic, lacnic, afrinic.
countrystring · queryoptionalFilter by ISO 3166-1 alpha-2 country code.
limitinteger · queryoptionalRows per page, 1–500. Defaults to 100.
offsetinteger · queryoptionalRows to skip. Defaults to 0.
Request
$ curl https://subnet.im/api/v1/unannounced?rir=ripencc&limit=2
Response
{ "prefixes": [ { "prefix": "5.44.128.0/18", "rir": "ripencc", "country": "GB", "allocatedAt": "2012-03-14" }, { "prefix": "5.62.0.0/19", "rir": "ripencc", "country": "NL", "allocatedAt": "2012-05-02" } ], "page": { "limit": 2, "offset": 0, "returned": 2 }, "meta": { "snapshotAt": "2026-08-12T06:00:00.000Z" } }

Errors

  • 400invalid_requestA filter or page argument is invalid.
  • 503data_unavailableThe routing corpus is unreachable or has never been populated.

Caching · One hour at the edge.

Service

Always available

How current the data is, and the machine-readable description of everything above.

get

/api/v1/status

Data freshness

Snapshot age, per-collector health and corpus size. Answers 200 even when the corpus is empty — available: false is the useful signal, and an endpoint whose job is to report an outage should not fail during one.

Request
$ curl https://subnet.im/api/v1/status
Response
{ "available": true, "snapshotAt": "2026-08-12T06:00:00.000Z", "ageSeconds": 10800, "collectors": [ { "name": "route-views2", "lastSnapshotAt": "2026-08-12T06:00:00.000Z", "v4Peers": 43, "v6Peers": 31 } ], "totals": { "ases": 82914, "v4Prefixes": 1024881, "v6Prefixes": 214402 } }

Caching · One minute at the edge.

get

/api/v1/openapi.json

OpenAPI 3.1 document

Generated from the same definitions that render this page, so it cannot drift from the documentation above.

Request
$ curl https://subnet.im/api/v1/openapi.json
Response
{ "openapi": "3.1.0", "info": { "title": "Subnet.im API", "version": "1.0.0" }, "paths": { "/api/v1/subnet": { "get": { "operationId": "subnet" } } } }

Caching · One hour at the edge.

Using this data

Routing data is derived from public RouteViews archives, RIR delegation files and RPKI repositories. Attribution to subnet.im is appreciated. Found something wrong, or need an endpoint that isn’t here? Open an issue.