ip.rt.ht and rq.rt.ht

ip.rt.ht answers with who you are: your IP address, and what Cloudflare knows about the request, such as the data center that served it. rq.rt.ht echoes any request back as JSON, so that you can see exactly what your code sends. Both answer every method and every path, and both allow any origin.

Syntax

plain
https://ip.rt.ht/
https://ip.efn.kr/
https://rq.rt.ht/<path>?<query>

Both hosts answer every method and every path with 200. ip.efn.kr runs the same code from another zone. For rq.rt.ht, end the path with a slash or with a dotted segment such as /a.json. A path with neither is first redirected with 307 to the path with a slash.

Parameters

ip.rt.ht and ip.efn.kr

None. Any method and any path returns the same JSON.

rq.rt.ht

The request is the input. Send any method, path, query string, headers and body, and the host reports them back.

Response

ip.rt.ht

The answer is 200 JSON with three members, pretty-printed with two spaces, with Content-Type: application/json;charset=utf-8, Cache-Control: no-store and Access-Control-Allow-Origin: *:

at

The server time, as an ISO string.

cf

Cloudflare's request.cf object for your request. Keys include asn, asOrganization, city, colo, continent, country, isEUCountry, latitude, longitude, postalCode, region, regionCode, timezone, httpProtocol, tlsVersion, tlsCipher, clientTcpRtt and clientQuicRtt. The keys come from Cloudflare, and can be absent when a value is unknown.

hs

Every request header, as a map from the lower-case name to the value.

Your public IP address is hs["cf-connecting-ip"], and also hs["x-real-ip"]. On ip.rt.ht the visitor-location headers are in hs too: cf-ipcountry, cf-ipcity, cf-ipcontinent, cf-iplatitude, cf-iplongitude, cf-postal-code, cf-region, cf-region-code and cf-timezone. Other keys include cf-ray, cf-visitor, host, user-agent, accept, accept-encoding and x-forwarded-proto. The answer is about 2.7 to 3 KB.

Cloudflare's own /cdn-cgi/trace works on these hosts as well. It returns key=value lines, with keys such as ip, colo, loc, http, tls and kex.

ip.efn.kr

ip.efn.kr returns the same { at, cf, hs } shape from another zone, with two differences. Its hs has no visitor-location headers except cf-ipcountry, and the location stays available in cf. And another Cloudflare data center can answer it than the one that answers ip.rt.ht, so cf.colo, the round-trip times and the edge location can differ for the same client.

rq.rt.ht

The answer is 200 JSON, pretty-printed with two spaces, with Content-Type: application/json;charset=utf-8, Cache-Control: no-store, Access-Control-Allow-Origin: * and Access-Control-Allow-Headers: *:

method

The request method.

url

The request URL.

_.url

The URL taken apart: hash, host, hostname, href, origin, password, pathname, port, protocol, search and searchParams. searchParams is a list of [name, value] pairs that keeps repeated names in order and decodes the values, so y=%F0%9F%98%80 reads as "😀". hash is always "", because browsers do not send fragments.

redirect

The redirect mode of the incoming request, which is "manual" on Cloudflare.

headers

The request headers.

body

Present only when the request body is not empty. As of 2026-10-04 it holds the raw body text as a string, so parse it yourself when it is JSON.

Used by the playground

The playground's output service worker fetches https://ip.rt.ht, with a timeout of 5 seconds and no caching, and keeps the first successful answer for its lifetime. It uses hs["cf-connecting-ip"] as request.rt.ip in playground workers, and cf feeds the emulated request.cf. After a failure, request.rt.ip is null, and the lookup is tried again no sooner than 5 seconds later. See Architecture and Request.rt.

Examples

Show your IP address and edge location

HTML

html
<output id=where>…</output>
<script type=module>
  const { hs, cf } = await fetch('https://ip.rt.ht/').then(r => r.json());
  where.value = `${hs['cf-connecting-ip']} · ${cf.city ?? '?'}, ${cf.country} · edge ${cf.colo}`;
</script>

Result

Echo a request to see what your code sends

The query keeps both x values in order, and body is the text that was sent.

HTML

html
<pre id=echo></pre>
<script type=module>
  const r = await fetch('https://rq.rt.ht/demo/?x=1&x=2', { method: 'POST', body: JSON.stringify({ hello: 'edge' }) });
  const { method, _, headers, body } = await r.json();
  echo.textContent = JSON.stringify({ method, query: _.url.searchParams, ua: headers['user-agent'], body }, null, 2);
</script>

Result

CORS and limits

Status

Both hosts are available. Their behavior was checked on 2026-10-04.

See also