Skip to main content

Batch IP Lookup

The Batch IP Lookup endpoint allows you to look up intelligence data for multiple IP addresses in a single request. This is more efficient than making individual requests when you need to query many IPs.

Endpoint​

POST /v1/ip/batch

Authentication​

This endpoint requires an API key. See Authentication for details.

Request​

Headers​

HeaderRequiredDescription
X-API-KeyYesYour API key
Content-TypeYesMust be application/json
AcceptNoSet to application/json (default)

Request Body​

{
"ips": ["8.8.8.8", "1.1.1.1", "208.67.222.222"]
}
FieldTypeRequiredDescription
ipsarrayYesArray of IPv4 or IPv6 addresses (max 1000)

Batch Size Limits​

TierMaximum Batch Size
Free10 IPs
Starter100 IPs
Pro500 IPs
Enterprise1000 IPs

Response Headers​

HeaderDescription
X-Request-IDUnique identifier for the request
X-RateLimit-LimitMaximum requests allowed per time window
X-RateLimit-RemainingRequests remaining in current window
X-RateLimit-ResetUnix timestamp when the limit resets

Response​

Success Response (200)​

{
"data": {
"8.8.8.8": {
"ip": "8.8.8.8",
"prefix": "8.8.8.0/24",
"asn": 15169,
"asn_name": "Google LLC",
"country": "US",
"detection": {
"is_datacenter": true,
"is_ai_crawler": false,
"cloud_provider": "Google Cloud"
},
"threat": {
"score": 0,
"level": "low"
}
},
"1.1.1.1": {
"ip": "1.1.1.1",
"prefix": "1.1.1.0/24",
"asn": 13335,
"asn_name": "Cloudflare, Inc.",
"country": "US",
"detection": {
"is_datacenter": true,
"is_ai_crawler": false,
"cloud_provider": "Cloudflare"
},
"threat": {
"score": 0,
"level": "low"
}
},
"208.67.222.222": {
"ip": "208.67.222.222",
"prefix": "208.67.222.0/24",
"asn": 36692,
"asn_name": "Cisco OpenDNS, LLC",
"country": "US",
"detection": {
"is_datacenter": true,
"is_ai_crawler": false
},
"threat": {
"score": 0,
"level": "low"
}
}
},
"meta": {
"request_id": "550e8400-e29b-41d4-a716-446655440001",
"processing_time_ms": 15,
"dataset_version": 1
}
}

Response Structure​

The response data field is a map where:

  • Keys are the queried IP addresses
  • Values are the lookup results (same schema as single IP lookup)

If an IP is not found in the database, it will be omitted from the results.

Error Responses​

400 Bad Request - Invalid JSON​

{
"data": {
"error": "invalid JSON body",
"code": "INVALID_JSON"
},
"meta": {
"processing_time_ms": 0
}
}

400 Bad Request - Missing IPs Array​

{
"data": {
"error": "ips array is required",
"code": "MISSING_IPS"
},
"meta": {
"processing_time_ms": 0
}
}

400 Bad Request - Batch Too Large​

{
"data": {
"error": "batch size exceeds limit of 1000",
"code": "BATCH_TOO_LARGE"
},
"meta": {
"processing_time_ms": 0
}
}

400 Bad Request - Invalid IP in Batch​

{
"data": {
"error": "invalid IP in batch: not-an-ip",
"code": "INVALID_IP"
},
"meta": {
"processing_time_ms": 0
}
}

401 Unauthorized​

{
"data": {
"error": "unauthorized",
"code": "UNAUTHORIZED"
},
"meta": {
"processing_time_ms": 0
}
}

429 Rate Limit Exceeded​

{
"data": {
"error": "rate limit exceeded",
"code": "RATE_LIMIT_EXCEEDED"
},
"meta": {
"processing_time_ms": 0
}
}

Code Examples​

cURL​

# Basic batch lookup
curl -X POST "https://api.limesindex.com/v1/ip/batch" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"ips": ["8.8.8.8", "1.1.1.1", "208.67.222.222"]
}'

# With jq for formatted output
curl -s -X POST "https://api.limesindex.com/v1/ip/batch" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"ips": ["8.8.8.8", "1.1.1.1"]}' | jq .

# From a file
echo '{"ips": ["8.8.8.8", "1.1.1.1", "9.9.9.9"]}' > ips.json
curl -X POST "https://api.limesindex.com/v1/ip/batch" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d @ips.json

