API Documentation
IP-Atlas gives you IP geolocation, ASN data, and datacenter detection through one REST endpoint. Every field is included on every plan — you pay for throughput, not fields.
Base URL: https://api.ip-atlas.io
Response format: JSON (Content-Type: application/json)
Source IP handling: CF-Connecting-IP, then X-Real-IP, then the first entry of X-Forwarded-For, then the TCP source address.
Authentication
Pass your API key in the X-API-Key header:
# Every paid endpoint; all /json/* also accept this. curl https://api.ip-atlas.io/json/8.8.8.8 \ -H "X-API-Key: ipa_live_YOUR_API_KEY_HERE"
The /json/* endpoints also accept unauthenticated requests (free anonymous tier, 60 req/min per IP). Endpoints under /v1/* always require a key.
Keys must be sent via the X-API-Key header. The legacy ?key=… query-string fallback is no longer accepted — query strings leak keys via access logs, browser history, and Referer headers.
Key permissions
Every API key carries three independent scopes. Lookups and MMDB are read-only (none or read); only Account has a write level (full). You choose them when you create the key on your dashboard keys page — pick a level per section, or use the “Agent key (read-only)” preset (sets every section to read), which is the recommended credential to hand an automated agent.
| Section | none | read | full |
|---|---|---|---|
| Lookups | blocked | all IP lookups — single (/json, /v1/json) and batch (/v1/batch) | — |
| MMDB | blocked | download (/v1/mmdb/*) | — (no write exists) |
| Account | blocked | view status/usage (GET /v1/account) | read + change billing settings (POST /v1/account/settings) + rotate the key |
A request that exceeds the key’s scope returns 403:
{ "error": "insufficient_scope", "required": "lookups:full" }
Keys created before scopes existed default to full access on every section, so nothing breaks. A read-only agent key can pull the MMDB and verify the account is active while being unable to spend money or change the account.
No key — at any scope, including account:full — can manage cards, cancel the subscription, or delete the account. Those actions live only behind a logged-in browser session, never the API.
Rate limits
| Plan | Monthly | Daily | Burst |
|---|---|---|---|
| Free | 2,000 | (none) | 60 req/min anonymous |
| Plus ($19/mo) | 150,000 | (none) | — |
| Pro ($79/mo) | 1,000,000 | (none) | — |
| Business ($249/mo) | 10,000,000 | (none) | — |
| Pay-as-you-go | unlimited (billed per use) | (none) | — |
Every successful response includes advisory headers:
| Header | Meaning |
|---|---|
X-RateLimit-Limit-Month | Monthly quota (always present for authenticated requests) |
X-RateLimit-Remaining-Month | Remaining requests this calendar month (always present for authenticated requests) |
X-RateLimit-Limit-Day | Only present on legacy plans that have a daily cap. The current model uses monthly quotas only. |
X-RateLimit-Remaining-Day | Only present on legacy plans that have a daily cap. |
X-Overage-Charged-Cents | USD cents charged on this request (0 if within quota) |
X-Spend-Cap-Reached | true when a 429 is caused by your spend cap |
On 429 Too Many Requests the response also includes Retry-After with the seconds until the window resets.
Overage billing
Every plan (including Free) has Automatic Overage Billing enabled by default. Once you pass your monthly quota, requests keep succeeding and are billed transparently.
Every plan: $0.40 per 1,000 requests beyond the included quota. Auto-billed at month end via Stripe.
Accrued overage is billed on the 1st of the following month via a Stripe Invoice Item. You'll receive warning emails at 80% and 95% of your monthly quota. To limit exposure:
- Turn off Automatic Overage Billing on your dashboard — requests will return
429at quota instead; - Set a spend cap (in USD cents) — once the cap is reached, requests return
429withX-Spend-Cap-Reached: true.
Errors
All errors return JSON of the form {"error": "<message>"}.
| Status | Condition |
|---|---|
| 400 | Malformed request (bad JSON, invalid IP, missing fields) |
| 401 | Missing or revoked API key on endpoints that require one |
| 404 | Unknown Stripe customer on /portal |
| 405 | Method not allowed (e.g. GET on /v1/batch) |
| 410 | Endpoint removed (e.g. legacy /signup — use /checkout) |
| 429 | Quota exceeded, per-IP rate limit hit, or spend cap reached — see Retry-After |
| 500 | Server error — transient, retry with backoff |
| 502 | Upstream (Stripe) failure |
GET/json/{ip}
Look up geolocation, ASN, organisation, datacenter and VPN detection for a specific IPv4 or IPv6 address.
curl https://api.ip-atlas.io/json/8.8.8.8
{
"ip": "8.8.8.8",
"country": "US",
"country_name": "United States",
"registry": "arin",
"asn": 15169,
"org": "GOOGLE",
"is_datacenter": true,
"is_vpn": false
}
GET/json
Look up the caller's own IP. Same response shape as /json/{ip}.
curl https://api.ip-atlas.io/json
POST/v1/batch
Look up up to 100 IPs in a single request. Requires an API key; each IP counts as one request against your quota.
curl -X POST https://api.ip-atlas.io/v1/batch \ -H "X-API-Key: ipa_live_..." \ -H "Content-Type: application/json" \ -d '{"ips":["8.8.8.8","1.1.1.1","not-an-ip"]}'
{
"results": [
{"ip":"8.8.8.8", "country":"US", ...},
{"ip":"1.1.1.1", "country":"AU", ...},
{"ip":"not-an-ip", "error":"invalid IP address"}
]
}
Results are returned in the same order as the input list. Each element is either a full lookup result or a {"ip": "...", "error": "..."} record for per-IP failures. Request-level failures (invalid key, over-quota, spend cap) return a normal 4xx/5xx, not inside the results array.
The entire batch is preflighted against your quota before any lookup runs.
GET/v1/account
Returns plan metadata, live usage, and billing settings for the key presented. Does not count against your quota.
curl https://api.ip-atlas.io/v1/account -H "X-API-Key: ipa_live_..."
{
"plan": "developer",
"plan_display_name": "Plus",
"key_prefix": "ipa_live_abc1",
"email": "<your-email>",
"status": "active",
"created_at": "2026-04-22T18:14:03Z",
"daily_used": 0,
"monthly_used": 14823, "monthly_limit": 150000,
"stripe_customer_id": "cus_...", "has_subscription": true,
"overage_enabled": true,
"overage_rate_cents_per_million": 40000,
"overage_accrued_cents": 0,
"spend_cap_cents": null
}
The plan field uses internal codes for API-stable identification:
free, developer, startup, business, payg.
The plan_display_name field gives the marketing name ("Free", "Plus", "Pro", "Business",
"Pay-as-you-go", "Enterprise"). Build clients against plan — it's stable. Use
plan_display_name for display copy.
POST/v1/account/rotate
Revokes the current key and issues a replacement with the same plan, email, and Stripe linkage. The new plaintext key is returned once.
curl -X POST https://api.ip-atlas.io/v1/account/rotate -H "X-API-Key: ipa_live_..."
{"api_key": "ipa_live_9f8e...", "key_prefix": "ipa_live_9f8e"}
POST/v1/account/settings
Update billing settings for your key. Fields not present are unchanged.
curl -X POST https://api.ip-atlas.io/v1/account/settings \ -H "X-API-Key: ipa_live_..." \ -H "Content-Type: application/json" \ -d '{"overage_enabled": false, "spend_cap_cents": 2500}'
Pass "clear_spend_cap": true to remove an existing cap.
GET/health
Unauthenticated liveness check. No rate limit.
curl https://api.ip-atlas.io/health
{"ok": true, "ips_loaded": 255000, "asns_loaded": 519000} // Counts shown are approximate; the live response reflects the data set at refresh time.
POST/checkout
Creates a Stripe Checkout session. Use plan: "free" for a Setup-mode checkout (card authorised, no charge). Paid plans run in Subscription mode. Most users hit this through the pricing page.
curl -X POST https://api.ip-atlas.io/checkout \ -H "Content-Type: application/json" \ -d '{"plan":"developer","email":"<your-email>"}'
Billing portal
Manage your card, download invoices, or cancel your plan from your dashboard billing page. The portal is session-gated and is not exposed as a public API endpoint — opening it requires a logged-in browser session, not an API key.
Node.js SDK
The official Node.js SDK is available on npm as @trellisdigitalservices/ip-atlas.
npm install @trellisdigitalservices/ip-atlas
import { IPAtlas } from '@trellisdigitalservices/ip-atlas'; const client = new IPAtlas({ apiKey: 'ipa_live_...' }); const result = await client.lookup('8.8.8.8'); console.log(result.country, result.org);
Python SDK
The official Python SDK is on PyPI as ipatlas.
pip install ipatlas
from ipatlas import IPAtlas client = IPAtlas(api_key="ipa_live_...") result = client.lookup("8.8.8.8") print(result["country"], result["org"]) # Batch (up to 100 IPs) batch = client.lookup_batch(["8.8.8.8", "1.1.1.1"])
Without an API key the client uses the auth-less endpoints (rate-limited to 60 req/min per source IP). With a key, your monthly quota applies.
curl
All you need is your API key in the X-API-Key header.
# Single lookup (anonymous) curl https://api.ip-atlas.io/json/8.8.8.8 # Single lookup with auth curl https://api.ip-atlas.io/json/8.8.8.8 \ -H "X-API-Key: ipa_live_..." # Batch with auth curl -X POST https://api.ip-atlas.io/v1/batch \ -H "X-API-Key: ipa_live_..." \ -H "Content-Type: application/json" \ -d '{"ips":["8.8.8.8","1.1.1.1"]}'
JavaScript / fetch
// Single lookup const res = await fetch('https://api.ip-atlas.io/json/8.8.8.8', { headers: { 'X-API-Key': 'ipa_live_...' } }); const data = await res.json(); // Batch lookup const batch = await fetch('https://api.ip-atlas.io/v1/batch', { method: 'POST', headers: { 'X-API-Key': 'ipa_live_...', 'Content-Type': 'application/json' }, body: JSON.stringify({ ips: ['8.8.8.8', '1.1.1.1'] }) }).then(r => r.json());
Python / requests
import requests KEY = "ipa_live_..." HEADERS = {"X-API-Key": KEY} # Single lookup data = requests.get("https://api.ip-atlas.io/json/8.8.8.8", headers=HEADERS).json() # Batch lookup batch = requests.post( "https://api.ip-atlas.io/v1/batch", headers=HEADERS, json={"ips": ["8.8.8.8", "1.1.1.1"]} ).json()
Go
req, _ := http.NewRequest("GET", "https://api.ip-atlas.io/json/8.8.8.8", nil) req.Header.Set("X-API-Key", "ipa_live_...") resp, err := http.DefaultClient.Do(req) defer resp.Body.Close() var result map[string]any json.NewDecoder(resp.Body).Decode(&result)
PHP
$ch = curl_init('https://api.ip-atlas.io/json/8.8.8.8'); curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ipa_live_...']); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $data = json_decode(curl_exec($ch), true);
Response fields
| Field | Type | Description |
|---|---|---|
ip | string | The queried IP |
country | string | ISO 3166-1 alpha-2 country code |
country_name | string | Full English country name |
continent_code | string | Two-letter continent code (NA, EU, AS, AF, OC, SA) |
region | string | State or region name |
city | string | City name |
latitude | float | Latitude (city-level centroid) |
longitude | float | Longitude (city-level centroid) |
asn | integer | Autonomous System Number |
org | string | Organization registered with the ASN |
is_datacenter | boolean | IP belongs to a known cloud / datacenter provider |
is_vpn | boolean | IP is associated with a known VPN or Tor exit service |
is_proxy | boolean | IP is likely a proxy (VPN or non-trusted datacenter) |
abuse_contact | string | Network abuse contact email from RIR WHOIS |
postal | string | Postal / ZIP code (populated where source data has coverage) |
timezone | string | IANA timezone identifier (populated where source data has coverage) |
country_code | string | Same as country. Alias for migration compatibility with services that use this field name. |
registry | string | Regional Internet Registry name (arin, ripe, apnic, lacnic, afrinic, or empty for unrouted). |
All 18 fields are included on every tier. No per-field upsells.
CORS & browser use
Do not call the IP-Atlas API directly from browser JavaScript with your API key. Anything in your client-side code can be read by anyone visiting the page — pasting your key into a fetch() call exposes it to harvesting and burns through your quota.
The right place to put the key is on a server you control: your own backend, a serverless function (Cloudflare Worker, Vercel Function, AWS Lambda), or an edge route. Your browser code calls your endpoint; your endpoint calls IP-Atlas with the key. That keeps the key off the wire that the browser sees.
Server-to-server is also faster: you can cache common lookups, batch requests, and avoid the CORS round-trip entirely.
If you have a one-off browser-only need (a static demo, a CodePen, a hackathon prototype), use the auth-less GET /json and GET /json/{ip} endpoints — they're rate-limited (60 req/min per IP) and don't require any key. They return the same shape, just without per-account quota accounting.
Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET, POST, OPTIONS Access-Control-Allow-Headers: Content-Type, X-API-Key
Preflight (OPTIONS) returns 204 No Content. The permissive CORS policy supports the auth-less demo use case above; it is not an endorsement of shipping API keys in browser code.
Data sources
- RIR delegation files — ARIN, RIPE NCC, APNIC, LACNIC, AFRINIC
delegated-extended, refreshed daily at 06:00 UTC. - ASN registry — All five RIR databases cross-referenced for ASN-to-organisation mapping (520k+ ASNs).
- Datacenter detection — Known IP ranges for AWS, Google Cloud, Azure, Cloudflare, DigitalOcean, OVH, Hetzner, Vultr, Linode/Akamai.
Data is hot-swapped daily without a service restart (atomic directory swap + SIGHUP).