Developers — Examples

Code you can paste.

Working examples for every endpoint, in the languages people actually call it from. There is no SDK to install: it's one HTTPS GET, so your standard HTTP client is all you need.

Before you start

Put your key in an environment variable so it never lands in source control. Create one in your dashboard.

export IPAPI_KEY="your_api_key_here"

01

Look up an IP address or domain

The core call. Pass an IPv4 address, IPv6 address or domain name in the path and your key as ?key=.

cURL

curl "https://fast.ipapi.gr/api/lookup/8.8.8.8?key=$IPAPI_KEY"

# IPv6 and domains work the same way
curl "https://fast.ipapi.gr/api/lookup/2001:4860:4860::8888?key=$IPAPI_KEY"
curl "https://fast.ipapi.gr/api/lookup/github.com?key=$IPAPI_KEY"

JavaScript

const ip = '8.8.8.8';
const res = await fetch(`https://fast.ipapi.gr/api/lookup/${encodeURIComponent(ip)}?key=${process.env.IPAPI_KEY}`);
const data = await res.json();
if (!res.ok) throw new Error(`${res.status}: ${data.message}`);

console.log(data.country_code, data.city, data.asn);
console.log(data.risk); // Pro keys only: { is_ai, is_crawler, is_scanner }

Python

import os
import requests

r = requests.get(
    "https://fast.ipapi.gr/api/lookup/8.8.8.8",
    params={"key": os.environ["IPAPI_KEY"]},
    timeout=5,
)
data = r.json()
if not r.ok:
    raise RuntimeError(f"{r.status_code}: {data.get('message')}")

print(data["country_code"], data.get("city"), data.get("asn"))
print(data.get("risk"))  # Pro keys only

Go

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"os"
	"time"
)

type Lookup struct {
	IP          string `json:"ip"`
	Country     string `json:"country"`
	CountryCode string `json:"country_code"`
	City        string `json:"city"`
	Timezone    string `json:"timezone"`
	ISP         string `json:"isp"`
	ASN         string `json:"asn"`
	Risk        *struct {
		IsAI      bool `json:"is_ai"`
		IsCrawler bool `json:"is_crawler"`
		IsScanner bool `json:"is_scanner"`
	} `json:"risk"` // nil on free keys
}

func main() {
	client := &http.Client{Timeout: 5 * time.Second}
	u := "https://fast.ipapi.gr/api/lookup/" + url.PathEscape("8.8.8.8") +
		"?key=" + url.QueryEscape(os.Getenv("IPAPI_KEY"))

	resp, err := client.Get(u)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		log.Fatalf("lookup failed: %s", resp.Status)
	}

	var data Lookup
	if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
		log.Fatal(err)
	}
	fmt.Println(data.CountryCode, data.City, data.ASN)
}

PHP

<?php
$url = 'https://fast.ipapi.gr/api/lookup/' . rawurlencode('8.8.8.8')
     . '?key=' . urlencode(getenv('IPAPI_KEY'));

$ctx  = stream_context_create(['http' => ['timeout' => 5, 'ignore_errors' => true]]);
$data = json_decode(file_get_contents($url, false, $ctx), true);

if (($data['status'] ?? null) === 'error') {
    throw new RuntimeException($data['message']);
}

echo $data['country_code'], ' ', $data['city'] ?? '', ' ', $data['asn'] ?? '', PHP_EOL;

02

Look up your own public IP

GET /api/lookup/self answers for the address the request comes from: handy for scripts, servers and devices that need to know their own public IP and location.

cURL

curl "https://fast.ipapi.gr/api/lookup/self?key=$IPAPI_KEY"

JavaScript

const res = await fetch(`https://fast.ipapi.gr/api/lookup/self?key=${process.env.IPAPI_KEY}`);
const me = await res.json();
console.log(`Public IP ${me.ip} in ${me.city}, ${me.country_code} (${me.timezone})`);

Python

import os
import requests

me = requests.get(
    "https://fast.ipapi.gr/api/lookup/self",
    params={"key": os.environ["IPAPI_KEY"]},
    timeout=5,
).json()
print(f"Public IP {me['ip']} in {me.get('city')}, {me.get('country_code')}")

03

Check your plan and usage

GET /api/status returns your plan, daily limit, usage and reset time. Calling it does not count towards your limit, and it also sends X‑RateLimit‑* headers.

cURL

# -i shows the X-RateLimit-Limit / -Remaining / -Reset headers
curl -i "https://fast.ipapi.gr/api/status?key=$IPAPI_KEY"

JavaScript

const res = await fetch(`https://fast.ipapi.gr/api/status?key=${process.env.IPAPI_KEY}`);
const { data } = await res.json();
const { limit, used, remaining, reset } = data.rateLimit;

console.log(`${data.plan}: ${used} of ${limit} used, ${remaining} left, resets ${reset}`);

Python

