# host.sh docs: 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

```text
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](https://www.host.sh/docs/index.md#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`.

| Parameter | What it does |
|---|---|
| `ip` | IPv4 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. |
| `fields` | Comma-separated dotted paths to keep, e.g. `geo.country.code,connection.ip`. Unknown paths are left out. |
| `format` | `json` (the default) or `text`. `text` returns one value as plain text and needs `fields` with exactly one path to a single value. |
| `lang` | Language 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. |
| `hostname` | `1` adds `connection.hostname`, the reverse DNS (PTR) name. It can add up to a second. |
| `pretty` | `1` indents the JSON and sorts its keys. |
| `callback` | JSONP: wraps the JSON in a call to this function name (letters, digits, `_`, `$` and `.`). |
| `ua` | A `User-Agent` string to parse into `client`, `os` and `device`. Needs a token with the user agent permission. |
| `al` | An `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](https://www.host.sh/docs/errors/index.md).

## Your own IP, no token

```sh
curl https://api.host.sh/v2/lookup/
```

```js
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
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.

```sh
curl -H "Authorization: Bearer YOUR_TOKEN" https://api.host.sh/v2/lookup/8.8.8.8
```

```js
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
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:

```sh
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.

```sh
curl https://api.host.sh/v2/ip/
```

```js
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
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](https://www.host.sh/docs/fields/index.md).

```json
{
  "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`.
