Use cases
Five integration patterns with real code and the fields you'll care about. Start with the one closest to yours — the API contract is the same for all of them.
Fraud detection
The majority of trial abuse, stuffed carts, and bot signups come from three IP types: commercial VPNs, residential proxies, and datacenter hosts. Every IP-Atlas response flags them — on every plan.
The three signals that pull the most weight
| Field | Why it matters |
|---|---|
is_vpn | True if the IP is on a known commercial VPN ASN or range. High-signal for "user hiding location". |
is_datacenter | True if the IP is on AWS/GCP/Azure/DO/Hetzner/etc. A human shouldn't be signing up from a VPS. |
asn | The network. Combine with your own allow/deny list of known hosting and proxy networks. |
Pattern 1 — Hard block at signup
Reject signups from datacenter or VPN IPs outright. One HTTP call per signup; adds ~20 ms to the critical path.
// Node — signup middleware import { IPAtlas } from '@trellisdigitalservices/ip-atlas'; const ipa = new IPAtlas({ apiKey: process.env.IPATLAS_KEY }); app.post('/signup', async (req, res) => { const ip = req.headers['cf-connecting-ip'] || req.ip; const r = await ipa.lookup(ip); if (r.is_datacenter || r.is_vpn) { return res.status(403).json({ error: 'Please sign up from your normal network.' }); } // ... proceed });
This is the strictest setting. About 0.3% of real users will be caught (corporate VPNs, travellers on mobile hotspots). For anything where false positives matter, use Pattern 2.
Pattern 2 — Risk score, hand-off to step-up auth
Don't block — score, then route suspicious traffic into captcha / SMS verification.
function riskScore(ipData, signup) { let score = 0; if (ipData.is_datacenter) score += 60; if (ipData.is_vpn) score += 40; if (signup.email.endsWith('+test@gmail.com')) score += 50; if (signup.country !== ipData.country) score += 20; return score; // >70 = step-up, >100 = block }
Pattern 3 — Batch-enrich your signup log daily
Don't gate the request path on a third-party API. Log the raw IP, and every night run the day's signups through /v1/batch to flag post-hoc.
# Python — nightly enrichment from ipatlas import IPAtlas c = IPAtlas(api_key=os.environ["IPATLAS_KEY"]) for chunk in chunked(todays_signup_ips, 100): batch = c.lookup_batch(chunk) for r in batch["results"]: if r.get("is_datacenter") or r.get("is_vpn"): mark_for_review(r["ip"])
Geo-targeting & localisation
Every visitor hits your origin with an IP. Resolve it once at the edge, cache it, and ship the right currency, language, and legal banners before your React bundle finishes parsing.
Fields you need
| Field | Use |
|---|---|
country | Currency, legal banners (GDPR, CCPA), regional pricing |
country_name | Human-readable "shipping to Germany" |
region | US state for sales-tax routing; EU region for VAT |
timezone | "Delivery at 3pm your time" instead of "3pm UTC" |
Cloudflare Worker pattern (recommended)
At the CDN edge, look up the visitor IP once per session and cache the result in a signed cookie. Your origin never pays the RTT.
// Cloudflare Worker — geo-cookie export default { async fetch(req, env) { const cookie = getCookie(req, 'ipa_geo'); if (cookie) return fetch(req, { cf: { cookie } }); const ip = req.headers.get('cf-connecting-ip'); const r = await fetch(`https://api.ip-atlas.io/json/${ip}`, { headers: { 'X-API-Key': env.IPATLAS_KEY } }).then(r => r.json()); const res = await fetch(req); res.headers.append('Set-Cookie', `ipa_geo=${r.country}; Max-Age=86400; Path=/; Secure`); return res; } };
Next.js middleware pattern
// middleware.ts export async function middleware(req) { const ip = req.ip; const r = await fetch(`https://api.ip-atlas.io/json/${ip}`, { headers: { 'X-API-Key': process.env.IPATLAS_KEY! } }).then(r => r.json()); const res = NextResponse.next(); res.cookies.set('country', r.country, { maxAge: 86400 }); res.cookies.set('tz', r.timezone, { maxAge: 86400 }); return res; }
What NOT to do
- Don't hit the API on every render. One call per session is enough. Cache the country in a cookie or KV store.
- Don't geo-lock by browser language. Users in Berlin who set English as their browser language shouldn't see German pricing.
- Don't rely on IP for identity. Geo-IP is probabilistic; use it for UX, not KYC.
Compliance & sanctions
Financial services, crypto, gaming, and export-controlled SaaS all need a country determination on every request — and a paper trail for audits.
What IP-Atlas gives you
| Field | Use |
|---|---|
country | ISO 3166-1 alpha-2, RIR-sourced. Check against OFAC SDN, EU sanctions, UK HMT lists. |
registry | Which RIR allocated the block. Useful for regional enforcement audits. |
is_vpn, is_datacenter | Flag attempts to bypass geo blocks via VPN or cloud relays. |
abuse_contact | For automated takedown notices when you detect abuse. |
OFAC-style block pattern
// Current US-OFAC comprehensive-sanctions list (2026) const BLOCKED = ['IR', 'KP', 'SY', 'CU']; app.use(async (req, res, next) => { const r = await ipa.lookup(req.ip); if (BLOCKED.includes(r.country)) { audit.log({ event: 'sanctions-block', ip: req.ip, country: r.country, ts: Date.now() }); return res.status(451).send('Service not available in your jurisdiction.'); } if (r.is_vpn) { // extra scrutiny: challenge with KYC step-up } next(); });
Audit considerations
- Log every block. Store the IP, country, ASN, timestamp, and IP-Atlas response version. Auditors will ask.
- Don't delete the log. Sanctions enforcement audits typically reach back 5 years.
- Document your lists. OFAC changes designations; your block list should be source-controlled with clear changelogs.
- Assume VPN bypass. A motivated user can use a commercial VPN. Combine IP with KYC for high-stakes decisions.
Analytics & attribution
Two options: batch-enrich via the API, or drop a monthly MMDB into your log pipeline and keep raw IPs out of your analyst's hands entirely.
Pattern A — Batch enrichment at ingest
Every hour, pull the last hour's IPs from your log ingest, chunk into 100-item batches, call /v1/batch, and join the results back onto the log line.
// Python — hourly enrichment from ipatlas import IPAtlas c = IPAtlas(api_key=os.environ["IPATLAS_KEY"]) for chunk in chunked(unique_ips_this_hour, 100): r = c.lookup_batch(chunk) for row in r["results"]: warehouse.upsert("ip_enrichment", row)
Volume: 1M unique IPs/day = 10,000 batch calls = ~30M requests/month. That exceeds the Pro plan's 150k/mo included quota — overage at $0.40/1k applies, or consider a dedicated pipeline with MMDB (no per-request cost).
Pattern B — MMDB at the log shipper
Pro subscribers get monthly MMDB snapshots. Ship them with your log shipper (Vector, Fluent Bit, Filebeat) and enrich inline. No outbound HTTP, no latency, no analyst ever touches the raw IP.
# Vector config — MMDB enrichment on every log event [transforms.geo] type = "geoip" inputs = ["logs"] database = "/etc/vector/ipatlas-2026-04.mmdb" source = "ip" target = "geo"
What enrichment unlocks
- Country slicing. "MRR by country, monthly" without a country list.
- ASN attribution. Detect corporate traffic ("all our traffic from AS8068 is Microsoft internal").
- Bot cohorts. Segment analytics by
is_datacenterto keep real user trends clean. - CDN-miss analysis. Correlate region with p99 latency to see where you need another PoP.
Privacy posture
GDPR, CCPA, and most state privacy laws treat IP addresses as personal data. The MMDB pattern lets you enrich once at the log shipper and strip the raw IP before the data reaches your warehouse. The country / ASN stays; the identifier doesn't.
Content delivery & edge routing
When you run multiple origins — US-East, EU-West, APAC — the client's country and ASN tell you which one to hit. IP-Atlas MMDB makes that lookup a memory read, not a network round trip.
Signals you'll use
| Field | Use |
|---|---|
country | Continental routing — US → us-east, CN → apac, etc. |
asn | Shortcut for edge-network peerings — e.g. prefer GCP origin for Google ASN clients. |
is_datacenter | Don't treat a server-to-server call as a human session; route to API-origin, not CDN-origin. |
latitude, longitude | Nearest-origin selection when you have many PoPs. |
Edge Worker routing pattern
// Cloudflare Worker — pick origin by country const ORIGIN = { US: 'https://us-east.app.example.com', GB: 'https://eu-west.app.example.com', JP: 'https://apac.app.example.com', DEFAULT: 'https://us-east.app.example.com' }; export default { async fetch(req, env) { const ip = req.headers.get('cf-connecting-ip'); const r = await env.IPATLAS.lookup(ip); // KV-cached wrapper const target = ORIGIN[r.country] || ORIGIN.DEFAULT; return fetch(target + new URL(req.url).pathname, req); } };
MMDB at your own Nginx
# nginx.conf — ngx_http_geoip2_module
load_module modules/ngx_http_geoip2_module.so;
geoip2 /etc/nginx/ipatlas-2026-04.mmdb {
$geo_country country;
$geo_asn asn;
}
server {
location / {
proxy_pass https://origin-$geo_country.internal;
}
}
Memory-mapped MMDB lookup is ~800 ns. On a box handling 50K RPS that's less than 0.1% CPU.
When NOT to geo-route
- Stateful sessions. If the user's session lives on US-East, don't send them to EU-West just because they flew to Paris.
- Data-residency requirements. GDPR may require EU data stays in the EU regardless of the user's current country.
- Low-volume services. One origin is simpler. Don't add complexity unless latency demands it.