Developer Documentation
All endpoints, response fields, error codes, rate limits — plus code samples in 5 languages.
Quickstart
From account to first lookup in 60 seconds.
- 1Sign up free — no credit card, verification email arrives instantly.
- 2After login, create an API key in the portal (1 click).
- 3Put the key into the X-API-Key header and fire your first lookup.
curl -H "X-API-Key: $KEY" https://api.geoips.info/v1/lookup/8.8.8.8Tip
Store the key as an environment variable (export KEY=gi_...) — never hardcode it in your repo!
Authentication
Every lookup endpoint requires an API key in the X-API-Key header. The anonymous /v1/public/lookup/:ip endpoint is the only exception.
Key format
API keys follow the format gi_<8-hex>_<48-base62>. Example: gi_f3908360_TQWxoyfbY0nCVeCP83mV1oMjXwmyC51t. The first block (gi_<8-hex>) is the public prefix (we use it internally for logs without storing the secret part).
Key rotation
Create a new key, deploy it, revoke the old one — all in the portal. Revoked keys are blocked immediately (no grace period).
Security tips
- Never use in browser JS or client apps — the key is then publicly readable.
- For frontend use cases: proxy API calls through your own backend, which holds the key.
- One key per environment (dev/staging/prod) — on leak only one is affected.
Rate limits & quotas
Every plan has two limits: per minute (burst protection) and per day (volume protection). Both are counted separately.
Per-minute limit
Sliding window: counts the last 60 seconds. Free 10/min, Starter 600/min, Business 3,000/min, Enterprise 10,000/min.
Per-day limit
Fixed window with configurable UTC reset (default 00:00 UTC). Free 5,000/day, Starter 100,000/day, Business 1M/day, Enterprise unlimited.
Batch counts N IPs
A POST /v1/lookup/batch with 50 IPs consumes 50 of your daily quota (but only 1 HTTP request for the minute limit check). Plan cap for IPs per batch: Free 10, Starter 50, Business 100, Enterprise 100.
Response headers
On 429 we set Retry-After (seconds until reset) and X-RateLimit-Remaining. Build your retry loop around those — not around a fixed sleep.
Whois endpoints count depending on settings mode: either against the daily limit, or against a dedicated Whois pot (whois_limit_per_day per plan).
/v1/lookup/{ip}X-API-Key requiredSingle lookup
Fetch geo data for a specific IPv4 or IPv6. Optionally this call triggers a background Whois pre-cache so that a subsequent /whois call answers instantly.
Parameters
| ip | IP address (IPv4 dotted or IPv6 standard notation). Example: 8.8.8.8 or 2606:4700:4700::1111. |
Code samples
curl -H "X-API-Key: $KEY" https://api.geoips.info/v1/lookup/8.8.8.8Response (200 OK)
{
"ip": "8.8.8.8",
"ip_version": 4,
"country_code": "US",
"country_name": "United States of America",
"flag": "🇺🇸",
"flag_url": "https://geoips.info/flags/us.svg",
"region": "California",
"city": "Mountain View",
"latitude": 37.40599,
"longitude": -122.07852,
"zip": "94043",
"timezone": "-08:00",
"asn": 15169,
"as": "Google LLC",
"cidr": "8.8.8.0/24"
}/v1/lookup/selfX-API-Key requiredYour own IP
Convenience variant: takes the client IP of the calling host. Useful for cron jobs that want to log their egress IP.
Code samples
curl -H "X-API-Key: $KEY" https://api.geoips.info/v1/lookup/selfResponse (200 OK)
{
"ip": "8.8.8.8",
"ip_version": 4,
"country_code": "US",
"country_name": "United States of America",
"flag": "🇺🇸",
"flag_url": "https://geoips.info/flags/us.svg",
"region": "California",
"city": "Mountain View",
"latitude": 37.40599,
"longitude": -122.07852,
"zip": "94043",
"timezone": "-08:00",
"asn": 15169,
"as": "Google LLC",
"cidr": "8.8.8.0/24"
}/v1/lookup/batchX-API-Key requiredBatch lookup
Up to 100 IPs in one request. The response delivers results[] with the same schema as the single endpoint. Each IP consumes 1 of the daily quota.
Request body
JSON with ips array. Min 1, max 100 entries. Each is a valid IPv4 or IPv6.
{ "ips": ["1.1.1.1", "2606:4700:4700::1111"] }Code samples
curl -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"ips":["1.1.1.1","2606:4700:4700::1111"]}' \
https://api.geoips.info/v1/lookup/batchResponse (200 OK)
{
"results": [
{ "ip": "1.1.1.1", "country_code": "US", "flag": "🇺🇸", "flag_url": "https://geoips.info/flags/us.svg", "asn": 13335, "as": "Cloudflare, Inc.", "...": "..." },
{ "ip": "2606:4700:4700::1111", "ip_version": 6, "country_code": "US", "flag": "🇺🇸", "...": "..." }
]
}Request body cap: 64 KB. On exceed: HTTP 413 with error_code: body_too_large.
/v1/lookup/{ip}/whoisX-API-Key requiredWhois / RDAP
CIDR block, netname, organisation and abuse contact directly from the 5 RIRs (RIPE/ARIN/APNIC/LACNIC/AFRINIC). Cached for 30 days — first lookup of a new IP triggers an RDAP sync (~200 ms), follow-up queries are sub-millisecond.
Code samples
curl -H "X-API-Key: $KEY" https://api.geoips.info/v1/lookup/8.8.8.8/whoisResponse (200 OK)
{
"ip": "8.8.8.8",
"cached": true,
"whois": {
"cidr": "8.8.8.0/24",
"netname": "GOOGLE",
"country": "US",
"flag": "🇺🇸",
"flag_url": "https://geoips.info/flags/us.svg",
"org_name": "Google LLC",
"abuse_email": "network-abuse@google.com",
"source": "arin",
"fetched_at": "2026-05-27T10:00:00Z"
}
}Plan gating: depending on admin settings either included in every plan, or from Starter upwards, or with own daily pot per plan.
202 Accepted (cache miss + async)
When the cache misses AND the background worker has not yet synced the entry, we respond with HTTP 202, a Retry-After header (~5 s) and whois: null. Simply retry after Retry-After.
/v1/healthanonymous (no key)Health check
Returns { status: "ok", version: "..." }. Anonymous endpoint, ideal for uptime monitoring (e.g. UptimeRobot, Pingdom).
Code samples
curl https://api.geoips.info/v1/healthResponse (200 OK)
{ "status": "ok", "version": "1.2.3" }/v1/limitsX-API-Key requiredLimits & quota
Returns the effective plan limits for the API key (per-minute rate, IPs/day, max IPs per batch, whois) plus the currently remaining quota. Ideal to make clients or monitoring pace themselves instead of hitting 429. 0 = unlimited.
Code samples
curl -H "X-API-Key: $KEY" https://api.geoips.info/v1/limitsResponse (200 OK)
{
"plan": { "code": "hobby", "name": "Hobby" },
"rate_limit_per_min": 300,
"rate_limit_per_day": 1000000,
"max_ips_per_batch": 100,
"whois": { "enabled": true, "gating_mode": "all", "limit_per_day": 2000 },
"remaining": { "per_min": 287, "per_day": 999812 }
}/v1/public/lookup/{ip}anonymous (no key)Anonymous (public)
The endpoint behind the /check page. Combines geo+whois in one response, requires NO key. Strict IP rate limit (10/h per requester IP) — for proper volume applications please use a key.
Code samples
curl https://api.geoips.info/v1/public/lookup/8.8.8.8Response (200 OK)
{
"ip": "8.8.8.8",
"geo": { "country_code": "US", "flag": "🇺🇸", "flag_url": "https://geoips.info/flags/us.svg", "city": "Mountain View", "...": "..." },
"whois": { "cidr": "8.8.8.0/24", "org_name": "Google LLC", "abuse_email": "network-abuse@google.com", "...": "..." }
}/v1/public/lookup/self has a separate, looser limit (60/h per IP) so that landing page reloads do not drain the main pot.
Response fields in detail
All fields a lookup CAN return. Nullable means: the field is missing entirely from the JSON if no data is available (all fields are omitempty).
GeoIP fields
| Field | Type | Nullable | Description |
|---|---|---|---|
| ip | string | always | Die abgefragte IP, normalisiert. |
| ip_version | int | always | 4 oder 6. |
| country_code | string | nullable | ISO 3166-1 alpha-2 uppercase ("US", "DE"). |
| country_name | string | nullable | Vollstaendiger Englischer Laendername. |
| flag | string | nullable | Country-Flag-Emoji (z.B. 🇩🇪) — abgeleitet aus country_code. |
| flag_url | string | nullable | Absolute URL zur SVG-Flagge (z.B. https://geoips.info/flags/de.svg). |
| region | string | nullable | Region/Bundesland (z.B. "California"). |
| city | string | nullable | Stadt. |
| latitude | float | nullable | WGS84. |
| longitude | float | nullable | WGS84. |
| zip | string | nullable | Postleitzahl (Format land-abhaengig). |
| timezone | string | nullable | UTC-Offset oder TZ-Name (z.B. "-08:00"). |
| asn | int | nullable | Autonomous System Number. |
| as | string | nullable | AS-Name (z.B. "Google LLC"). |
| cidr | string | nullable | CIDR-Block der IP (z.B. "8.8.8.0/24"). |
Whois fields
| Field | Type | Nullable | Description |
|---|---|---|---|
| cidr | string | always | RDAP-Inetnum als CIDR. |
| netname | string | nullable | Netzwerk-Bezeichnung beim RIR. |
| country | string | nullable | RIR-registriertes Land (kann von GeoIP-country_code abweichen). |
| flag | string | nullable | Country-Flag-Emoji aus country. |
| flag_url | string | nullable | Absolute URL zur SVG-Flagge. |
| org_name | string | nullable | Organisations-Name (Betreiber). |
| abuse_email | string | nullable | Abuse-Kontakt-Email. |
| asn | int | nullable | ASN — aus dem RDAP-Response wo verfuegbar. |
| as_name | string | nullable | AS-Name. |
| source | string | always | Welches RIR — "ripe"/"arin"/"apnic"/"lacnic"/"afrinic". |
| fetched_at | datetime | always | ISO-8601-Zeitstempel des letzten RDAP-Fetch. |
Country flag assets
Every response with country_code additionally contains flag (emoji) and flag_url (hosted SVG). Free to use in your UI.
URL pattern
Lowercase ISO 3166-1 alpha-2. 271 countries available. Cache header 1 day.
https://geoips.info/flags/<iso-lowercase>.svg
# Beispiele:
https://geoips.info/flags/de.svg
https://geoips.info/flags/us.svg
https://geoips.info/flags/jp.svgCORS
SVGs are served with Access-Control-Allow-Origin: * — direct cross-origin embed works.
Live-Preview
Error codes
All errors return a JSON body with error (human readable), error_code (machine readable), and depending on the code further fields. The code value is stable — you can write if/else against it.
| HTTP | error_code | Meaning | How to react? |
|---|---|---|---|
| 400 | invalid_ip | IP-Adresse konnte nicht geparst werden. | Vor dem Call lokal validieren (`net.ParseIP`, `ipaddress` etc.). |
| 400 | private_ip | IP ist in einer privaten/reservierten Range (10.x, 192.168.x, fc00::/7, ...). | Diese IPs sind nicht route-bar — vorher filtern. |
| 400 | invalid_body | Batch-Request-Body ist kein gueltiges JSON oder schema-falsch. | Schema pruefen: `{ "ips": ["...", "..."] }`, 1-100 Eintraege. |
| 400 | batch_too_large | Batch enthaelt mehr IPs als dein Plan erlaubt. | In kleinere Batches splitten oder Plan upgraden. |
| 401 | missing_api_key | Header `X-API-Key` fehlt. | Header setzen. Auch case-insensitive zulaessig. |
| 401 | invalid_api_key | Key existiert nicht oder ist widerrufen. | Im Portal pruefen ob Key noch aktiv ist. |
| 403 | whois_plan_required | Dein Plan beinhaltet keinen Whois-Zugriff (gating_mode=starter_plus + Free-Plan). | Upgrade auf Starter oder hoeher. |
| 413 | body_too_large | Request-Body groesser als 64 KB. | Batch splitten (100 IPv6-Adressen passen problemlos in 64 KB). |
| 429 | rate_limit_per_minute | Per-Minute-Quote erschoepft. | `Retry-After` Header respektieren, dann erneut. |
| 429 | rate_limit_per_day | Tageskontingent erschoepft. | `Retry-After` zeigt Sekunden bis UTC-Reset, oder Plan upgraden. |
| 429 | whois_quota_exhausted | Whois-Tagestopf erschoepft (nur bei gating_mode=per_plan_limit). | Bis UTC-Reset warten oder Plan-Whois-Limit erhoehen. |
| 429 | rate_limit_public | Anon-Endpoint /v1/public/lookup: pro Requester-IP > 10/h. | Mit API-Key auf /v1/lookup wechseln (10 000x hoeheres Limit). |
| 202 | pending | Whois noch nicht im Cache, async-Fetch laeuft. | Nach `Retry-After` Sekunden (~5 s) erneut anfragen. |
| 503 | maintenance_mode | Plattform im Wartungsmodus. | `Retry-After` respektieren, der Header sagt wann wieder verfuegbar. |
| 503 | whois_disabled | Whois-Feature global deaktiviert (Admin-Setting). | Nur Geo-Endpoints verwenden bis das Feature wieder aktiv ist. |
| 500 | internal_error | Unerwarteter Server-Fehler. | Mit exponential backoff retry; falls persistent: Support kontaktieren mit Request-ID. |
Best practices
Recommendations from production. Follow them = little trouble; ignore = probably an incident at some point.
Retry with exponential backoff
On 429 (rate limit), 502/503 or network errors: don't hammer, retry with growing delay. Rule of thumb: 1s, 2s, 4s, 8s — then abort. On 429 ALWAYS respect the Retry-After header (seconds until reset); if header is absent the backoff default applies.
No retries on 4xx (except 429) — these are permanent (invalid_ip, missing_api_key etc.) and retrying just burns quota.
Caching your lookups
GeoIP data changes rarely (an IP switches country every few years, city almost never). Sensible local cache: TTL 24h, LRU eviction. Saves quota drastically. Whois data we cache 30 days internally — no need to cache it again.
Cache key: the IP itself. For IPv6 normalize first (lowercase, no leading zeros) otherwise cache miss on every spelling variant.
Error handling pattern
ALWAYS check the HTTP status first, then the error_code. Rule of thumb: 4xx = your bug (invalid input); 429 = rate limit, retry with backoff; 5xx = our bug, retry with backoff; 200 = OK, continue in code.
Log 5xx + 429 with the value of Retry-After + error_code — that helps us enormously when you contact support.
Batch instead of N single lookups
If you have more than 5 IPs to check: use POST /v1/lookup/batch instead of N parallel GETs. Saves roundtrips, consumes the daily quota the same, is significantly faster (one request instead of N).
For >100 IPs: split into chunks (cap per batch = 100). Send sequentially or with small concurrency limit (3-5 parallel batches is enough) — otherwise you'll hit the per-minute limit.
Handling API keys
Never hardcode. Never in frontend JS. Never commit to Git. Always via ENV variable, vault/secrets manager or CI/CD secret. On suspicion of leak: immediately revoke in the portal + create a new key.
Performance & latency
What response times to expect and how we achieve those numbers.
Typical response times
- P50 below 30 ms from EU datacenters for single lookups.
- P99 below 100 ms — peaks are usually GC pauses or Whois async pre-cache, not actual wait time.
- Batch with 100 IPs: below 100 ms P50 (internally parallelized, a single DB roundtrip).
- First Whois lookup of a new IP: ~200-400 ms (RDAP roundtrip to the RIR). From the 2nd call onwards: sub-millisecond (30-day cache).
Infrastructure
Servers in Germany + Austria (EU hosting for GDPR). In-memory binary range index instead of classical DB queries — so no cold start, no connection pool pressure.
Response cache headers
Geo lookups: Cache-Control: private, no-cache (result may change when we update GeoLite2). Whois: Cache-Control: max-age=86400 (30-day backend cache, 1-day client cache OK). /flags/*.svg: Cache-Control: public, max-age=86400, immutable.
Integration recipes
Concrete copy-paste ready. Three common setups in practice.
Cloudflare Worker — IP enrichment at the edge
Every request gets the country of the visitor IP injected in the Worker. Saves doing it in the origin app code.
// Cloudflare Worker — IP-Enrichment am Edge
export default {
async fetch(req, env) {
const ip = req.headers.get("cf-connecting-ip");
const r = await fetch(`https://api.geoips.info/v1/lookup/${ip}`, {
headers: { "X-API-Key": env.GEOIPS_KEY },
});
const geo = await r.json();
const next = await fetch(req);
const res = new Response(next.body, next);
res.headers.set("x-geo-country", geo.country_code || "");
res.headers.set("x-geo-city", geo.city || "");
return res;
},
};Nginx — country in access log
Via Lua module a single lookup per request, then in the custom log format. Async fetch via lua-resty-http.
# nginx.conf — Country in jedem Access-Log
log_format with_geo '$remote_addr [$geo_country] "$request" $status';
location / {
access_by_lua_block {
local http = require "resty.http"
local h = http.new()
local res = h:request_uri("https://api.geoips.info/v1/lookup/" .. ngx.var.remote_addr, {
headers = { ["X-API-Key"] = os.getenv("GEOIPS_KEY") },
})
if res and res.status == 200 then
local cjson = require "cjson.safe"
local data = cjson.decode(res.body)
ngx.var.geo_country = data and data.country_code or "??"
end
}
access_log /var/log/nginx/access.log with_geo;
}WordPress — "Where am I?" shortcode
Plugin snippet that registers a [where_am_i] shortcode. Returns country + flag of the current visitor. Cached per session.
<?php
// functions.php — [where_am_i] Shortcode
add_shortcode('where_am_i', function () {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'];
$cache_key = 'geo_' . md5($ip);
$data = get_transient($cache_key);
if (!$data) {
$resp = wp_remote_get("https://api.geoips.info/v1/lookup/$ip", [
'headers' => ['X-API-Key' => getenv('GEOIPS_KEY')],
'timeout' => 3,
]);
if (is_wp_error($resp)) return '';
$data = json_decode(wp_remote_retrieve_body($resp), true);
set_transient($cache_key, $data, 6 * HOUR_IN_SECONDS);
}
if (empty($data['country_code'])) return '';
return sprintf(
'<img src="https://geoips.info/flags/%s.svg" width="20" alt=""> %s, %s',
strtolower($data['country_code']),
esc_html($data['city'] ?? ''),
esc_html($data['country_name'] ?? '')
);
});AWS Lambda / Vercel — edge function
Serverless pattern: API key from env, fetch in the function, response forwarded. Works in any cloud because nothing AWS-specific.
// AWS Lambda (Node 24) — eingebettete Geo-Enrichment-Function
export const handler = async (event) => {
const ip = event.headers["x-forwarded-for"]?.split(",")[0] ?? event.requestContext.identity.sourceIp;
const r = await fetch(`https://api.geoips.info/v1/lookup/${ip}`, {
headers: { "X-API-Key": process.env.GEOIPS_KEY },
});
const geo = await r.json();
return {
statusCode: 200,
headers: { "content-type": "application/json" },
body: JSON.stringify({ source_country: geo.country_code, ...geo }),
};
};Migration from competitor APIs
Coming from MaxMind, ipinfo.io or ipstack? Here's how the fields map.
| Our field | MaxMind GeoLite2 / GeoIP2 | ipinfo.io | ipstack |
|---|---|---|---|
| country_code | country.iso_code | country | country_code |
| country_name | country.names.en | country_name | country_name |
| flag | — | country_flag.emoji | — |
| flag_url | — | country_flag_url | location.country_flag |
| region | subdivisions[0].names.en | region | region_name |
| city | city.names.en | city | city |
| zip | postal.code | postal | zip |
| latitude | location.latitude | loc (split) | latitude |
| longitude | location.longitude | loc (split) | longitude |
| timezone | location.time_zone | timezone | time_zone.id |
| asn | traits.autonomous_system_number | org (parsed) | connection.asn |
| as | traits.autonomous_system_organization | org (parsed) | connection.isp |
| cidr | traits.network | — | — |
| whois.org_name | — | abuse.name (premium) | — |
| whois.abuse_email | — | abuse.email (premium) | — |
Important differences
- We deliver Whois + flag emojis/SVGs at NO extra cost — with the others both cost extra or aren't available at all.
- Our country_code is ALWAYS ISO 3166-1 alpha-2 uppercase. MaxMind partially uses proprietary codes, we don't.
- Batch endpoint takes 100 IPs in one request. MaxMind doesn't know this; ipinfo caps at 1000 (but only in more expensive plans).
- Our free plan: 5,000 lookups/day. MaxMind/ipinfo/ipstack free: usually 1,000/day or less.
Glossary
Briefly explained, what we use where.
- RDAP
- Registration Data Access Protocol — successor to WHOIS (port 43). JSON-based, REST API. The 5 RIRs (RIPE/ARIN/...) all implement it. We cache the responses for 30 days.
- RIR (Regional Internet Registry)
- The 5 organisations that allocate IP blocks: RIPE (Europe+Middle East), ARIN (North America), APNIC (Asia-Pacific), LACNIC (Latin America), AFRINIC (Africa).
- CIDR (Classless Inter-Domain Routing)
- Notation for IP blocks: 8.8.8.0/24 = 256 addresses 8.8.8.0 through 8.8.8.255. The /24 says: 24 bits are the network, the rest is host space.
- ASN (Autonomous System Number)
- Unique number for a network operator (ISP, cloud provider). 32-bit integer, in practice written with AS prefix (AS15169 = Google). Multiple CIDR blocks often share one ASN.
- MaxMind GeoLite2
- Free tier of the MaxMind database for IP-to-geo mapping. Updates weekly, accuracy country ~99.8%, city ~75% within 50 km.
- Private IP
- Reserved blocks that are not routable: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 (IPv4), fc00::/7 (IPv6). Loopback 127.0.0.0/8 and ::1. We reject lookups on these ranges with 400/private_ip.
- Idempotency
- Multiple calls have the same effect as one. All our GET endpoints are idempotent. POST /v1/lookup/batch is too (it doesn't modify any server state, just read-only).
- Sliding window (rate limit)
- Counts requests in the last N seconds — glides continuously with time. Unlike fixed window (e.g. "in this hour") there are no burst spikes at hour transitions.
- Fixed window (daily limit)
- Counter resets at a fixed point in time (default 00:00 UTC). Predictable for billing. Drawback: a few seconds before reset you can use full limit, few seconds after reset again — short burst possible.
- Anycast
- One IP is simultaneously announced from multiple locations worldwide (BGP). The visitor always lands on the geographically nearest one. Well-known anycast IPs: 8.8.8.8 (Google DNS), 1.1.1.1 (Cloudflare). GeoIP responses for anycast IPs are just a "most probable" assignment.
Status, SLA & support
How you reach us when something doesn't work.
Uptime monitoring
GET /v1/health returns {status:"ok", version:"..."} without auth. Plug the endpoint into UptimeRobot / Pingdom / your own monitoring (frequency: every 60s is enough). On status != ok or HTTP error: trigger alert.
SLA
Free/Starter/Business have no formal SLA — we deliver best-effort 99.5%+ uptime (see health endpoint). Enterprise plan can be individually agreed contractually (e.g. 99.95% with flat credit on shortfall).
Contact
Logged-in users: via the support form in the portal (gets stored internally as a ticket, no lost email). Not logged in: support@geoips.info. Response time normally under 4 hours (business day).
Incidents
On larger outages we activate maintenance mode (all API calls return 503 with error_code maintenance_mode + Retry-After). Status page with historical uptime is on the roadmap for 2026.
Developer FAQ
How many IPs can a POST /v1/lookup/batch contain?▾
Max 100, plan dependent. The binding hard cap is 100, your plan cap may be lower (Free: 10, Starter: 50, Business+: 100). On exceed: HTTP 400 with batch_too_large.
What does a Whois lookup cost?▾
Depending on admin setting (whois.gating_mode): either a Whois consumes 1 of the daily quota, or has a dedicated pot (whois_limit_per_day per plan), or is plan-gated (e.g. Starter+ only).
My server sits behind a reverse proxy. Does /v1/lookup/self still work?▾
Yes, as long as your proxy sets X-Forwarded-For correctly. We read the last (= trustworthy) entry of the chain, not the first — defense in depth against spoofing.
Can I query 1000 IPs at once?▾
Not in one batch (cap 100). But: simply send 10 batches of 100 in sequence — the sliding minute limiter allows that easily as long as you stay in the daily budget.
How do I verify a response actually comes from you?▾
TLS certificate via Cloudflare. If you have stricter requirements (e.g. for compliance): contact us, we can implement response body signatures via HMAC with your key.
Are private IPs (10.x, 192.168.x) looked up?▾
No — these return HTTP 400 with private_ip. Filter in code (ready-made IPv4/IPv6 private range checks exist in every language) — also prevents private IPs from accidentally ending up in logs.
What about Whois for domain names (not IPs)?▾
Currently IP whois only (RDAP). Domain whois is a different protocol (registrar database). On the roadmap for 2026.
Do you have an official SDK?▾
No, intentionally not — the API is small enough that fetch/requests/http in any language is sufficient. The code examples above are copy-paste ready.