DEV Community

NexGenData
NexGenData

Posted on

IPinfo Got Expensive. MaxMind Got Complicated. Here's the $0.0005 Drop-In That Needs Zero Setup

IPinfo Got Expensive. MaxMind Got Complicated. Here's the $0.0005 Drop-In That Needs Zero Setup

IP geolocation is one of those infrastructure pieces that every backend needs and nobody thinks about until the invoice shows up or the download link starts 403-ing.

For a decade the cheap default was IPinfo's free tier (50,000 requests per month, no credit card, keys issued in under a minute) or MaxMind GeoLite2 (a free database updated twice weekly, dropped into your Docker image, looked up offline). Between those two options, most teams never paid for IP geolocation at all. Cloud providers like AWS, Cloudflare, and Fastly quietly added header-level geo fields, but those only worked for inbound HTTP traffic; anything ingesting logs, validating users, or analyzing audit trails still reached for IPinfo or MaxMind.

Both of those defaults have quietly disintegrated.

In May 2025 IPinfo restructured its free tier down to 1,000 requests per day with a credit card on file and a soft-gate captcha on any spike above 10 requests per second. The old "50k/month free, no card" tier is gone. The paid tiers start at $249/month for 250k requests, which is fine for mid-scale but absurd for a weekend project.

MaxMind's trajectory is messier. The GeoLite2 database is still nominally free, but since late 2019 MaxMind has required a license key, an account, and a signed EULA for every download. In December 2023 they tightened verification further — license keys started expiring every 90 days unless you reconfirmed via email, and automated download tooling (geoipupdate) started failing silently for accounts whose phone numbers didn't verify. By early 2026 the "GeoLite2 is free, drop it in your Docker image" pattern takes roughly 45 minutes of paperwork per environment, and the licensing language is ambiguous enough that a lot of compliance teams have started refusing it outright.

If you just need to map an IP to a country and a city, the setup cost now outweighs the value.

This post is about ip-geolocation-replacement (actor ID vf4i827J4F8Wr8weB), an Apify actor that blends DB-IP Lite and ipwho.is under a single unified schema, costs $0.0005 per IP lookup on PPE, and needs zero signup beyond an Apify token.

Pricing data for IPinfo and MaxMind cited here reflects published rates as of Q1 2026; recheck the vendor pages for current pricing.

What actually changed

IPinfo 2025 restructure

IPinfo's original free tier — issued 2014, expanded in 2018 — was one of the more generous deals on the open web. 50,000 lookups per month, no credit card, a simple API key in the dashboard, a stable JSON schema with country, city, region, loc, org, postal, timezone, and (on upgrade) ASN and carrier detail. A lot of open-source tools bundled "get an IPinfo key" as their setup path because it was a real answer that worked.

In May 2025 that plan was replaced by a new "free" tier at 1,000 requests per day, card-required. The company's justification, posted in a dev blog on 2025-05-14, was the usual mix of "abuse mitigation" and "sustainability of the data pipeline." The practical effect was that the free tier dropped by an order of magnitude and became gated behind a payment method. The next paid tier is the Basic plan at $249/month for 250,000 requests.

That pricing is defensible if you've been paying vendors for IP data before. It's not defensible if you're a side project doing 30k lookups a month and used to pay zero.

MaxMind's drift

MaxMind's path is gradual friction rather than a single policy change. Key moments:

  • December 2019: GeoLite2 downloads require a MaxMind account and license key, following a pseudo-legal dispute about redistribution.
  • 2020-2022: Periodic EULA updates, all requiring re-acceptance. Enterprise teams start hitting them in compliance review.
  • December 2023: License keys begin expiring every 90 days without email reconfirmation. geoipupdate tooling silently stops updating.
  • Q3 2024: Free tier blocked for accounts registered with disposable-email domains. Verification of a corporate email becomes effectively mandatory.
  • 2025: Several indirect enterprise reports of EULA language being interpreted aggressively by MaxMind's legal team in audit settings, leading to broader compliance-team hesitancy.

