IP lookup endpoint, GET /v2/lookup/{ip}

Look up one IPv4 or IPv6 address with the host.sh API: parameters, the path and query forms, and examples in curl, JavaScript, Python, PHP and Go.

/v2/lookup/ answers with the location, time zone, ISP, ASN, connection type and anonymity flags of one address. Without a token it looks up your own IP; with a token, the IP you send.

Request

GET  https://api.host.sh/v2/lookup/                 your own IP
GET  https://api.host.sh/v2/lookup/?ip=8.8.8.8      any IP (token)
GET  https://api.host.sh/v2/lookup/8.8.8.8          any IP (token), trailing slash optional
POST https://api.host.sh/v2/lookup/                 parameters in a JSON or form body

HEAD works like GET, and OPTIONS (a browser's CORS preflight) gets 204. Any other method gets 405. For the token, see Authentication.

Parameters

Send them in the query string, a form body or a JSON body. The path form /v2/lookup/{ip} takes the address from the path instead of ip.

ParameterWhat it does
ipIPv4 or IPv6 address to look up. Needs a token, and with a token, send it: the API fills in your own address only for tokenless requests, so a token request without ip has no location or network data. To look up your own IP with a token, get it from /v2/ip/ first.
fieldsComma-separated dotted paths to keep, e.g. geo.country.code,connection.ip. Unknown paths are left out.
formatjson (the default) or text. text returns one value as plain text and needs fields with exactly one path to a single value.
langLanguage of continent, country, region and city names: en (default), de, es, fr, ja, pt-BR, ru or zh-CN. Falls back to English where a name is missing.
hostname1 adds connection.hostname, the reverse DNS (PTR) name. It can add up to a second.
pretty1 indents the JSON and sorts its keys.
callbackJSONP: wraps the JSON in a call to this function name (letters, digits, _, $ and .).
uaA User-Agent string to parse into client, os and device. Needs a token with the user agent permission.
alAn Accept-Language value to parse into client.languages. Needs a token.

An invalid value gets 400 with a reason such as INVALID_IP_ADDRESS, INVALID_LANGUAGE, INVALID_FORMAT or INVALID_CALLBACK; see Errors.

Your own IP, no token

curl
curl https://api.host.sh/v2/lookup/
JavaScript (fetch)
const res = await fetch('https://api.host.sh/v2/lookup/');
const data = await res.json();
if (!res.ok) throw new Error(data.errors[0].reason + ': ' + data.errors[0].detail);
console.log(data.connection.ip, data.geo.country.name, data.geo.city.name);
Python (requests)
import requests

r = requests.get("https://api.host.sh/v2/lookup/", timeout=10)
data = r.json()
if not r.ok:
    raise SystemExit(data["errors"][0]["reason"] + ": " + data["errors"][0]["detail"])
print(data["connection"]["ip"], data["geo"]["country"]["name"], data["geo"]["city"]["name"])
PHP
<?php
$context = stream_context_create(['http' => ['ignore_errors' => true, 'timeout' => 10]]);
$data = json_decode(file_get_contents('https://api.host.sh/v2/lookup/', false, $context), true);
if (isset($data['errors'])) {
    fwrite(STDERR, $data['errors'][0]['reason'] . ': ' . $data['errors'][0]['detail'] . "\n");
    exit(1);
}
echo $data['connection']['ip'], ' ', $data['geo']['country']['name'], "\n";
Go
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
)

type Lookup struct {
	Geo struct {
		Country struct {
			Code string `json:"code"`
			Name string `json:"name"`
		} `json:"country"`
		City struct {
			Name string `json:"name"`
		} `json:"city"`
	} `json:"geo"`
	Connection struct {
		IP string `json:"ip"`
	} `json:"connection"`
	Errors []struct {
		Reason string `json:"reason"`
		Detail string `json:"detail"`
	} `json:"errors"`
}

func main() {
	res, err := http.Get("https://api.host.sh/v2/lookup/")
	if err != nil {
		log.Fatal(err)
	}
	defer res.Body.Close()
	var data Lookup
	if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
		log.Fatal(err)
	}
	if len(data.Errors) > 0 {
		log.Fatal(data.Errors[0].Reason + ": " + data.Errors[0].Detail)
	}
	fmt.Println(data.Connection.IP, data.Geo.Country.Name, data.Geo.City.Name)
}

Any IP, with a token

Replace YOUR_TOKEN with your token. These send it as a Bearer header.

curl
curl -H "Authorization: Bearer YOUR_TOKEN" https://api.host.sh/v2/lookup/8.8.8.8
JavaScript (fetch)
const res = await fetch('https://api.host.sh/v2/lookup/8.8.8.8', {
  headers: { Authorization: 'Bearer YOUR_TOKEN' },
});
const data = await res.json();
if (!res.ok) throw new Error(data.errors[0].reason + ': ' + data.errors[0].detail);
console.log(data.geo.country.code, data.connection.isp.asn, data.connection.anonymity.vpn.score);
Python (requests)
import requests

