IP Address Lookup
Resolve an IPv4 or IPv6 address to its PTR record, geographic location and registry allocation. Call it from the browser, or from a script against the public API in six output formats.
Overview
The lookup answers three separate questions about one address, and you choose how many of them to ask
through the level parameter:
- Level 0: is the address valid, is it public or reserved, and what is its PTR record.
- Level 1: adds geographic location from a local MaxMind database.
- Level 2: adds registry data from a live RDAP query.
Each level is a superset of the one below it. Higher levels are slower: level 0 and 1 are answered from local data, level 2 waits on an external registry.
Reserved and private addresses never reach levels 1 and 2. The level is silently reset to 0 and the
response carries an additional_context block naming the RFC that reserves the range instead.
Web interface
Enter an address in the field and submit. Leaving it empty and pressing My IP looks up the address you are connecting from. IPv6 examples fills the field with a known-good IPv6 address.
Results arrive in three panels:
- Address: version, PTR record and, for reserved ranges, the allocating RFC.
- Location: coordinates, country, region, city, postcode and UTC offset. The database tabs switch between MaxMind, DB-IP and IP2Location, which disagree more often than you would expect; comparing them is the point of having all three.
- Routing: origin AS, announced BGP prefix and the RDAP registry record.
The map is only requested after you consent to it, because loading it contacts Google.
URL parameters
The tool page accepts the address directly in the path, so a result is linkable and bookmarkable.
| Pattern | Example |
|---|---|
/lookup/ip/{ip} | /lookup/ip/8.8.8.8 |
/lookup/ip/[{ipv6}] | /lookup/ip/[2001:4860:4860::8888] |
IPv6 addresses must be wrapped in square brackets. Without them the colons are ambiguous against the other route patterns and the address will not match.
These are web paths and return an HTML page. The API equivalent carries the
version prefix, so /v1/lookup/ip/8.8.8.8 returns plain text while
/lookup/ip/8.8.8.8 returns the tool page.
API
No authentication, no API key, no registration. Every endpoint is a GET against
https://api.troubleshooting.tools.
/lookup/ip/{format}/lvl{level}/{ip}
Every path segment except the format is optional, which gives five usable shapes:
| Path | Resolves to |
|---|---|
/lookup/ip/{format}/lvl{level}/{ip} | Named address, explicit level |
/lookup/ip/{format}/{level}/{ip} | Same, short level form (0 for lvl0) |
/lookup/ip/{format}/{ip} | Named address, level 0 |
/lookup/ip/{format}/lvl{level}/ | Caller's own address, explicit level |
/lookup/ip/{format}/ | Caller's own address, level 0 |
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
| format | enum | required | One of json, jsonp, xml, yaml,
csv, html. Omit the segment entirely for plain text. |
| level | integer | 0 |
Detail depth, 0 to 2. Values outside that range are clamped
rather than rejected, so lvl9 behaves as lvl2. |
| ip | string | caller's address | IPv4 or IPv6. Omitted means the address the request arrives from, taken from
X-Real-IP, Client-IP or the first entry of
X-Forwarded-For. |
| callback | string | callback |
JSONP function name, query parameter rather than path segment. Must match
[A-Za-z_$][A-Za-z0-9_$]*; anything else falls back to the literal name
callback. |
Example
$ curl -s "https://api.troubleshooting.tools/v1/lookup/ip/json/lvl2/8.8.8.8" | jq .
{
"ip": "8.8.8.8",
"invalid": false,
"reserved": false,
"ip_version": 4,
"ptr_record": "dns.google",
"location_data": {
"ipVersion": 4,
"ipAddress": "8.8.8.8",
"latitude": 37.751,
"longitude": -97.822,
"countryName": "United States",
"countryCode": "US",
"timeZone": "-05:00",
"zipCode": null,
"cityName": null,
"regionName": null
},
"registry_data": {
"rir": "ARIN",
"netRange": "8.8.8.0 - 8.8.8.255",
"cidr": "8.8.8.0/24",
"name": "GOGL",
"handle": "NET-8-8-8-0-2",
"parent": "NET-8-0-0-0-1",
"netType": "DIRECT ALLOCATION",
"lastChanged": "Fri, 14 Mar 2025 16:52:05 GMT",
"remark": "not provided",
"isp": "Google LLC",
"country": "US",
"address": "1600 Amphitheatre Parkway, Mountain View, CA, 94043, United States",
"ipVersion": 4,
"source": "https://search.arin.net/rdap/?query=8.8.8.8"
}
}
Response fields
Present at every level:
| Field | Type | Description |
|---|---|---|
| ip | string | The address that was looked up. |
| invalid | boolean | Input was not a parseable IP address. |
| reserved | boolean | Address falls in a private or otherwise reserved range. |
| ip_version | integer | 4, 6, or 0 when invalid. |
| ptr_record | string | Reverse DNS name without the trailing dot, or the literal
string No PTR record found. Omitted for invalid and reserved addresses. |
| additional_context | object | Only for reserved addresses:
address_block, name, rfc, allocation_date,
ip_version. |
location_data, added at level 1 and above, carries ipVersion,
ipAddress, latitude, longitude, countryName,
countryCode, timeZone, zipCode, cityName and
regionName. Unknown values are null. timeZone is a UTC offset such as
-05:00, not an IANA zone name.
registry_data, added at level 2, carries rir, netRange,
cidr, name, handle, parent, netType,
lastChanged, remark, isp, country, address,
ipVersion and source.
Missing registry values are the string not provided, not
null. Test for that literal rather than for absence, and never feed a
registry_data value straight into a numeric or date parser.
Output formats
| Segment | Content-Type | Notes |
|---|---|---|
| json | application/json | Pretty-printed, slashes and Unicode unescaped. |
| jsonp | application/javascript | JSON wrapped in the callback function call. |
| xml | application/xml | Root element <ip_lookup>, nested objects become nested elements. |
| yaml | text/yaml | Hand-rolled emitter; values needing quotes are quoted. |
| csv | text/csv | Two lines, header and values. Nested keys are flattened with underscores, e.g. location_data_cityName. |
| html | text/html | Standalone table page with its own styling. Handy in a browser, not meant for scraping. |
| omitted | text/plain | Just the address, or Invalid IP address / Reserved IP address. |
# Your own address, as plain text
$ curl -s https://api.troubleshooting.tools/v1/lookup/ip/
203.0.113.42
# Country only, from CSV
$ curl -s "https://api.troubleshooting.tools/v1/lookup/ip/csv/lvl1/1.1.1.1" | cut -d, -f9
# JSONP for a legacy browser integration
$ curl -s "https://api.troubleshooting.tools/v1/lookup/ip/jsonp/lvl1/1.1.1.1?callback=onLookup"
Errors
The endpoint answers HTTP 200 for every well-formed request, including lookups of
garbage input. Failure is reported in the body, not the status line. Branch on invalid and
reserved, not on the status code.
| Condition | Response |
|---|---|
| Unparseable address | invalid: true, ip_version: 0, level forced to 0. |
| Private or reserved address | reserved: true plus additional_context, level forced to 0. |
| Geolocation database unavailable | location_error instead of location_data. |
| RDAP query failed or timed out | registry_error instead of registry_data. |
| Rate limit exceeded | HTTP 429 from the edge, before the lookup runs. |
A partial answer is normal and not an error: level 2 can return location_data together with
registry_error when RDAP is slow. Treat the two blocks independently.
Rate limits
50 requests per second per IP address, with a burst allowance of 100 and a ceiling of 50 concurrent
connections. Exceeding it returns HTTP 429.
There is no quota, no key and no paid tier. The limit exists to keep the service responsive, not to meter it. Level 2 depends on an external registry, so if you are iterating over a large list, cache the results and stay well below the ceiling.
Data sources
| Data | Source |
|---|---|
| Location | MaxMind GeoLite2 City, DB-IP City and IP2Location LITE DB11, all queried locally. |
| Registry | RDAP, live. RIPE first, falling back to ARIN and APNIC, plus LACNIC and AFRINIC for IPv6. |
| Routing | Team Cymru IP-to-ASN service over DNS. |
| PTR record | Direct DNS query from the server. |
| Reserved ranges | The IANA special-purpose address registries, compiled into the service. |
Geolocation databases are refreshed on a schedule and are estimates. City-level accuracy is unreliable and country-level accuracy is good but not perfect. Do not use them for access control or compliance decisions.
Privacy
Lookups are not tied to an account, because there are no accounts. Queried addresses appear in server logs for operational purposes and are not sold, shared or used to build profiles. The map on the tool page is the only third-party request on the page, and it is made only after you consent to it.
When you call the API without an ip parameter, the address being looked up is your own, taken
from the connection. That is the entire mechanism, and it is why the empty form returns your address.