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.

The tool page accepts the address directly in the path, so a result is linkable and bookmarkable.

PatternExample
/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.

GET /lookup/ip/{format}/lvl{level}/{ip}

Every path segment except the format is optional, which gives five usable shapes:

PathResolves 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

NameTypeDefaultDescription
formatenumrequired One of json, jsonp, xml, yaml, csv, html. Omit the segment entirely for plain text.
levelinteger0 Detail depth, 0 to 2. Values outside that range are clamped rather than rejected, so lvl9 behaves as lvl2.
ipstringcaller'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.
callbackstringcallback 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

shell
$ curl -s "https://api.troubleshooting.tools/v1/lookup/ip/json/lvl2/8.8.8.8" | jq .
json
{
  "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:

FieldTypeDescription
ipstringThe address that was looked up.
invalidbooleanInput was not a parseable IP address.
reservedbooleanAddress falls in a private or otherwise reserved range.
ip_versioninteger4, 6, or 0 when invalid.
ptr_recordstringReverse DNS name without the trailing dot, or the literal string No PTR record found. Omitted for invalid and reserved addresses.
additional_contextobjectOnly 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

SegmentContent-TypeNotes
jsonapplication/jsonPretty-printed, slashes and Unicode unescaped.
jsonpapplication/javascriptJSON wrapped in the callback function call.
xmlapplication/xmlRoot element <ip_lookup>, nested objects become nested elements.
yamltext/yamlHand-rolled emitter; values needing quotes are quoted.
csvtext/csvTwo lines, header and values. Nested keys are flattened with underscores, e.g. location_data_cityName.
htmltext/htmlStandalone table page with its own styling. Handy in a browser, not meant for scraping.
omittedtext/plainJust the address, or Invalid IP address / Reserved IP address.
shell
# 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.

ConditionResponse
Unparseable addressinvalid: true, ip_version: 0, level forced to 0.
Private or reserved addressreserved: true plus additional_context, level forced to 0.
Geolocation database unavailablelocation_error instead of location_data.
RDAP query failed or timed outregistry_error instead of registry_data.
Rate limit exceededHTTP 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

DataSource
LocationMaxMind GeoLite2 City, DB-IP City and IP2Location LITE DB11, all queried locally.
RegistryRDAP, live. RIPE first, falling back to ARIN and APNIC, plus LACNIC and AFRINIC for IPv6.
RoutingTeam Cymru IP-to-ASN service over DNS.
PTR recordDirect DNS query from the server.
Reserved rangesThe 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.