GeoLite2 itself is still free. But "free" in the sense of "usable in production without a compliance review" is gone. And the paid GeoIP2 database product starts at $24/month for the City database with monthly updates, $370/month for GeoIP2 Precision Web Service at 10k queries/day. Moving up costs real money quickly.

The downstream effect

The open-source ecosystem adjusted quickly. ipwho.is (a free no-key public API), DB-IP Lite (a CC-BY-licensed free database, fully redistributable), ip-api.com (free tier, 45 req/min with HTTPS paid), and a long tail of smaller providers filled the gap. None of them is a single drop-in replacement for either IPinfo or MaxMind — they differ on coverage, rate limits, auth requirements, and schema.

A real replacement needs to blend them behind a single schema.

What the replacement actor does

The ip-geolocation-replacement actor is a thin blend layer:

  1. Primary lookup: DB-IP Lite database, loaded into the actor's memory at startup. CC-BY licensed, monthly updates, covers country/region/city/lat-lng/timezone/ASN.
  2. Enrichment fallback: ipwho.is for fields DB-IP doesn't expose well — ISP name, VPN/proxy/hosting flags, currency, calling code.
  3. IPinfo-compatible schema: the output shape mirrors IPinfo's published fields, so legacy code looking for country, city, loc, org, timezone parses unchanged.
  4. Batch ingestion: pass an array of up to 10,000 IPs in one run. The actor resolves them in parallel and returns a dataset.

PPE pricing is $0.0005 per IP lookup. At 100,000 lookups per month that's $50 — roughly one-fifth of IPinfo's Basic tier for equivalent volume and zero signup friction.

The actor emits this response shape:

{
  "ip": "8.8.8.8",
  "ip_version": 4,
  "country": "US",
  "country_name": "United States",
  "region": "California",
  "region_code": "CA",
  "city": "Mountain View",
  "postal": "94043",
  "loc": "37.4056,-122.0775",
  "latitude": 37.4056,
  "longitude": -122.0775,
  "timezone": "America/Los_Angeles",
  "utc_offset": "-0700",
  "asn": "AS15169",
  "asn_name": "Google LLC",
  "org": "GOOGLE",
  "isp": "Google LLC",
  "hosting": true,
  "vpn": false,
  "proxy": false,
  "currency": "USD",
  "calling_code": "+1",
  "sources": ["db-ip-lite", "ipwho.is"],
  "fetched_at": "2026-08-28T12:00:00Z"
}
Enter fullscreen mode Exit fullscreen mode

sources tells you which upstreams contributed to the record. hosting, vpn, proxy come from ipwho.is plus a supplementary bogon/datacenter list maintained internally.

Old vs. new: the comparison table

Feature IPinfo Basic ($249/mo) MaxMind GeoIP2 City DB-IP Paid ($99/mo) ipwho.is (free) ip-geolocation-replacement
Monthly quota 250k requests unlimited (DB copy) 100k requests ~10k/day soft cap pay-per-use
Setup friction card + signup license key + EULA + email verify card + signup none Apify token only
Country accuracy ~99% ~99% ~99% ~97% ~99%
City accuracy (IPv4) ~80% ~80% ~80% ~75% ~80%
IPv6 coverage yes yes yes yes yes
ASN + org yes yes (separate DB) yes yes yes
VPN/proxy/hosting flags paid add-on paid add-on paid tier partial yes (default)
Batch of 10k in one call no n/a no no yes
GDPR redistribution clarity good complex EULA moderate very simple CC-BY + aggregator
Free tier in 2026 1k/day, card required 90-day-expiring license limited ~10k/day $5/mo Apify credit
Per-lookup price at 100k $0.001 amortized ~$0.0002 $0.001 $0 (rate-limited) $0.0005
Works offline no yes no no no (actor-hosted)

MaxMind's amortized per-lookup cost is lower if you're doing millions of lookups against a single downloaded database. For anything below 1M/month, or for teams that don't want to deal with the license/update friction, PPE pricing wins.

