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.

  1. 1Sign up free — no credit card, verification email arrives instantly.
  2. 2After login, create an API key in the portal (1 click).
  3. 3Put the key into the X-API-Key header and fire your first lookup.
user@geoips:~$cat quickstart.sh
curl -H "X-API-Key: $KEY" https://api.geoips.info/v1/lookup/8.8.8.8

Tip

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).

GET/v1/lookup/{ip}X-API-Key required

Single 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

ipIP address (IPv4 dotted or IPv6 standard notation). Example: 8.8.8.8 or 2606:4700:4700::1111.

Code samples

user@geoips:~$cat example.sh
curl -H "X-API-Key: $KEY" https://api.geoips.info/v1/lookup/8.8.8.8

Response (200 OK)

user@geoips:~$cat response.json
{
  "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"
}
GET/v1/lookup/selfX-API-Key required

Your 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

user@geoips:~$cat example.sh
curl -H "X-API-Key: $KEY" https://api.geoips.info/v1/lookup/self

Response (200 OK)

user@geoips:~$cat response.json
{
  "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"
}
POST/v1/lookup/batchX-API-Key required

Batch 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.

user@geoips:~$cat request.json
{ "ips": ["1.1.1.1", "2606:4700:4700::1111"] }

Code samples

user@geoips:~$cat example.sh
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/batch

Response (200 OK)

