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.