Migration: the two-line change

If you currently call IPinfo:

import requests

resp = requests.get(
    f"https://ipinfo.io/{ip}/json",
    params={"token": IPINFO_TOKEN}
)
data = resp.json()
print(data["country"], data["city"], data["loc"])
Enter fullscreen mode Exit fullscreen mode

The replacement:

from apify_client import ApifyClient

client = ApifyClient("APIFY_TOKEN")
run = client.actor("nexgendata/ip-geolocation-replacement").call(run_input={
    "ips": [ip]
})
data = next(client.dataset(run["defaultDatasetId"]).iterate_items())
print(data["country"], data["city"], data["loc"])
Enter fullscreen mode Exit fullscreen mode

Field names match IPinfo's published schema. Legacy code reads the same keys.

If you're migrating from MaxMind, the switch is even simpler because MaxMind's mmdb record is a dict — you just change the source:

# Before (MaxMind reader)
import geoip2.database
reader = geoip2.database.Reader('GeoLite2-City.mmdb')
rec = reader.city(ip)
print(rec.country.iso_code, rec.city.name, rec.location.latitude)
Enter fullscreen mode Exit fullscreen mode
# After
from apify_client import ApifyClient
client = ApifyClient("APIFY_TOKEN")
run = client.actor("nexgendata/ip-geolocation-replacement").call(
    run_input={"ips": [ip]}
)
rec = next(client.dataset(run["defaultDatasetId"]).iterate_items())
print(rec["country"], rec["city"], rec["latitude"])
Enter fullscreen mode Exit fullscreen mode

The field-mapping layer is yours to write once, but it's trivial.

Code examples

Python: log-ingestion enrichment at scale

