Batch IP lookup, up to 100 addresses at once

Look up to 100 IPv4 and IPv6 addresses in one POST with the host.sh batch API: body formats, options, per-address errors and code examples.

POST /v2/lookup/batch/ takes a list of addresses and answers with one result per address, in the same order. It always needs a token, sent as a Bearer header or ?token= (the body is the list, so it can't carry the token). Every address counts as one lookup.

Request

POST https://api.host.sh/v2/lookup/batch/
Authorization: Bearer YOUR_TOKEN

The body is one of:

  • a JSON array: ["8.8.8.8", "1.1.1.1"]
  • a JSON object with an ips array: {"ips": ["8.8.8.8", "1.1.1.1"]}
  • plain text, addresses separated by commas, spaces or new lines: 8.8.8.8, 1.1.1.1

At most 100 addresses per request. The query string takes fields, lang and pretty, which work as on Lookup and apply to every result. OPTIONS (CORS preflight) gets 204; GET and other methods get 405.

Examples

Replace YOUR_TOKEN with your token.

curl
curl -H "Authorization: Bearer YOUR_TOKEN" \
  -d '["8.8.8.8", "1.1.1.1"]' https://api.host.sh/v2/lookup/batch/
JavaScript (fetch)
const res = await fetch('https://api.host.sh/v2/lookup/batch/?fields=connection.ip,geo.country.code', {
  method: 'POST',
  headers: { Authorization: 'Bearer YOUR_TOKEN', 'Content-Type': 'application/json' },
  body: JSON.stringify(['8.8.8.8', '1.1.1.1']),
});
const data = await res.json();
if (!res.ok) throw new Error(data.errors[0].reason + ': ' + data.errors[0].detail);
for (const r of data.results) console.log(r.errors ? r.errors[0].reason : r.connection.ip + ' ' + r.geo.country.code);
Python (requests)
import requests

r = requests.post(
    "https://api.host.sh/v2/lookup/batch/",
    params={"fields": "connection.ip,geo.country.code"},
    headers={"Authorization": "Bearer YOUR_TOKEN"},
    json=["8.8.8.8", "1.1.1.1"],
    timeout=30,
)
data = r.json()
if not r.ok:
    raise SystemExit(data["errors"][0]["reason"] + ": " + data["errors"][0]["detail"])
for result in data["results"]:
    print(result["errors"][0]["reason"] if "errors" in result else result["connection"]["ip"])
PHP
<?php
$context = stream_context_create(['http' => [
    'method' => 'POST',
    'header' => "Authorization: Bearer YOUR_TOKEN\r\nContent-Type: application/json\r\n",
    'content' => json_encode(['8.8.8.8', '1.1.1.1']),
    'ignore_errors' => true,
    'timeout' => 30,
]]);
$data = json_decode(file_get_contents('https://api.host.sh/v2/lookup/batch/?fields=connection.ip', false, $context), true);
if (isset($data['errors'])) {
    fwrite(STDERR, $data['errors'][0]['reason'] . ': ' . $data['errors'][0]['detail'] . "\n");
    exit(1);
}
foreach ($data['results'] as $result) {
    echo isset($result['errors']) ? $result['errors'][0]['reason'] : $result['connection']['ip'], "\n";
}
Go
package main

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

type Result struct {
	Connection struct {
		IP string `json:"ip"`
	} `json:"connection"`
	Errors []struct {
		Reason string `json:"reason"`
		Detail string `json:"detail"`
	} `json:"errors"`
}

func main() {
	body, _ := json.Marshal([]string{"8.8.8.8", "1.1.1.1"})
	req, err := http.NewRequest("POST", "https://api.host.sh/v2/lookup/batch/?fields=connection.ip", bytes.NewReader(body))
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", "Bearer YOUR_TOKEN")
	req.Header.Set("Content-Type", "application/json")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer res.Body.Close()
	var data struct {
		Results []Result `json:"results"`
		Result
	}
	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)
	}
	for _, r := range data.Results {
		if len(r.Errors) > 0 {
			fmt.Println(r.Errors[0].Reason)
		} else {
			fmt.Println(r.Connection.IP)
		}
	}
}

Response

results has one item per address, in request order. Each item is the same object a single lookup returns, filtered by fields. An address that isn't valid gets an errors item in its place, and the rest of the batch is still answered with 200:

{"results":[
  {"connection":{"ip":"8.8.8.8"},"geo":{"country":{"code":"US"}}},
  {"errors":[{"code":400,"message":"Bad request","reason":"INVALID_IP_ADDRESS","detail":"ip must be an IPv4 or IPv6 address"}]}
]}

Batch results have no user_agent, client, os or device sections.

Errors for the whole request

These fail the request instead of one item. Details are on Errors.

StatusReasonWhen
400NO_IPSThe body is empty or has no addresses
400TOO_MANY_IPSMore than 100 addresses
400BAD_REQUESTThe body starts like JSON but isn't valid JSON
400INVALID_LANGUAGElang is not a supported language
401TOKEN_MISSINGNo token
403TOKEN_INVALIDUnknown or disabled token, or no permission for batch lookups
405METHOD_NOT_ALLOWEDNot a POST

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