v4x.me · what is my IP

IP address API

Plain text or JSON. No key, no sign-up, no rate limit, and CORS open to every origin.

Your public IP address IPv4

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

RequestResponse
GET /The bare IP for curl, wget and scripts. The HTML page for browsers.
GET /ipThe IP address and nothing else.
GET /apiIPv4,216.73.216.169 — family and address, comma separated.
GET /uaYour User-Agent string.
GET /jsonAddress, 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