import os
import requests

data = requests.get(
    "https://fast.ipapi.gr/api/status",
    params={"key": os.environ["IPAPI_KEY"]},
    timeout=5,
).json()["data"]
rl = data["rateLimit"]
print(f"{data['plan']}: {rl['used']} of {rl['limit']} used, resets {rl['reset']}")

04

Look up many IPs in one request

POST /api/lookup/batch takes up to 100 IPs and domains and returns one result per item, in order. Each distinct item counts as one request; invalid items come back as errors and are not counted.

cURL

curl -X POST "https://fast.ipapi.gr/api/lookup/batch?key=$IPAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ips": ["8.8.8.8", "1.1.1.1", "github.com"]}'

JavaScript

const ips = ['8.8.8.8', '1.1.1.1', 'github.com']; // up to 100 per request
const res = await fetch(`https://fast.ipapi.gr/api/lookup/batch?key=${process.env.IPAPI_KEY}`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ ips })
});
const body = await res.json();
// 400: malformed batch. 429: fewer requests left today than the batch needs (nothing is counted).
if (!res.ok) throw new Error(`${res.status}: ${body.details || body.message}`);

console.log(`${body.charged} requests counted for ${body.count} items`);
for (const item of body.results) { // same order as ips
  if (item.status === 'error') console.warn(item.query, item.code, item.message);
  else console.log(item.query, item.ip, item.country_code, item.asn);
}

05

Handle errors and retries

Branch on the HTTP status, not the message text. Don't retry 4xx errors; for a 429 wait until resetTime; retry 5xx and 504 timeouts with backoff.

JavaScript

async function lookup(ip, { retries = 2 } = {}) {
  const url = `https://fast.ipapi.gr/api/lookup/${encodeURIComponent(ip)}?key=${process.env.IPAPI_KEY}`;
  for (let attempt = 0; ; attempt++) {
    const res = await fetch(url, { signal: AbortSignal.timeout(5000) });
    const body = await res.json().catch(() => ({}));
    if (res.ok) return body;
    if (res.status === 429) throw new Error(`Daily limit reached, resets at ${body.resetTime}`);
    if (res.status < 500) throw new Error(`${res.status}: ${body.message}`); // 400, 401, 403
    if (attempt >= retries) throw new Error(`Lookup failed after ${attempt + 1} attempts: ${res.status}`);
    await new Promise((r) => setTimeout(r, 500 * 2 ** attempt)); // 0.5 s, 1 s, 2 s ...
  }
}

Python

import os
import time
import requests

def lookup(ip, retries=2):
    url = f"https://fast.ipapi.gr/api/lookup/{ip}"
    for attempt in range(retries + 1):
        r = requests.get(url, params={"key": os.environ["IPAPI_KEY"]}, timeout=5)
        try:
            body = r.json()
        except ValueError:
            body = {}
        if r.ok:
            return body
        if r.status_code == 429:
            raise RuntimeError(f"Daily limit reached, resets at {body.get('resetTime')}")
        if r.status_code < 500:  # 400, 401, 403: retrying won't help
            raise ValueError(f"{r.status_code}: {body.get('message')}")
        if attempt < retries:
            time.sleep(0.5 * 2 ** attempt)  # 0.5 s, 1 s, 2 s ...
    raise RuntimeError(f"Lookup failed after {retries + 1} attempts: {r.status_code}")

06

Cache results on your side

The same IP usually shows up many times. A small cache with a time-to-live saves requests and latency. Keep the TTL modest if you rely on the bot flags.

JavaScript

const cache = new Map();
const TTL_MS = 60 * 60 * 1000; // 1 hour

async function cachedLookup(ip) {
  const hit = cache.get(ip);
  if (hit && hit.expires > Date.now()) return hit.data;

  const res = await fetch(`https://fast.ipapi.gr/api/lookup/${encodeURIComponent(ip)}?key=${process.env.IPAPI_KEY}`);
  if (!res.ok) throw new Error(`Lookup failed: ${res.status}`);
  const data = await res.json();

  if (cache.size > 10000) cache.delete(cache.keys().next().value); // drop the oldest entry
  cache.set(ip, { data, expires: Date.now() + TTL_MS });
  return data;
}

Python

import os
import time
import requests

TTL = 3600  # seconds
_cache = {}

def cached_lookup(ip):
    hit = _cache.get(ip)
    if hit and hit[1] > time.time():
        return hit[0]
    r = requests.get(
        f"https://fast.ipapi.gr/api/lookup/{ip}",
        params={"key": os.environ["IPAPI_KEY"]},
        timeout=5,
    )
    r.raise_for_status()
    data = r.json()
    _cache[ip] = (data, time.time() + TTL)
    return data

Next: put it to work.

See complete recipes for blocking AI crawlers, verifying search bots and tagging scanners. Free keys include 1,000 requests a day.

Get a free API key