Make a request
Use GET /json to detect the caller, GET /json?ip=ADDRESS for a supplied address, or GET /ADDRESS/json for the path form. For only country code, region, city and timezone, use /json_lite. Both IPv4 and IPv6 literals are supported. URL-encode query values.
curl --fail "https://ip-info.com/json"
curl --fail "https://ip-info.com/json?ip=8.8.8.8"
curl --fail "https://ip-info.com/8.8.8.8/json"
curl --fail "https://ip-info.com/json?ip=2001:4860:4860::8888"
Replace https:// with http:// for HTTP access. HTTP is not redirected; HTTPS encrypts the connection. Reused HTTPS connections can avoid a fresh handshake.
JSON lookups accept only public unicast addresses. Private, loopback, multicast, documentation and other special-purpose ranges are rejected. IPv4-mapped IPv6 addresses are normalized to IPv4. Hostnames, duplicate parameters, unknown parameters and conflicting path/query IPs are rejected. Matching path/query values are accepted.
Get only your IP address
Use GET /myip for just the visitor’s IPv4 or IPv6 address, followed by a newline, with Content-Type: text/plain; charset=utf-8. No JSON wrapper or geolocation fields are included.
curl --fail https://ip-info.com/myip8.8.8.8The example address is illustrative. This endpoint uses the same trusted-proxy and authenticated-ingress rules as /json and reports the requesting device or proxy’s network exit. Query parameters are ignored and cannot override the caller address. It works without the geolocation database and also returns local/private caller addresses when run locally. IPv4-mapped IPv6 addresses are normalized to IPv4.
Responses use Cache-Control: no-store and support public cross-origin GET. If the caller cannot be determined or trusted-ingress headers are invalid, the response is HTTP 400 with the standard JSON error body. HEAD returns the same headers with no body.
The full JSON response
This example illustrates the response format. Live values may differ. Every listed field is present; unavailable values are null.
{
"ip": "8.8.8.8",
"city": "Mountain View",
"city_geoname_id": 5375480,
"region": "California",
"region_code": "CA",
"region_geoname_id": 5332921,
"district": "Santa Clara",
"district_code": null,
"district_geoname_id": 5393021,
"country": "US",
"country_name": "United States",
"country_geoname_id": 6252001,
"is_eu": false,
"continent": "NA",
"continent_name": "North America",
"continent_geoname_id": 6255149,
"loc": "37.422,-122.085",
"latitude": 37.422,
"longitude": -122.085,
"postal": "94043",
"timezone": "America/Los_Angeles",
"weather_code": "USCA0746",
"asn": 15169,
"as_name": "Google LLC",
"isp": "Google LLC",
"org": "Level 3",
"connection_type": "Corporate",
"user_type": "hosting",
"is_anycast": true
}
Field reference
- ipstring
- Normalized public IP address that was looked up.
- citystring | null
- City name in English.
- city_geoname_idinteger | null
- City GeoNames identifier.
- regionstring | null
- First administrative subdivision in English.
- region_codestring | null
- Subdivision code as stored in the database; not prefixed with country.
- region_geoname_idinteger | null
- First subdivision GeoNames identifier.
- districtstring | null
- Second administrative subdivision in English.
- district_codestring | null
- Second subdivision code, where available.
- district_geoname_idinteger | null
- Second subdivision GeoNames identifier.
- countrystring | null
- Two-letter ISO country code.
- country_namestring | null
- Country name in English.
- country_geoname_idinteger | null
- Country GeoNames identifier.
- is_euboolean | null
- European Union membership; null means unknown.
- continentstring | null
- Two-letter continent code.
- continent_namestring | null
- Continent name in English.
- continent_geoname_idinteger | null
- Continent GeoNames identifier.
- locstring | null
- Approximate latitude,longitude pair; null if either coordinate is missing.
- latitudenumber | null
- Approximate latitude in decimal degrees.
- longitudenumber | null
- Approximate longitude in decimal degrees.
- postalstring | null
- Postal code, where available.
- timezonestring | null
- IANA time zone name.
- weather_codestring | null
- Nearest weather station code supplied by the database.
- asninteger | null
- Autonomous system number as a number, without AS prefix.
- as_namestring | null
- Autonomous system organization name.
- ispstring | null
- Internet service provider name.
- orgstring | null
- Organization using the IP; separate from ASN and ISP.
- connection_typestring | null
- Network connection classification, such as Cable/DSL, Cellular, Corporate or Dialup.
- user_typestring | null
- Network usage classification, such as business, residential, cellular or hosting.
- is_anycastboolean | null
- Anycast classification; not a VPN or proxy detection flag.
Get only the location fields
Use GET /json_lite for the caller, GET /json_lite?ip=ADDRESS for a supplied address, or GET /ADDRESS/json_lite for the path form. IPv4 and IPv6 are supported. Successful responses are compact application/json with exactly four fields: country_code, region, city and timezone.
curl --fail "https://ip-info.com/json_lite"
curl --fail "https://ip-info.com/json_lite?ip=8.8.8.8"
curl --fail "https://ip-info.com/8.8.8.8/json_lite"
curl --fail "https://ip-info.com/json_lite?ip=2001:4860:4860::8888"{"country_code":"US","region":"California","city":"Mountain View","timezone":"America/Los_Angeles"}This example is illustrative. Every key is present, with null for an unavailable value. country_code is the same two-letter ISO code returned as country by /json. The full response keeps its existing field names. Lite lookups use the same database, caller detection, input validation and JSON errors as full lookups. Public GET CORS, Cache-Control: no-store and HEAD requests are supported.
- country_codestring | null
- Two-letter ISO country code.
- regionstring | null
- First administrative subdivision in English.
- citystring | null
- City name in English.
- timezonestring | null
- IANA time zone name.
Use your language
curl --fail --max-time 10 "https://ip-info.com/json"
Select Full JSON or Lite JSON for each language and lookup mode. Complete examples remain available below without JavaScript. The interactive selector also offers HTTP variants.
cURL — complete examples
Detect caller IP
curl --fail --max-time 10 "https://ip-info.com/json"Look up a supplied IP
curl --fail --max-time 10 "https://ip-info.com/json?ip=8.8.8.8"Lite location for the caller
curl --fail --max-time 10 "https://ip-info.com/json_lite"Lite location for a supplied IP
curl --fail --max-time 10 "https://ip-info.com/json_lite?ip=8.8.8.8"JavaScript / Node.js — complete examples
Detect caller IP
// Node.js 18+ or a modern browser
const response = await fetch("https://ip-info.com/json", {
signal: AbortSignal.timeout(10000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Look up a supplied IP
// Node.js 18+ or a modern browser
const response = await fetch("https://ip-info.com/json?ip=8.8.8.8", {
signal: AbortSignal.timeout(10000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Lite location for the caller
// Node.js 18+ or a modern browser
const response = await fetch("https://ip-info.com/json_lite", {
signal: AbortSignal.timeout(10000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Lite location for a supplied IP
// Node.js 18+ or a modern browser
const response = await fetch("https://ip-info.com/json_lite?ip=8.8.8.8", {
signal: AbortSignal.timeout(10000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Python — complete examples
Detect caller IP
# Python 3; standard library only
import json
from urllib.request import urlopen
with urlopen("https://ip-info.com/json", timeout=10) as response:
print(json.load(response))Look up a supplied IP
# Python 3; standard library only
import json
from urllib.request import urlopen
with urlopen("https://ip-info.com/json?ip=8.8.8.8", timeout=10) as response:
print(json.load(response))Lite location for the caller
# Python 3; standard library only
import json
from urllib.request import urlopen
with urlopen("https://ip-info.com/json_lite", timeout=10) as response:
print(json.load(response))Lite location for a supplied IP
# Python 3; standard library only
import json
from urllib.request import urlopen
with urlopen("https://ip-info.com/json_lite?ip=8.8.8.8", timeout=10) as response:
print(json.load(response))PHP — complete examples
Detect caller IP
<?php
$curl = curl_init('https://ip-info.com/json');
curl_setopt_array($curl, [Chttps://ip-info.com/jsonOPT_RETURNTRANSFER => true,
Chttps://ip-info.com/jsonOPT_TIMEOUT => 10, Chttps://ip-info.com/jsonOPT_FAILONERROR => true]);
$body = curl_exec($curl);
if ($body === false) { throw new RuntimeException(curl_error($curl)); }
curl_close($curl);
print_r(json_decode($body, true, 512, JSON_THROW_ON_ERROR));Look up a supplied IP
<?php
$curl = curl_init('https://ip-info.com/json?ip=8.8.8.8');
curl_setopt_array($curl, [Chttps://ip-info.com/json?ip=8.8.8.8OPT_RETURNTRANSFER => true,
Chttps://ip-info.com/json?ip=8.8.8.8OPT_TIMEOUT => 10, Chttps://ip-info.com/json?ip=8.8.8.8OPT_FAILONERROR => true]);
$body = curl_exec($curl);
if ($body === false) { throw new RuntimeException(curl_error($curl)); }
curl_close($curl);
print_r(json_decode($body, true, 512, JSON_THROW_ON_ERROR));Lite location for the caller
<?php
$curl = curl_init('https://ip-info.com/json_lite');
curl_setopt_array($curl, [Chttps://ip-info.com/json_liteOPT_RETURNTRANSFER => true,
Chttps://ip-info.com/json_liteOPT_TIMEOUT => 10, Chttps://ip-info.com/json_liteOPT_FAILONERROR => true]);
$body = curl_exec($curl);
if ($body === false) { throw new RuntimeException(curl_error($curl)); }
curl_close($curl);
print_r(json_decode($body, true, 512, JSON_THROW_ON_ERROR));Lite location for a supplied IP
<?php
$curl = curl_init('https://ip-info.com/json_lite?ip=8.8.8.8');
curl_setopt_array($curl, [Chttps://ip-info.com/json_lite?ip=8.8.8.8OPT_RETURNTRANSFER => true,
Chttps://ip-info.com/json_lite?ip=8.8.8.8OPT_TIMEOUT => 10, Chttps://ip-info.com/json_lite?ip=8.8.8.8OPT_FAILONERROR => true]);
$body = curl_exec($curl);
if ($body === false) { throw new RuntimeException(curl_error($curl)); }
curl_close($curl);
print_r(json_decode($body, true, 512, JSON_THROW_ON_ERROR));Go — complete examples
Detect caller IP
package main
import ("encoding/json"; "fmt"; "net/http"; "time")
func main() {
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Get("https://ip-info.com/json")
if err != nil { panic(err) }
defer resp.Body.Close()
if resp.StatusCode != 200 { panic(resp.Status) }
var data map[string]any
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) }
fmt.Println(data)
}Look up a supplied IP
package main
import ("encoding/json"; "fmt"; "net/http"; "time")
func main() {
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Get("https://ip-info.com/json?ip=8.8.8.8")
if err != nil { panic(err) }
defer resp.Body.Close()
if resp.StatusCode != 200 { panic(resp.Status) }
var data map[string]any
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) }
fmt.Println(data)
}Lite location for the caller
package main
import ("encoding/json"; "fmt"; "net/http"; "time")
func main() {
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Get("https://ip-info.com/json_lite")
if err != nil { panic(err) }
defer resp.Body.Close()
if resp.StatusCode != 200 { panic(resp.Status) }
var data map[string]any
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) }
fmt.Println(data)
}Lite location for a supplied IP
package main
import ("encoding/json"; "fmt"; "net/http"; "time")
func main() {
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Get("https://ip-info.com/json_lite?ip=8.8.8.8")
if err != nil { panic(err) }
defer resp.Body.Close()
if resp.StatusCode != 200 { panic(resp.Status) }
var data map[string]any
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) }
fmt.Println(data)
}Rust — complete examples
Detect caller IP
// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }
// serde_json = "1"
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::blocking::Client::builder()
.timeout(std::time::Duration::from_secs(10)).build()?;
let data: serde_json::Value = client.get("https://ip-info.com/json")
.send()?.error_for_status()?.json()?;
println!("{data}");
Ok(())
}Look up a supplied IP
// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }
// serde_json = "1"
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::blocking::Client::builder()
.timeout(std::time::Duration::from_secs(10)).build()?;
let data: serde_json::Value = client.get("https://ip-info.com/json?ip=8.8.8.8")
.send()?.error_for_status()?.json()?;
println!("{data}");
Ok(())
}Lite location for the caller
// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }
// serde_json = "1"
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::blocking::Client::builder()
.timeout(std::time::Duration::from_secs(10)).build()?;
let data: serde_json::Value = client.get("https://ip-info.com/json_lite")
.send()?.error_for_status()?.json()?;
println!("{data}");
Ok(())
}Lite location for a supplied IP
// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }
// serde_json = "1"
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::blocking::Client::builder()
.timeout(std::time::Duration::from_secs(10)).build()?;
let data: serde_json::Value = client.get("https://ip-info.com/json_lite?ip=8.8.8.8")
.send()?.error_for_status()?.json()?;
println!("{data}");
Ok(())
}Java — complete examples
Detect caller IP
// Java 11+
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
class Lookup {
public static void main(String[] args) throws Exception {
var request = HttpRequest.newBuilder(URI.create("https://ip-info.com/json"))
.timeout(Duration.ofSeconds(10)).GET().build();
var response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) throw new RuntimeException("Lookup failed");
System.out.println(response.body());
}
}Look up a supplied IP
// Java 11+
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
class Lookup {
public static void main(String[] args) throws Exception {
var request = HttpRequest.newBuilder(URI.create("https://ip-info.com/json?ip=8.8.8.8"))
.timeout(Duration.ofSeconds(10)).GET().build();
var response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) throw new RuntimeException("Lookup failed");
System.out.println(response.body());
}
}Lite location for the caller
// Java 11+
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
class Lookup {
public static void main(String[] args) throws Exception {
var request = HttpRequest.newBuilder(URI.create("https://ip-info.com/json_lite"))
.timeout(Duration.ofSeconds(10)).GET().build();
var response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) throw new RuntimeException("Lookup failed");
System.out.println(response.body());
}
}Lite location for a supplied IP
// Java 11+
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
class Lookup {
public static void main(String[] args) throws Exception {
var request = HttpRequest.newBuilder(URI.create("https://ip-info.com/json_lite?ip=8.8.8.8"))
.timeout(Duration.ofSeconds(10)).GET().build();
var response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) throw new RuntimeException("Lookup failed");
System.out.println(response.body());
}
}C# — complete examples
Detect caller IP
// .NET 6+
using System;
using System.Net.Http;
using System.Text.Json;
using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
using var response = await client.GetAsync("https://ip-info.com/json");
response.EnsureSuccessStatusCode();
using var data = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
Console.WriteLine(data.RootElement);Look up a supplied IP
// .NET 6+
using System;
using System.Net.Http;
using System.Text.Json;
using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
using var response = await client.GetAsync("https://ip-info.com/json?ip=8.8.8.8");
response.EnsureSuccessStatusCode();
using var data = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
Console.WriteLine(data.RootElement);Lite location for the caller
// .NET 6+
using System;
using System.Net.Http;
using System.Text.Json;
using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
using var response = await client.GetAsync("https://ip-info.com/json_lite");
response.EnsureSuccessStatusCode();
using var data = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
Console.WriteLine(data.RootElement);Lite location for a supplied IP
// .NET 6+
using System;
using System.Net.Http;
using System.Text.Json;
using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
using var response = await client.GetAsync("https://ip-info.com/json_lite?ip=8.8.8.8");
response.EnsureSuccessStatusCode();
using var data = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
Console.WriteLine(data.RootElement);Ruby — complete examples
Detect caller IP
require "net/http"
require "json"
uri = URI("https://ip-info.com/json")
response = Net::HTTP.start(uri.host, uri.port,
use_ssl: uri.scheme == "https", open_timeout: 10, read_timeout: 10) do |http|
http.get(uri.request_uri)
end
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
puts JSON.parse(response.body)Look up a supplied IP
require "net/http"
require "json"
uri = URI("https://ip-info.com/json?ip=8.8.8.8")
response = Net::HTTP.start(uri.host, uri.port,
use_ssl: uri.scheme == "https", open_timeout: 10, read_timeout: 10) do |http|
http.get(uri.request_uri)
end
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
puts JSON.parse(response.body)Lite location for the caller
require "net/http"
require "json"
uri = URI("https://ip-info.com/json_lite")
response = Net::HTTP.start(uri.host, uri.port,
use_ssl: uri.scheme == "https", open_timeout: 10, read_timeout: 10) do |http|
http.get(uri.request_uri)
end
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
puts JSON.parse(response.body)Lite location for a supplied IP
require "net/http"
require "json"
uri = URI("https://ip-info.com/json_lite?ip=8.8.8.8")
response = Net::HTTP.start(uri.host, uri.port,
use_ssl: uri.scheme == "https", open_timeout: 10, read_timeout: 10) do |http|
http.get(uri.request_uri)
end
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
puts JSON.parse(response.body)Verify a Shifter proxy exit
Send the caller-detection request through your proxy to see its exit IP and database location. Replace the username and password with your own Shifter credentials; do not put credentials into the IP Info URL.
curl --fail --max-time 15 \
--proxy http://p.shifter.io:443 \
--proxy-user "customer-USERNAME-country-us-sid-123ABC:PASSWORD" \
https://ip-info.com/jsonCompare country with your requested country code. Different services may report different city or country values.
Errors and retries
{
"error": {
"code": "invalid_ip",
"message": "Supply a literal IPv4 or IPv6 address."
}
}- 400 · invalid_ip
- Malformed, conflicting, non-public, or unsupported lookup input.
- 404 · ip_not_found
- No record in the active database.
- 405 · method_not_allowed
- Use GET for lookups.
- 431 · request_too_large
- Request headers exceed the configured limit.
- 503 · database_unavailable
- Database unavailable; retry later with backoff.
- 504 · timeout
- Request timed out; retry later with backoff.
Fix 4xx inputs before retrying. For 503/504 or connection failures, use bounded exponential backoff, for example 1, 2 and 4 seconds, then report failure. Low-level malformed HTTP may be rejected before routing.
Browser access and caching
Public GET requests allow cross-origin reads. API responses use Cache-Control: no-store; do not cache visitor-specific responses in shared caches. An HTTPS page must use HTTPS for fetch requests to avoid mixed-content blocking.
Data limitations
Coordinates are approximate. Anycast IPs may be used in many locations. ASN and ISP fields do not establish a person’s identity or prove VPN/proxy status. The first subdivision maps to region and the second to district. English names only are returned. Data may be incomplete or outdated.
Download the OpenAPI 3.1 definition or read the plain-text reference.