The IP Narwhal API

Your public address as JSON or plain text. No key, no signup, no logging, and one useful trick most IP APIs cannot do.

The short version

$ curl https://api4.ipnarwhal.com/iptool/ip.txt
203.0.113.42

That is the whole thing for most uses. There is no key to request, no account, and no rate limit you are likely to notice. We log nothing: your address is read from the request, put in the response, and forgotten.

Two hostnames, and why that matters

This is the part worth reading, because it is the thing most IP APIs get wrong by accident.

HostDNSAnswers over
api4.ipnarwhal.comA record onlyIPv4 only
api6.ipnarwhal.comAAAA record onlyIPv6 only

A host with a single-family DNS record cannot be reached over the other family. So api4 is guaranteed to report your IPv4 address and api6 is guaranteed to report your IPv6 address, without depending on your client honoring a -4 or -6 flag and without any chance of the answer quietly coming back over the wrong stack.

An API on a dual-stacked hostname cannot promise that. It reports whichever family your client happened to prefer that second, which is usually IPv6 when it is available, and you have no way to tell from the response which one you got.

A timeout on api6 is an answer

If api6 does not resolve or does not connect, that is the finding: this connection has no working IPv6. It is not a fault to retry. Our own front page uses exactly this to decide whether to print an address or "Not detected", and it gives up after five seconds.

Endpoints

PathReturnsContent type
/iptool/ipJSON, address plus approximate locationapplication/json
/iptool/ip.txtThe bare address and a newlinetext/plain

Both exist on both hostnames. Only GET is supported.

The JSON response

$ curl -s https://api4.ipnarwhal.com/iptool/ip
{
  "ip": "203.0.113.42",
  "version": "IPv4",
  "geo": {
    "country_code": "US",
    "country": "United States",
    "region": "Pennsylvania",
    "city": "Easton",
    "postal_code": "18045",
    "timezone": "America/New_York",
    "latitude": "40.69410",
    "longitude": "-75.29040",
    "asn": "6079"
  }
}
FieldNotes
ipThe address we saw the request come from
versionIPv4 or IPv6
geo.country_codeTwo letter code. The most reliable field here by a wide margin
geo.region, geo.city, geo.postal_codeApproximate. See the accuracy note below
geo.latitude, geo.longitudeStrings, not numbers. The center of a coverage area, never a street
geo.asnThe autonomous system holding the address, usually your provider

Any geo field can be absent when the edge did not supply it, so read defensively rather than assuming every key is present.

Examples

A shell variable

MY_IP=$(curl -s https://api4.ipnarwhal.com/iptool/ip.txt)

Both families at once

v4=$(curl -s --max-time 5 https://api4.ipnarwhal.com/iptool/ip.txt)
v6=$(curl -s --max-time 5 https://api6.ipnarwhal.com/iptool/ip.txt)
echo "v4=${v4:-none} v6=${v6:-none}"

Just the country

curl -s https://api4.ipnarwhal.com/iptool/ip | jq -r .geo.country_code

Python

import urllib.request, json

with urllib.request.urlopen("https://api4.ipnarwhal.com/iptool/ip") as r:
    data = json.load(r)

print(data["ip"], data["geo"].get("country_code"))

Limits, stated plainly

Where to swim next