Python​

import requests
import os
from typing import List, Dict

API_KEY = os.environ.get("LIMESINDEX_API_KEY")
BASE_URL = "https://api.limesindex.com"

def batch_lookup(ips: List[str]) -> Dict:
"""Look up intelligence data for multiple IP addresses."""
response = requests.post(
f"{BASE_URL}/v1/ip/batch",
headers={
"X-API-Key": API_KEY,
"Content-Type": "application/json",
"Accept": "application/json"
},
json={"ips": ips}
)
response.raise_for_status()
return response.json()

# Example usage
ips_to_check = [
"8.8.8.8",
"1.1.1.1",
"208.67.222.222",
"9.9.9.9",
"185.199.108.153"
]

result = batch_lookup(ips_to_check)

# Process results
for ip, data in result["data"].items():
print(f"\n{ip}:")
print(f" ASN: {data['asn']} ({data['asn_name']})")
print(f" Country: {data['country']}")
print(f" Datacenter: {data['detection']['is_datacenter']}")
print(f" Threat Level: {data['threat']['level']}")

# Find IPs not in results (not found in database)
found_ips = set(result["data"].keys())
not_found = set(ips_to_check) - found_ips
if not_found:
print(f"\nIPs not found: {not_found}")

JavaScript (Node.js)​

const API_KEY = process.env.LIMESINDEX_API_KEY;
const BASE_URL = 'https://api.limesindex.com';

async function batchLookup(ips) {
const response = await fetch(`${BASE_URL}/v1/ip/batch`, {
method: 'POST',
headers: {
'X-API-Key': API_KEY,
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({ ips })
});

if (!response.ok) {
const error = await response.json();
throw new Error(`API Error: ${error.data.error} (${error.data.code})`);
}

return response.json();
}

// Example usage
async function main() {
const ipsToCheck = [
'8.8.8.8',
'1.1.1.1',
'208.67.222.222',
'9.9.9.9'
];

try {
const result = await batchLookup(ipsToCheck);

// Process results
for (const [ip, data] of Object.entries(result.data)) {
console.log(`\n${ip}:`);
console.log(` ASN: ${data.asn} (${data.asn_name})`);
console.log(` Country: ${data.country}`);
console.log(` Datacenter: ${data.detection.is_datacenter}`);
console.log(` Threat Level: ${data.threat.level}`);
}

console.log(`\nProcessed in ${result.meta.processing_time_ms}ms`);
} catch (error) {
console.error('Batch lookup failed:', error.message);
}
}

main();

Go​

package main

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)

const baseURL = "https://api.limesindex.com"

type BatchRequest struct {
IPs []string `json:"ips"`
}

type Detection struct {
IsDatacenter bool `json:"is_datacenter"`
IsTorExit bool `json:"is_tor_exit"`
IsProxy bool `json:"is_proxy"`
IsVPN bool `json:"is_vpn"`
IsAICrawler bool `json:"is_ai_crawler"`
CloudProvider string `json:"cloud_provider,omitempty"`
}

type Threat struct {
Score int `json:"score"`
Level string `json:"level"`
}

type IPData struct {
IP string `json:"ip"`
Prefix string `json:"prefix"`
ASN int `json:"asn"`
ASNName string `json:"asn_name"`
Country string `json:"country"`
Detection Detection `json:"detection"`
Threat Threat `json:"threat"`
}

type Meta struct {
RequestID string `json:"request_id"`
ProcessingTimeMs int `json:"processing_time_ms"`
DatasetVersion int `json:"dataset_version"`
}

type BatchResponse struct {
Data map[string]IPData `json:"data"`
Meta Meta `json:"meta"`
}

func batchLookup(apiKey string, ips []string) (*BatchResponse, error) {
reqBody := BatchRequest{IPs: ips}
jsonBody, err := json.Marshal(reqBody)
if err != nil {
return nil, err
}

req, err := http.NewRequest("POST", baseURL+"/v1/ip/batch", bytes.NewBuffer(jsonBody))
if err != nil {
return nil, err
}

req.Header.Set("X-API-Key", apiKey)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API error: %s", string(body))
}

var result BatchResponse
if err := json.Unmarshal(body, &result); err != nil {
return nil, err
}

return &result, nil
}