A SaaS billing team processes 2M authentication events per day across a fleet of 40 services. Historically they MaxMind-enriched each event at ingest time using a bundled GeoLite2 database, and re-ran the enrichment weekly when the database updated. The 2025 license-key friction broke their CI pipeline (90-day expiration, CI-bot email didn't verify), and rather than fight IT about corporate-account ownership of the license, they cut over to the actor.

from apify_client import ApifyClient
import itertools

client = ApifyClient("APIFY_TOKEN")

def chunk(iterable, size):
    it = iter(iterable)
    while True:
        batch = list(itertools.islice(it, size))
        if not batch:
            break
        yield batch

# auth_events is a list of {event_id, user_id, ip, ts, ...}
def enrich_auth_events(auth_events):
    ip_to_event = {}
    for e in auth_events:
        ip_to_event.setdefault(e["ip"], []).append(e)

    unique_ips = list(ip_to_event.keys())
    enriched_events = []

    for ip_batch in chunk(unique_ips, 5000):
        run = client.actor("nexgendata/ip-geolocation-replacement").call(
            run_input={"ips": ip_batch}
        )
        for rec in client.dataset(run["defaultDatasetId"]).iterate_items():
            for evt in ip_to_event[rec["ip"]]:
                enriched_events.append({
                    **evt,
                    "country": rec["country"],
                    "city": rec["city"],
                    "asn": rec["asn"],
                    "hosting": rec["hosting"],
                    "vpn_flag": rec["vpn"],
                })
    return enriched_events
Enter fullscreen mode Exit fullscreen mode

The collapse-by-unique-IP pattern matters. Auth events from a busy SaaS follow a long-tail distribution across IPs — 2M events might touch 150k unique IPs. At $0.0005 per lookup, that's $75/day or $2,250/month. MaxMind-with-license-friction was "free" but required a reliable CI bot and a human renewing license keys quarterly. The actor is predictable cashflow in exchange for eliminating the human.

curl: ad-hoc single-IP investigation

curl -X POST "https://api.apify.com/v2/acts/nexgendata~ip-geolocation-replacement/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"ips": ["1.1.1.1"]}' \
  | jq '.[0] | {ip, country, city, asn_name, hosting, vpn}'
Enter fullscreen mode Exit fullscreen mode

Fast for shell pipelines and on-call investigations. Returns:

{
  "ip": "1.1.1.1",
  "country": "US",
  "city": "Los Angeles",
  "asn_name": "Cloudflare, Inc.",
  "hosting": true,
  "vpn": false
}
Enter fullscreen mode Exit fullscreen mode

Node.js: fraud-signal middleware

A classic use case: deny or challenge requests from VPN/proxy/hosting-provider IPs on a signup endpoint. This is fraud-adjacent work where MaxMind's anonymous-IP add-on costs an extra $90/month and IPinfo's "privacy detection" add-on costs $50/month on top of Basic.

const { ApifyClient } = require('apify-client');
const express = require('express');

const apify = new ApifyClient({ token: process.env.APIFY_TOKEN });
const app = express();
app.use(express.json());

// In-memory cache; production would be Redis with TTL.
const cache = new Map();
const CACHE_TTL_MS = 60 * 60 * 1000;

async function getGeo(ip) {
  const entry = cache.get(ip);
  if (entry && Date.now() - entry.t < CACHE_TTL_MS) return entry.rec;

  const run = await apify.actor('nexgendata/ip-geolocation-replacement').call({
    ips: [ip],
  });
  const { items } = await apify.dataset(run.defaultDatasetId).listItems();
  const rec = items[0];
  cache.set(ip, { t: Date.now(), rec });
  return rec;
}

app.post('/signup', async (req, res) => {
  const ip = req.headers['x-forwarded-for']?.split(',')[0] || req.ip;
  const geo = await getGeo(ip);

  if (geo.vpn || geo.proxy || geo.hosting) {
    return res.status(403).json({
      error: 'signup_from_anonymizing_network',
      country: geo.country,
      reason: geo.hosting ? 'datacenter' : 'anonymizer',
    });
  }

  // proceed with signup ...
  res.json({ ok: true });
});

app.listen(3000);
Enter fullscreen mode Exit fullscreen mode

Cache matters. Without the 60-minute TTL, a signup endpoint under attack could fan out millions of lookups; with it, the same attacker IP costs $0.0005 once.

Python: bulk IP list analysis for a threat-intel team

A CTI team ingests a daily feed of ~250k IPs flagged across their partner feeds and wants quick country/ASN attribution plus hosting-provider detection.

from apify_client import ApifyClient
from collections import Counter

client = ApifyClient("APIFY_TOKEN")

with open("daily_feed.txt") as f:
    ips = [line.strip() for line in f if line.strip()]

by_country = Counter()
by_asn = Counter()
hosting_count = 0

for i in range(0, len(ips), 5000):
    batch = ips[i:i+5000]
    run = client.actor("nexgendata/ip-geolocation-replacement").call(
        run_input={"ips": batch}
    )
    for rec in client.dataset(run["defaultDatasetId"]).iterate_items():
        by_country[rec["country"]] += 1
        by_asn[rec["asn_name"]] += 1
        if rec["hosting"]:
            hosting_count += 1

print(f"Top 10 countries: {by_country.most_common(10)}")
print(f"Top 10 ASNs: {by_asn.most_common(10)}")
print(f"Hosting-provider IPs: {hosting_count} ({hosting_count/len(ips)*100:.1f}%)")
Enter fullscreen mode Exit fullscreen mode

250k lookups at $0.0005 is $125/day. For a CTI team running this as a nightly batch, that's a reasonable number against what IPinfo or MaxMind Precision would cost.

curl: ASN-only lookup for a network-mapping experiment

curl -X POST "https://api.apify.com/v2/acts/nexgendata~ip-geolocation-replacement/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "ips": ["8.8.8.8", "1.1.1.1", "9.9.9.9", "208.67.222.222"],
    "fields": ["ip", "asn", "asn_name", "country"]
  }' | jq
Enter fullscreen mode Exit fullscreen mode