user@geoips:~$cat response.json
{
  "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.

GET/v1/lookup/{ip}/whoisX-API-Key required

Whois / 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

user@geoips:~$cat example.sh
curl -H "X-API-Key: $KEY" https://api.geoips.info/v1/lookup/8.8.8.8/whois

Response (200 OK)

user@geoips:~$cat response.json
{
  "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.

GET/v1/healthanonymous (no key)

Health check

Returns { status: "ok", version: "..." }. Anonymous endpoint, ideal for uptime monitoring (e.g. UptimeRobot, Pingdom).

Code samples

user@geoips:~$cat example.sh
curl https://api.geoips.info/v1/health

Response (200 OK)

user@geoips:~$cat response.json
{ "status": "ok", "version": "1.2.3" }
GET/v1/limitsX-API-Key required

Limits & 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

user@geoips:~$cat example.sh
curl -H "X-API-Key: $KEY" https://api.geoips.info/v1/limits

Response (200 OK)

user@geoips:~$cat response.json
{
  "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 }
}
GET/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

user@geoips:~$cat example.sh
curl https://api.geoips.info/v1/public/lookup/8.8.8.8

Response (200 OK)

user@geoips:~$cat response.json
{
  "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

FieldTypeNullableDescription
ipstringalwaysDie abgefragte IP, normalisiert.
ip_versionintalways4 oder 6.
country_codestringnullableISO 3166-1 alpha-2 uppercase ("US", "DE").
country_namestringnullableVollstaendiger Englischer Laendername.
flagstringnullableCountry-Flag-Emoji (z.B. 🇩🇪) — abgeleitet aus country_code.
flag_urlstringnullableAbsolute URL zur SVG-Flagge (z.B. https://geoips.info/flags/de.svg).
regionstringnullableRegion/Bundesland (z.B. "California").
citystringnullableStadt.
latitudefloatnullableWGS84.
longitudefloatnullableWGS84.
zipstringnullablePostleitzahl (Format land-abhaengig).
timezonestringnullableUTC-Offset oder TZ-Name (z.B. "-08:00").
asnintnullableAutonomous System Number.
asstringnullableAS-Name (z.B. "Google LLC").
cidrstringnullableCIDR-Block der IP (z.B. "8.8.8.0/24").

Whois fields

FieldTypeNullableDescription
cidrstringalwaysRDAP-Inetnum als CIDR.
netnamestringnullableNetzwerk-Bezeichnung beim RIR.
countrystringnullableRIR-registriertes Land (kann von GeoIP-country_code abweichen).
flagstringnullableCountry-Flag-Emoji aus country.
flag_urlstringnullableAbsolute URL zur SVG-Flagge.
org_namestringnullableOrganisations-Name (Betreiber).
abuse_emailstringnullableAbuse-Kontakt-Email.
asnintnullableASN — aus dem RDAP-Response wo verfuegbar.
as_namestringnullableAS-Name.
sourcestringalwaysWelches RIR — "ripe"/"arin"/"apnic"/"lacnic"/"afrinic".
fetched_atdatetimealwaysISO-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.

user@geoips:~$cat flag-urls.txt
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.svg

CORS

SVGs are served with Access-Control-Allow-Origin: * — direct cross-origin embed works.

Live-Preview

deusjpfrbrcngbchauinzasa

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.

HTTPerror_codeMeaningHow to react?
400invalid_ipIP-Adresse konnte nicht geparst werden.Vor dem Call lokal validieren (`net.ParseIP`, `ipaddress` etc.).
400private_ipIP ist in einer privaten/reservierten Range (10.x, 192.168.x, fc00::/7, ...).Diese IPs sind nicht route-bar — vorher filtern.
400invalid_bodyBatch-Request-Body ist kein gueltiges JSON oder schema-falsch.Schema pruefen: `{ "ips": ["...", "..."] }`, 1-100 Eintraege.
400batch_too_largeBatch enthaelt mehr IPs als dein Plan erlaubt.In kleinere Batches splitten oder Plan upgraden.
401missing_api_keyHeader `X-API-Key` fehlt.Header setzen. Auch case-insensitive zulaessig.
401invalid_api_keyKey existiert nicht oder ist widerrufen.Im Portal pruefen ob Key noch aktiv ist.
403whois_plan_requiredDein Plan beinhaltet keinen Whois-Zugriff (gating_mode=starter_plus + Free-Plan).Upgrade auf Starter oder hoeher.
413body_too_largeRequest-Body groesser als 64 KB.Batch splitten (100 IPv6-Adressen passen problemlos in 64 KB).
429rate_limit_per_minutePer-Minute-Quote erschoepft.`Retry-After` Header respektieren, dann erneut.
429rate_limit_per_dayTageskontingent erschoepft.`Retry-After` zeigt Sekunden bis UTC-Reset, oder Plan upgraden.
429whois_quota_exhaustedWhois-Tagestopf erschoepft (nur bei gating_mode=per_plan_limit).Bis UTC-Reset warten oder Plan-Whois-Limit erhoehen.
429rate_limit_publicAnon-Endpoint /v1/public/lookup: pro Requester-IP > 10/h.Mit API-Key auf /v1/lookup wechseln (10 000x hoeheres Limit).
202pendingWhois noch nicht im Cache, async-Fetch laeuft.Nach `Retry-After` Sekunden (~5 s) erneut anfragen.
503maintenance_modePlattform im Wartungsmodus.`Retry-After` respektieren, der Header sagt wann wieder verfuegbar.
503whois_disabledWhois-Feature global deaktiviert (Admin-Setting).Nur Geo-Endpoints verwenden bis das Feature wieder aktiv ist.
500internal_errorUnerwarteter 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.

user@geoips:~$cat worker.js
// 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.

user@geoips:~$cat nginx-geo.lua
# 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.

user@geoips:~$cat wp-geo-shortcode.php
<?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.

user@geoips:~$cat lambda-handler.js
// 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 fieldMaxMind GeoLite2 / GeoIP2ipinfo.ioipstack
country_codecountry.iso_codecountrycountry_code
country_namecountry.names.encountry_namecountry_name
flag—country_flag.emoji—
flag_url—country_flag_urllocation.country_flag
regionsubdivisions[0].names.enregionregion_name
citycity.names.encitycity
zippostal.codepostalzip
latitudelocation.latitudeloc (split)latitude
longitudelocation.longitudeloc (split)longitude
timezonelocation.time_zonetimezonetime_zone.id
asntraits.autonomous_system_numberorg (parsed)connection.asn
astraits.autonomous_system_organizationorg (parsed)connection.isp
cidrtraits.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.