func main() {
apiKey := os.Getenv("LIMESINDEX_API_KEY")

ips := []string{
"8.8.8.8",
"1.1.1.1",
"208.67.222.222",
"9.9.9.9",
}

result, err := batchLookup(apiKey, ips)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}

for ip, data := range result.Data {
fmt.Printf("\n%s:\n", ip)
fmt.Printf(" ASN: %d (%s)\n", data.ASN, data.ASNName)
fmt.Printf(" Country: %s\n", data.Country)
fmt.Printf(" Datacenter: %t\n", data.Detection.IsDatacenter)
fmt.Printf(" Threat Level: %s\n", data.Threat.Level)
}

fmt.Printf("\nProcessed in %dms\n", result.Meta.ProcessingTimeMs)
}

Use Cases​

Log Analysis​

import requests
from collections import defaultdict

def analyze_access_logs(log_ips: List[str]) -> dict:
"""Analyze a list of IPs from access logs."""
# Deduplicate IPs
unique_ips = list(set(log_ips))

# Process in batches of 1000
batch_size = 1000
all_results = {}

for i in range(0, len(unique_ips), batch_size):
batch = unique_ips[i:i + batch_size]
result = batch_lookup(batch)
all_results.update(result["data"])

# Analyze results
stats = {
"total": len(unique_ips),
"datacenter": 0,
"vpn": 0,
"tor": 0,
"residential": 0,
"ai_crawler": 0,
"by_country": defaultdict(int),
"by_asn": defaultdict(int),
"high_threat": []
}

for ip, data in all_results.items():
detection = data["detection"]
stats["by_country"][data["country"]] += 1
stats["by_asn"][data["asn_name"]] += 1

if detection.get("is_datacenter"):
stats["datacenter"] += 1
if detection.get("is_vpn"):
stats["vpn"] += 1
if detection.get("is_tor_exit"):
stats["tor"] += 1
if detection.get("is_residential"):
stats["residential"] += 1
if detection.get("is_ai_crawler"):
stats["ai_crawler"] += 1

if data["threat"]["score"] >= 70:
stats["high_threat"].append(ip)

return stats

Firewall Rule Generation​

def generate_firewall_rules(ips: List[str]) -> List[str]:
"""Generate firewall block rules for suspicious IPs."""
result = batch_lookup(ips)
rules = []

for ip, data in result["data"].items():
if data["threat"]["score"] >= 70:
rules.append(f"iptables -A INPUT -s {ip} -j DROP # Threat: {data['threat']['level']}")
elif data["detection"].get("is_tor_exit"):
rules.append(f"iptables -A INPUT -s {ip} -j DROP # Tor exit node")

return rules

Geographic Distribution Analysis​

async function analyzeGeographicDistribution(ips) {
const result = await batchLookup(ips);

const countryStats = {};
for (const [ip, data] of Object.entries(result.data)) {
const country = data.country;
if (!countryStats[country]) {
countryStats[country] = { count: 0, ips: [] };
}
countryStats[country].count++;
countryStats[country].ips.push(ip);
}

// Sort by count
const sorted = Object.entries(countryStats)
.sort((a, b) => b[1].count - a[1].count);

return sorted;
}

Performance Considerations​

Optimal Batch Size​

While the maximum batch size is 1000 IPs, consider these factors:

Batch SizeLatencyThroughput
1-50~50msGood for real-time
50-200~100msBalanced
200-500~200msHigh throughput
500-1000~400msMaximum throughput

Rate Limiting​

Batch requests count as a single request against your rate limit, making them much more efficient for high-volume lookups.

Parallel Batching​

For very large datasets, process batches in parallel:

import asyncio
import aiohttp

async def batch_lookup_async(session, ips):
async with session.post(
f"{BASE_URL}/v1/ip/batch",
headers={"X-API-Key": API_KEY, "Content-Type": "application/json"},
json={"ips": ips}
) as response:
return await response.json()

async def process_large_dataset(all_ips, batch_size=500, concurrency=5):
batches = [all_ips[i:i+batch_size] for i in range(0, len(all_ips), batch_size)]

async with aiohttp.ClientSession() as session:
semaphore = asyncio.Semaphore(concurrency)

async def limited_lookup(batch):
async with semaphore:
return await batch_lookup_async(session, batch)

results = await asyncio.gather(*[limited_lookup(b) for b in batches])

return results