The fields parameter trims the response to just the columns you need, which reduces bandwidth for very large batches. Same price per lookup, smaller payload.

Worked example: a login-risk gate that used to depend on MaxMind

A fintech ran a login-risk model that used MaxMind GeoIP2 City + Anonymous IP as two of its ~40 features. Their deployment pattern: bundle the .mmdb files into a Docker image, rebuild weekly when MaxMind's update cycle ran, and cut a new image for their feature-store service.

In January 2026 their license key expired silently (the renewal email landed in a shared ops inbox nobody watched). geoipupdate kept running but returned the same database for two weeks. Their login-risk model's VPN-detection precision dropped noticeably before their on-call surfaced the root cause.

The post-mortem had two threads: improve monitoring on the .mmdb file's last-updated timestamp, and reduce the operational surface area of IP geolocation as a dependency. They chose to replace the MaxMind reader in their online feature path with the actor, keeping MaxMind in the nightly offline batch (where staleness was detected more reliably via a Great Expectations check).

The migration cost: about three engineering days to write the adapter, wire up the cache, validate the VPN-flag precision/recall parity against their held-out label set, and update runbook docs. The ongoing cost: roughly $140/month against their online login volume (~280k unique IPs/day with cache, roughly 8-9k unique lookups/day after deduplication). Net effect: one class of outage eliminated, modest incremental monthly spend, and the MaxMind license no longer on the incident-critical path.

This pattern — actor for online/interactive lookups where operational simplicity matters, MaxMind for offline batch where amortized per-lookup cost matters — is what most production deployments converge to.

Schema-level gotchas

A few differences worth noting when migrating from IPinfo or MaxMind:

  • loc format. IPinfo uses "lat,lng" as a comma-separated string. MaxMind surfaces separate location.latitude and location.longitude floats. The actor provides both (loc string for IPinfo-compat, latitude/longitude floats for MaxMind-compat).
  • org vs isp vs asn_name. These are overlapping but not identical. IPinfo's org historically returned the ASN name (e.g., "AS15169 Google LLC"). The actor returns the ASN name in asn_name, the parent organization in org, and the retail ISP in isp. For most use cases asn_name is what you want.
  • Hosting/VPN/proxy detection. The actor's flags are based on ipwho.is plus a curated datacenter ASN list. Recall is good but not as tight as IPinfo's paid Privacy Detection or MaxMind's Anonymous IP. For high-stakes fraud decisions, consider adding a dedicated tool on top.
  • City-level accuracy. DB-IP Lite and MaxMind GeoLite2 both derive city from ASN ownership plus netblock geolocation, and both run in the 75-85% accuracy band at the city level. This isn't a gap in any one vendor; it's the ceiling of the underlying data.
  • IPv6. All major sources have IPv6 coverage now. Accuracy is typically 5-10 percentage points lower than IPv4 because IPv6 netblocks rotate more aggressively.
  • Private and bogon IPs. The actor returns country: null and sources: ["bogon"] for RFC1918, link-local, and loopback addresses. Client code should handle the null country gracefully.

When this is not the right answer

  • You're doing 10M+ lookups per month with an offline-DB pattern. MaxMind GeoIP2 City at $24/month is unbeatable at that volume if you can stomach the licensing.
  • You need latency under 10ms. The actor's p50 is 300-600ms. Cache aggressively or use an offline DB for hot paths.
  • You need the highest-grade VPN/proxy detection. IPinfo Privacy Detection and MaxMind Anonymous IP both run dedicated detection pipelines; the actor's VPN flag is good-enough for most filtering, not investigative-grade.
  • You need ISP-level billing addresses or carrier-phone-type data. IPinfo and MaxMind paid tiers expose fields (residential-vs-business classification, carrier type) that the free upstreams don't.
  • You're operating in a fully airgapped environment. You need a database solution, not an API.

FAQ

Why is this cheaper than IPinfo?

