Developer reference
API docs
A fast, global IP-data API served from the Cloudflare edge. Every endpoint returns clean JSON — no key required for the free tier. Point curl, fetch, or requests at it and ship.
getting started
All responses are JSON with Content-Type: application/json and Cache-Control: no-store. No authentication is required today; the free tier is rate-limited per IP (see rate limits). Paid tiers with higher limits and richer data (proxy/VPN/threat signals, bulk lookups) are coming.
https://whatsmyconnection.com · The root / is content-negotiated — curl gets a bare IP, an Accept: application/json header gets the same payload as /api/ip, and a browser gets the HTML page.curl -s https://whatsmyconnection.com
# → 203.0.113.7
curl -s -H "Accept: application/json" https://whatsmyconnection.com
/api/ip
GET /api/ip
The flagship endpoint. Returns the caller's public IP plus full connection intelligence: geolocation, ASN/ISP, edge datacenter, a coarse networkType classification, reverse DNS, and parsed client (browser/OS/device) — all derived from the request, no client-side JavaScript.
Parameters: none. The response describes the caller's own connection.
Response schema
| Field | Type | Notes |
|---|---|---|
ip | string | Connecting public IP (CF-Connecting-IP). Whichever family the client used. |
ipVersion | 4 | 6 | 0 | IP family of `ip`; 0 when unknown. |
ipv4 / ipv6 | string | null | Dual-stack view; the family not used on this request is null. |
city / region / country | string | null | Edge geolocation (ISO-3166 alpha-2 country). |
timezone | string | null | IANA timezone, e.g. America/New_York. |
asn | number | null | Autonomous System Number of the connecting network. |
asOrganization | string | null | Network/ISP organization name. |
colo | string | null | Cloudflare datacenter (IATA code) that served the request. |
networkType | object | Coarse classification from free ASN signals: { type, confidence, label, reason }. type is residential | hosting | mobile | business | vpn | unknown. |
hostname | string | null | Reverse-DNS PTR of `ip` (production only; null when live upstreams are off). |
client | object | Parsed from request headers: { browser, os, device, languages[] }. No client-side JS. |
latitude / longitude / postalCode / … | string | null | Additional free request.cf geolocation + TLS/HTTP connection fields (httpProtocol, tlsVersion, clientTcpRtt, rayId, …). |
Examples
curl -s https://whatsmyconnection.com/api/ip
const res = await fetch("https://whatsmyconnection.com/api/ip", {
headers: { Accept: "application/json" },
});
const data = await res.json();
console.log(data.ip, data.asn, data.networkType.type);
import requests
r = requests.get("https://whatsmyconnection.com/api/ip")
data = r.json()
print(data["ip"], data["asn"], data["networkType"]["type"])
/api/network
GET /api/network
A network-engineering view of the caller's connection: server-observed identity, the same networkType classification, best-effort BGP route context, and configuration for the optional client-side dual-stack and DNS-resolver probes.
Parameters: none.
Response schema
| Field | Type | Notes |
|---|---|---|
serverObserved | object | { ip, ipVersion, ipv4, ipv6, asn, asOrganization, colo, country } as seen by the edge. |
networkType | object | Same classification object as /api/ip: { type, confidence, label, reason }. |
browserObserved | object | { ipv4, ipv6 } — placeholders (null) for an optional client-side dual-stack probe. |
dns | object | { status: "requires_probe", resolvers[], clientSubnets[], dnssec } — resolver detection needs the gated probe. |
bgp | object | null | Best-effort BGP route for `ip`: prefix, asn, name, registry, country, source, fetchedAt (fixture-backed locally). |
familyProbe | object | { mode } — "pending" or "self-hosted" browser dual-stack probe mode. |
probeEndpoints | object | { ipv4[], ipv6[] } probe URLs for the optional client family probe. |
dnsProbe | object | DNS-probe config (enabled flag + watch/zone) for the gated resolver-leak feature. |
Examples
curl -s https://whatsmyconnection.com/api/network
const res = await fetch("https://whatsmyconnection.com/api/network");
const { serverObserved, networkType, bgp } = await res.json();
console.log(serverObserved.asn, networkType.label, bgp?.prefix);
/api/lookup
GET /api/lookup?query=<value>
The general-purpose enrichment endpoint. Look up any IP, CIDR prefix, ASN, domain, or MAC address — not just your own. The input type is auto-detected and normalized; the response carries source labels, freshness, and partial-result warnings so it stays honest about what was available.
| Param | Type | Notes |
|---|---|---|
query | string (required) | An IPv4/IPv6 address, CIDR prefix (e.g. 1.1.1.0/24), ASN (AS13335 or 13335), domain, or MAC address (00-00-5E-00-53-01, 00:00:5E:00:53:01, …). Max 253 chars; control chars and </> are rejected. |
Response schema
| Field | Type | Notes |
|---|---|---|
query | object | Normalized input: { type, input, normalized, ipVersion, prefixLength, asn, mac }. type is ipv4 | ipv6 | prefix | asn | domain | mac. |
result | object | Flattened summary: ip, ipVersion, asn, organization, networkName, prefix, route, registry, country, rdapSummary, whoisSummary, resolvedIps[], dnsRecords, reverseDns[], oui, vendor, rpkiStatus, irrStatus, dataSources[], fetchedAt, partial. |
data | object | Discriminated by `kind` (ip | prefix | asn | domain | mac) — the raw per-source records (rdap, bgp, rpki, irr, whois, originatedPrefixes, records, relatedIps, vendor, …). |
sources | array | [{ id, name, url, status }] — every data source consulted; status is used | failed. |
warnings | array | [{ code, message }] — stable machine codes, e.g. rdap_unavailable, no_enrichment, unknown_vendor. |
cache | object | { mode: "none" | "memory", ttlSeconds, hit? } — the Worker's internal upstream-protection cache (HTTP response is still no-store). |
Examples
curl -s 'https://whatsmyconnection.com/api/lookup?query=1.1.1.1'
curl -s 'https://whatsmyconnection.com/api/lookup?query=AS13335'
curl -s 'https://whatsmyconnection.com/api/lookup?query=example.com'
curl -s 'https://whatsmyconnection.com/api/lookup?query=1.1.1.0/24'
curl -s 'https://whatsmyconnection.com/api/lookup?query=00-00-5E-00-53-01'
const q = encodeURIComponent("1.1.1.1");
const res = await fetch(`https://whatsmyconnection.com/api/lookup?query=${q}`);
if (res.status === 400) throw new Error("invalid query");
const { result, sources, warnings } = await res.json();
console.log(result.organization, result.prefix, result.partial);
import requests
r = requests.get("https://whatsmyconnection.com/api/lookup", params={"query": "AS13335"})
r.raise_for_status()
print(r.json()["result"]["organization"])
/api/asn/:asn
GET /api/asn/:asn
A convenience alias for an ASN lookup: /api/asn/AS13335 (or /api/asn/13335) returns the same LookupPayload shape as /api/lookup?query=AS13335 — whois organization, originated prefixes, and registry context. A non-ASN path segment returns the error shape with HTTP 400.
| Param | Type | Notes |
|---|---|---|
:asn | string (required) | An ASN with or without the AS prefix, e.g. AS13335 or 13335. |
Examples
curl -s https://whatsmyconnection.com/api/asn/AS13335
curl -s https://whatsmyconnection.com/api/asn/13335
const res = await fetch("https://whatsmyconnection.com/api/asn/13335");
const { result, data } = await res.json();
console.log(result.organization, data.originatedPrefixes.length);
rate limits
The free tier allows 60 requests per minute per IP across all data/API surfaces (/api/ip, /api/network, /api/lookup, /api/asn/:asn, and the plain/JSON forms of /). Every response carries the standard rate-limit headers:
| Header | Meaning |
|---|---|
x-ratelimit-limit | Your ceiling for the current window (60 on the free tier). |
x-ratelimit-remaining | Requests left in the current window. |
x-ratelimit-reset | ISO-8601 timestamp when the window resets. |
retry-after | Seconds to wait before retrying (429 responses only). |
Over the limit returns HTTP 429 with a plain-text body. Paid tiers with higher limits and richer data are coming — bots and scripts are welcome here, they are future customers, not blocked traffic.
HTTP/2 429
retry-after: 42
x-ratelimit-limit: 60
x-ratelimit-remaining: 0
429 - rate limit exceeded. Try again in a minute.
errors
Invalid or hostile input to /api/lookup or /api/asn/:asn returns HTTP 400 with a stable, machine-readable error envelope:
{
"error": {
"code": "invalid_query",
"message": "query must be an IP address, CIDR prefix, ASN, domain, or MAC address"
}
}
Successful responses can still be partial: when a source is unavailable, the payload sets result.partial = true and appends a { code, message } entry to warnings[] rather than failing the whole request.
API FAQ
Do I need an API key?
No. The free tier requires no key — it is rate-limited per IP. Authenticated paid tiers with higher limits and richer data (proxy/VPN/threat detection, bulk lookups) are coming.
What's the difference between /api/ip and /api/lookup?
/api/ip describes your own connection (no parameters). /api/lookup?query=<value> enriches any IP, prefix, ASN, domain, or MAC you pass it.
Is CORS supported?
The API sends Cache-Control: no-store and reflects the site's own origin for cross-origin browser requests. For server-to-server use (curl, fetch from your backend, requests) no CORS handling is needed.