r = requests.get(
    "https://api.host.sh/v2/lookup/8.8.8.8",
    headers={"Authorization": "Bearer YOUR_TOKEN"},
    timeout=10,
)
data = r.json()
if not r.ok:
    raise SystemExit(data["errors"][0]["reason"] + ": " + data["errors"][0]["detail"])
print(data["geo"]["country"]["code"], data["connection"]["isp"]["asn"])
PHP
<?php
$context = stream_context_create(['http' => [
    'header' => "Authorization: Bearer YOUR_TOKEN\r\n",
    'ignore_errors' => true,
    'timeout' => 10,
]]);
$data = json_decode(file_get_contents('https://api.host.sh/v2/lookup/8.8.8.8', false, $context), true);
if (isset($data['errors'])) {
    fwrite(STDERR, $data['errors'][0]['reason'] . ': ' . $data['errors'][0]['detail'] . "\n");
    exit(1);
}
echo $data['geo']['country']['code'], ' AS', $data['connection']['isp']['asn'], "\n";
Go
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
)

func main() {
	req, err := http.NewRequest("GET", "https://api.host.sh/v2/lookup/8.8.8.8", nil)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", "Bearer YOUR_TOKEN")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer res.Body.Close()
	var data map[string]any
	if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
		log.Fatal(err)
	}
	if res.StatusCode != http.StatusOK {
		e := data["errors"].([]any)[0].(map[string]any)
		log.Fatal(e["reason"], ": ", e["detail"])
	}
	country := data["geo"].(map[string]any)["country"].(map[string]any)
	isp := data["connection"].(map[string]any)["isp"].(map[string]any)
	fmt.Println(country["code"], isp["asn"])
}

Only the fields you need

fields keeps the answer small. With format=text and one field you get the bare value, handy in shell scripts:

curl "https://api.host.sh/v2/lookup/?fields=geo.country.code,connection.ip"
curl "https://api.host.sh/v2/lookup/?fields=geo.timezone.name&format=text"

The first prints {"connection":{"ip":"…"},"geo":{"country":{"code":"…"}}}, the second a line like Europe/Lisbon. A field that is null prints an empty line, true and false print as words.

Your IP only: /v2/ip/

GET /v2/ip/ returns the address the request comes from and nothing else, with no token and no lookup. Add ?format=json for {"ip": "…"}. On a dual-stack connection, https://ipv4.host.sh/v2/ip/ gives your IPv4 address.

curl
curl https://api.host.sh/v2/ip/
JavaScript (fetch)
const res = await fetch('https://api.host.sh/v2/ip/?format=json');
if (!res.ok) throw new Error('HTTP ' + res.status);
console.log((await res.json()).ip);
Python (requests)
import requests

r = requests.get("https://api.host.sh/v2/ip/", timeout=10)
r.raise_for_status()
print(r.text.strip())
PHP
<?php
$ip = file_get_contents('https://api.host.sh/v2/ip/');
if ($ip === false) {
    exit(1);
}
echo $ip;
Go
package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
)

func main() {
	res, err := http.Get("https://api.host.sh/v2/ip/")
	if err != nil {
		log.Fatal(err)
	}
	defer res.Body.Close()
	if res.StatusCode != http.StatusOK {
		log.Fatal(res.Status)
	}
	ip, err := io.ReadAll(res.Body)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Print(string(ip))
}

Response

A full answer for 8.8.8.8 with a token whose plan has every section, shortened where marked. Each field is explained under Response fields.

{
  "connection": {
    "anonymity": {
      "bot": { "name": null, "score": 0, "url": null, "useragent": null },
      "cdn": { "name": null, "score": 0, "url": null },
      "dch": { "name": "Google LLC", "score": 0.875, "url": null },
      "ddos": { "score": 0 },
      "proxy": { "score": 0 },
      "scraper": { "score": 0 },
      "tor": { "score": 0 },
      "vpn": { "score": 0 }
    },
    "bogon": false,
    "ip": "8.8.8.8",
    "isp": { "asn": 15169, "aso": "Google LLC", "mcc": null, "mnc": null, "name": "Google", "organization": "Google" },
    "type": "Corporate",
    "usage": { "com": { "name": null, "score": 0, "url": null }, "…": "edu, gov, isp, lib, mil, mob, org alike" },
    "version": 4
  },
  "geo": {
    "city": { "id": null, "name": null },
    "continent": { "code": "NA", "id": 6255149, "name": "North America" },
    "country": {
      "calling_code": "+1", "capital": "Washington D.C.", "code": "US",
      "currency": { "code": "USD", "name": "United States dollar", "symbol": "$" },
      "flag": "πŸ‡ΊπŸ‡Έ", "id": 6252001, "is_eu": false, "name": "United States", "tld": ".us"
    },
    "latitude": 37.751,
    "longitude": -97.822,
    "postal_code": null,
    "radius": 1000,
    "subdivision": { "code": null, "id": null, "name": null },
    "timezone": {
      "current_time": "2026-10-26T13:17:15-05:00", "is_dst": true,
      "name": "America/Chicago", "offset": 300, "utc_offset": "-05:00"
    }
  }
}

A tokenless lookup of your own IP also has user_agent, client, os and device, parsed from the request's own headers, and no connection.usage.

This page as Markdown: /docs/lookup/index.md