IP address API
Plain text or JSON. No key, no sign-up, no rate limit, and CORS open to every origin.
216.73.216.169
This is the address the internet sees. You reached v4x.me over IPv4, so your IPv6 address is not shown. What is the difference?
Endpoints
| Request | Response |
|---|---|
| GET / | The bare IP for curl, wget and scripts. The HTML page for browsers. |
| GET /ip | The IP address and nothing else. |
| GET /api | IPv4,216.73.216.169 — family and address, comma separated. |
| GET /ua | Your User-Agent string. |
| GET /json | Address, family, parsed client and connection details. |
Shell
$ curl v4x.me 216.73.216.169 $ curl -s v4x.me/api IPv4,216.73.216.169
Store it in a variable
MY_IP=$(curl -s v4x.me) echo "public address is $MY_IP"
Force IPv4 or IPv6
curl picks a protocol on its own. Pin it with -4 or -6 to
see how each one reports:
$ curl -4 -s v4x.me $ curl -6 -s v4x.me
If the second command fails, your connection has no working IPv6. See IPv4 vs IPv6.
JSON
$ curl -s v4x.me/json | jq .family "IPv4"
Other languages
Python
import urllib.request
ip = urllib.request.urlopen("https://v4x.me/ip").read().decode().strip()
JavaScript
const ip = await (await fetch("https://v4x.me/ip")).text();
Go
resp, _ := http.Get("https://v4x.me/ip")
body, _ := io.ReadAll(resp.Body)
Why the bare address
Most IP services answer with JSON even when you asked for one value. That forces a
dependency on jq or a parser just to read a string you already know the shape of. This
API answers GET / with the address and a newline, so the useful form is
the default and the structured form is opt-in.
Detection is by User-Agent. curl, wget, HTTPie, python-requests, httpx, aiohttp,
Go's client and PowerShell get plain text. Everything else gets the HTML page. You can
always force plain text by asking for /ip instead.
Practical uses
Update a firewall allowlist
A home connection with a dynamic address breaks allowlists. Fetch the current address and compare it against the last one you stored:
#!/usr/bin/env bash current=$(curl -fsS v4x.me) previous=$(cat ~/.cache/my-ip 2>/dev/null || true) if [ "$current" != "$previous" ]; then echo "address changed: $previous -> $current" printf '%s' "$current" > ~/.cache/my-ip # re-apply your firewall rule or DNS record here fi
Check which egress a container uses
Useful when a host has several uplinks and you need to know which one a workload actually leaves through:
$ docker run --rm curlimages/curl -s v4x.me
Confirm a VPN is up before doing anything else
if [ "$(curl -fsS v4x.me)" = "$EXPECTED_VPN_IP" ]; then echo "tunnel is up" fi
Reliability and fair use
The service runs as a Cloudflare Worker at the edge, so it answers from a location near the caller and has no origin server to fall over. There is no rate limit and no key.
Please do not poll it every second from a loop. Once per minute is more than enough
to catch a changed address, and a cron job every five minutes is plenty for dynamic
DNS. Use curl -fsS so a failure is visible in your script instead of
silently producing an empty string.
Notes
- Responses carry
no-store. The answer differs per caller, so a proxy must never cache it. /ip,/ua,/apiand/jsonsendAccess-Control-Allow-Origin: *, so browser JavaScript can call them directly.- Nothing is written to a database and no cookie is set.