Two reasons. First, DB-IP Lite is CC-BY licensed — the actor pays nothing to redistribute it. Second, ipwho.is's free tier covers the enrichment fields. Apify's infrastructure covers the hosting and compute, which is what the $0.0005 PPE covers. We're not paying a per-record licensing fee, so we don't pass one to you.

What about accuracy vs. paid IPinfo?

For country-level attribution, effectively identical (both in the 99%+ band). For city-level, slightly lower — IPinfo paid tiers run custom collection on top of the public data and claim 85%+ city accuracy in major markets; the actor runs at about 80%. For most use cases — logging, fraud signals, content geo-gating — this is not a meaningful difference.

Is the data redistribution legally clear?

Yes. DB-IP Lite ships under CC-BY-4.0 with attribution (present in the actor output's sources field and in the actor's README). ipwho.is is a free public API with an open usage policy. Neither carries the re-distribution friction of MaxMind's EULA. For teams that have had compliance review push back on GeoLite2, this is the cleanest option.

Can I get the raw database offline?

Not through the actor; it runs as a hosted service. If you need the offline DB-IP Lite database, download it directly from db-ip.com (also free). The actor exists to save you the setup time, not to replace the offline pattern.

How often does the data update?

DB-IP Lite updates monthly. ipwho.is's backing data updates weekly to daily depending on the field. The actor refreshes its in-memory DB-IP Lite copy on every run start (every deployment cycle), and ipwho.is lookups are always live.

Does it handle IPv6?

Yes. Pass IPv6 addresses alongside IPv4 in the same request. Both DB-IP Lite and ipwho.is cover IPv6; the schema is identical.

What about rate limits?

Apify-side, the actor supports up to 100 concurrent lookups per run by default and tolerates batches of 10,000 IPs in a single run. Upstream, ipwho.is rate-limits aggressively — the actor paces calls under the free-tier allowance and uses DB-IP Lite for everything that doesn't need enrichment flags.

Can I run this on Apify's free tier?

Yes. Apify's free tier includes $5/month of compute credit, which works out to roughly 10,000 IP lookups on PPE. Beyond that it's pay-as-you-go. See the actor page for current pricing.

Does the actor respect GDPR?

IP addresses are personal data under GDPR when they can be linked to an identifiable individual. The actor processes them in transient memory only — there is no IP-lookup log retained beyond the Apify run log retention window (7 days for paid plans, shorter for free). For EU deployments requiring DPA coverage, Apify offers a standard DPA under the master terms.

What's next

Adjacent actors from the same pipeline that often show up in the same stacks:

  • asn-lookup-and-prefix-enumerator — returns all CIDR prefixes announced by a given ASN, useful for building deny/allow lists or network-footprint analysis.
  • reverse-dns-bulk — bulk PTR-record lookups for log enrichment, with caching semantics friendly to high-churn SaaS log pipelines.
  • tor-exit-node-feed — the Tor Project's public exit-node list, refreshed every 30 minutes, returned in a stable schema for deny-list workflows.

Conclusion

IP geolocation used to be the boring infrastructure piece that every backend got for free. IPinfo's 50k/month free tier and MaxMind's redistributable GeoLite2 covered most production needs between them. Both have gradually tightened — IPinfo's 2025 restructure cut the free tier by 98%, MaxMind's license-key and EULA friction has crept up through 2020-2025 to where many compliance teams now push back on it.

You don't have to rewrite your auth pipeline. ip-geolocation-replacement emits an IPinfo-compatible JSON schema on top of DB-IP Lite + ipwho.is, costs $0.0005 per lookup with zero setup beyond an Apify token, and handles batches of 10,000 IPs in a single call. For teams doing under 10M lookups a month, this is the lowest-friction path from "pipeline is broken or expensive" back to "pipeline works and is predictable."

If you're doing 10M+ lookups a month, keep MaxMind's offline DB in your batch path and put the actor on the online/interactive path where operational simplicity matters more than amortized cost. That split has been the sweet spot for every production deployment we've seen migrate in the last twelve months.

Top comments (0)