Learn
What an "IP fact" is, where the data comes from, and how to consume it from code or an AI agent. Skim or jump to a section.
What is an "IP fact"?
An IP fact is a small, durable statement about an IP address — something that's true today and that you might want to reason about. We split them into two layers:
-
/basic— the foundation: country, public vs private, IPv4 vs IPv6, and a special-range classification. -
/full— enrichment on top of/basic: Tor exit-node status and cloud provider classification (Azure and AWS — with the specific service tag where the provider publishes it).
Both layers are exposed via the REST API, the MCP server, and a CLI.
Country + public/private (the /basic layer)
The /basic lookup answers a few questions, fast:
- Country. ISO-3166 two-letter code, sourced from Azure
Maps. We return
"??"when the geolocation provider has no answer (private IPs, reserved ranges, unknown allocations). - Public vs private. RFC 1918 / RFC 4193 / loopback / link-local. Private IPs short-circuit before we hit any external provider — no leaks, no cost.
- Address family.
"IPv4"or"IPv6". - Special range. the RFC classification of reserved space (see below).
The provider abstraction means we can swap geolocation backends without consumers noticing — Azure Maps today, something else tomorrow.
Enriched facts (the /full layer)
The enriched lookup wraps /basic and adds the bits you actually
want when investigating a suspicious request:
{
"ip": "20.232.0.137",
"countryCode": "US",
"isPublic": true,
"addressFamily": "IPv4",
"specialRange": null,
"isTor": false,
"azure": { "service": "AzureCloud", "region": null },
"aws": null
}
Private IPs return the same shape but with countryCode: "??", a non-null specialRange, and azure /
aws as null — no provider calls, no telemetry,
no surprise cost.
Special-range classification
Every lookup classifies the address against the reserved IPv4/IPv6
ranges defined by the RFCs. specialRange is null for publicly routable space, otherwise one of:
Private— RFC 1918 / RFC 4193 (ULA).Loopback—127.0.0.0/8,::1.LinkLocal—169.254.0.0/16,fe80::/10.Cgnat— RFC 6598 carrier-grade NAT space.-
Documentation— RFC 5737 / RFC 3849 example ranges. -
Multicast,Broadcast,Unspecified.
Private, reserved, and documentation IPs short-circuit before any external geolocation call — the classification is purely local.
Tor exit-node detection
isTor reflects whether the IP is currently advertised as a
Tor exit node on the public Tor consensus. We refresh the list every 30
minutes from check.torproject.org and keep it in memory for
O(1) lookups.
This is a "is it on the list right now" check — not a "has this IP ever been a Tor exit" check. False negatives are possible when a node is rotating.
Example: 192.42.116.20 is a useful Tor-exit test address
when it is present in the current Tor consensus.
Azure + AWS cloud classification
For Azure IPs we return the service tag that the address belongs to —
"AzureCloud", "AzureFrontDoor.Frontend",
"Storage.eastus", etc. Source is the official ARM
Microsoft.Network/locations/{location}/serviceTags endpoint, refreshed daily.
For AWS IPs we return the service and region from the published
ip-ranges.json dataset — { "service": "S3", "region": "us-east-1" }. Region is null when AWS does not publish one.
Region is populated when an Azure service tag carries one (Storage.eastus → "eastus"); for cloud-wide tags like AzureCloud region is null.
Tor and cloud attribution are independent signals. A Tor exit relay can
run on cloud infrastructure, so an IP can be both isTor: true
and attributed to Azure or AWS.
Examples to try: Azure 20.232.0.137, AWS
3.5.140.1, Tor 192.42.116.20.
MCP — call from an AI agent
mcp.ipfacts.com exposes the same lookups as
MCP tools so
an LLM agent can call them directly:
-
ip_facts_basic(ip)— free. Returns the foundation facts. -
ip_facts_full(ip)— paid per-call via x402 (see below). Returns the enriched shape.
Built on the official MCP C# SDK; any MCP-capable client (Claude Desktop, an Agent Framework runtime, your own client) can plug in.
MCP is dynamic — not a static SDK
The difference between calling ipfacts over MCP and wiring up an old-school REST client isn't the wire format — it's when the contract is bound. A REST SDK bakes the operations, their parameters, and their docs into your program at build time; adding a tool or changing a description means a developer re-reads the docs, edits code, and redeploys.
With MCP the agent asks the server tools/list at
runtime and gets each tool's name, its
description, and a JSON input schema back as data. The
model reasons over those descriptions to decide what to call and how —
so the description is the contract. Nothing about ipfacts'
tools is compiled into the client; it picks ip_facts_full
over ip_facts_basic purely from what the server says they
do.
It's also live. MCP defines a
notifications/tools/list_changed push: when a server adds
a tool, edits a description, or changes a schema, it can notify
connected clients, and a compliant runtime (Claude Desktop, a Microsoft
Agent Framework host, your own client) re-fetches tools/list
on the open session — no reconnect, no redeploy. Even without
the push, every new session re-discovers the current tools, so a client
never drifts from the live surface.
Practical upshot: when we add a lookup, rename a tool, or sharpen a description, agents adapt on their own — no client release. That adaptability is the trade-off: an agent reading descriptions at runtime is more flexible than a hardcoded SDK, and correspondingly less rigidly deterministic.
x402 — pay per call, no subscription
The enriched ip_facts_full surface charges $0.0001 in USDC per
call via the x402 protocol — HTTP 402 Payment Required with a machine-readable
challenge that an agent's wallet can sign and retry. Symmetric on REST and
MCP: both api.ipfacts.com/full/{ip} and the MCP
ip_facts_full tool gate on payment; ip_facts_basic is free on both surfaces.
Why we like x402 for this: you only pay for the calls you make, agents can authorize spend without a human in the loop, and we don't have to run a billing portal for the long tail of integrations.
The free ip_facts_basic tool is not paywalled, so anything that
just needs country / public-private classification stays free.
Sandbox & testnet — try x402 with play money
Before pointing real USDC at api.ipfacts.com, there's a
permanent testnet twin: api.sandbox.ipfacts.com and
mcp.sandbox.ipfacts.com. Same binary, same tools — but it
settles on Base Sepolia testnet through the free,
public x402.org facilitator, so every call is paid in
testnet USDC — play money, no real spend.
Payments are gasless (EIP-3009): the facilitator submits the on-chain transfer, so your wallet only needs testnet USDC, not Sepolia ETH. Grab some from the open faucet:
- Testnet USDC faucet: faucet.circle.com — choose Base Sepolia, paste your wallet address, receive 20 test USDC. No login; up to once every 2 hours.
Because the payments are real on-chain transfers, you can watch them.
The sandbox flow has two wallets — the client that pays and the
payTo that collects — both inspectable on the Base Sepolia
explorer:
- Payer — the lab client's wallet
(
0xcB7afF71…aE4bC13): view transactions. - Recipient — the sandbox
payTo(0xE8E20587…fc5A02C69): view transactions.
Point your own client at the sandbox and your payer address shows up here too — the signed authorization and the USDC transfer are both on-chain and inspectable.
REST API reference
| Endpoint | Returns | Auth |
|---|---|---|
GET /basic/{ip} | Foundation facts (country, public/private, address family, special range). | None. |
GET /full/{ip} | Enriched facts (above + Tor + Azure/AWS classification). | x402-paid — first call returns 402 with a v2 payment
challenge; retry with X-PAYMENT. |
GET /livez | 200 if the process is alive. | None. |
GET /readyz | 200 if dependencies are ready. | None. |
GET /version | Service name, semver, and commit SHA. | None. |
Base URL: https://api.ipfacts.com.
CLI
The ipfacts CLI wraps the same lookups for terminal and
scripting use — pipe an IP in, get JSON out:
$ ipfacts basic 8.8.8.8
{ "countryCode": "US", "isPublic": true, "addressFamily": "IPv4" }
$ ipfacts full 20.232.0.137
{ ..., "isTor": false, "azure": { "service": "AzureCloud" } }
$ ipfacts full 3.5.140.1
{ ..., "aws": { "service": "EC2", "region": "ap-northeast-2" } }
$ ipfacts full 192.42.116.20
{ ..., "isTor": true } basic is free; full negotiates x402 payment with
a configured wallet, the same way the MCP ip_facts_full tool
does.
Limits & freshness
- Tor list: refreshed every 30 minutes.
- Azure ServiceTags: refreshed every 24 hours.
- AWS ip-ranges: refreshed every 24 hours.
- Country lookup: Azure Maps — sub-second, no per-IP cache layer yet.
- Rate limits: none enforced today on the free REST surface; please don't make us add some.