GuidesIP GEOLOCATION

Free IP geolocation APIs tested from the browser

We tested 11 free IP geolocation APIs from a browser on 26 October 2026: 10 work without a key, every cross-origin one sends CORS headers, and ip-api.com's free tier is HTTP only, so HTTPS pages block it. Call the API from the visitor's own browser: through any proxy, cors.dev included, the API sees the proxy's IP, and in our test it placed a North Carolina visitor in Ashburn, Virginia.

Every IP API we tested

We ran each API on 26 October 2026 with curl and an Origin: https://example.com header from a datacenter IP (162.19.169.95, OVH) and US residential IPs, then in Chromium 147.0.7727.15 from http://127.0.0.1:8001 and https://example.com. Every cross-origin API here sends CORS headers, so none needs a proxy. More browser-ready endpoints are in our list of 70 free public APIs.

Free IP geolocation APIs tested from a browser with fetch(), checked October 2026
APIKeyHTTPS on the free tierCORSFree limitFields
ipwho.isNoneYesYes, ACAO *1,000 requests/day, counted per site for browser callsCity, lat/lon, ASN, timezone
ipapi.coNoneYesYes, echoes OriginTrial: 1,000 per 24 hours, 30,000/monthCity, region, lat/lon, ASN
ip-api.comNoneNo, HTTP only (HTTPS gives 403)Yes, ACAO *45 requests/minute, non-commercialCountry, region, city, lat/lon
ipinfo.ioNone for /json; free token for LiteYesYes, ACAO *Legacy: 50,000/month; Lite: unlimitedCity, lat/lon, org; Lite: country and ASN
freeipapi.comNoneYesYes, ACAO *60 requests/minuteCountry, lat/lon
GeoJSNoneYesYes, ACAO *No rate limits (yet)Country, city when known, lat/lon, ASN
ipifyNoneYesYes, ACAO *No limitIP address only
DB-IPNoneYesYes, ACAO *500 requests/dayCountry, region, city
ipdataNone at ipdata.free; key for api.ipdata.coYesYes, ACAO *1,000 city-level lookups/day, then country onlyCountry, lat/lon, ASN, timezone
ipgeolocation.ioFree keyYesYes, echoes Origin1,000 credits/dayNot checked (401 without a key)
Cloudflare /cdn-cgi/traceNoneYesSame origin onlyNot an API; troubleshooting endpointIP, country, data center

Why a proxy reports the wrong location

An IP geolocation API locates the IP address that connects to it. A proxy opens its own connection to the API, and cors.dev does not forward the visitor's IP in any header, so the API locates the proxy. Same connections on 26 October 2026, direct and through proxy.cors.dev:

  • From a Spectrum residential IP in Laurinburg, North Carolina (65.191.24.190), ipinfo.io returned that IP and Laurinburg. Through the proxy it returned 172.68.138.140, Cloudflare, Ashburn, Virginia.
  • api.ipapi.is said Laurinburg directly and 104.22.104.228 in Ashburn through the proxy.
  • api.ipify.org returned 65.191.24.190 directly and 2a06:98c0:3600::103, a Cloudflare IPv6 address, through the proxy.
  • From our datacenter, GeoJS returned 162.19.169.95, OVH SAS, France directly, and 2a06:98c0:3600::103, Cloudflare, United States through the proxy.

A shared proxy IP also burns shared quotas. Through the proxy, ipwho.is answered 429 Rate limit exceeded, ipinfo.io 429 Rate limit hit, ipapi.co 429, freeipapi.com a 403 Cloudflare page, and DB-IP the error TEMPORARY_BLOCKED. Run this snippet in your console to see the difference yourself:

Direct versus proxied: what the API sees
const target = 'https://get.geojs.io/v1/ip/geo.json';

async function lookup(url, init) {
  const response = await fetch(url, init);
  if (!response.ok) {
    throw new Error(`${url} failed: ${response.headers.get('X-Cors-Error') ?? response.status}`);
  }
  return response.json();
}

const direct = await lookup(target);
const proxied = await lookup('https://proxy.cors.dev/' + target, { credentials: 'omit' });

console.table({ direct, proxied }, ['ip', 'city', 'country', 'organization_name']);

Locate the visitor with a fallback chain

Free tiers rate-limit by IP or by site, and any provider can be down or block a network. Chain three providers with a 3-second timeout each so one slow API can't hang your page. Every provider in the chain sends Access-Control-Allow-Origin: *, so the browser calls each one directly. Pass the coordinates to a free weather API to show local weather.

Locate the visitor from their own browser, with a fallback chain and a timeout
const providers = [
  {
    url: 'https://get.geojs.io/v1/ip/geo.json',
    pick: (d) => ({ ip: d.ip, country: d.country_code, city: d.city, lat: Number(d.latitude), lon: Number(d.longitude) }),
  },
  {
    url: 'https://ipinfo.io/json',
    pick: (d) => {
      const [lat, lon] = (d.loc ?? ',').split(',').map(Number);
      return { ip: d.ip, country: d.country, city: d.city, lat, lon };
    },
  },
  {
    url: 'https://ipwho.is/',
    pick: (d) => d.success && { ip: d.ip, country: d.country_code, city: d.city, lat: d.latitude, lon: d.longitude },
  },
];

async function locateVisitor(timeoutMs = 3000) {
  const failures = [];

  for (const { url, pick } of providers) {
    try {
      const response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
      if (!response.ok) throw new Error(`HTTP ${response.status}`);

      const location = pick(await response.json());
      if (location?.country) return { ...location, source: new URL(url).hostname };
      throw new Error('no location in the response');
    } catch (error) {
      failures.push(`${new URL(url).hostname}: ${error.message}`);
    }
  }

  throw new Error(`Every IP lookup failed: ${failures.join('; ')}`);
}

console.log(await locateVisitor());

Limits that bite in production

  • ipwho.is allows 1,000 free requests per day, and browser calls are counted per site (the Origin), shared by all your visitors. Over the limit you get 429 with Retry-After. From one IP, Origin: https://example.com got 429 with Retry-After: 42568, Origin: http://127.0.0.1:8001 got 429 with 72,094 seconds (about 20 hours), and a fresh origin got 200.
  • ip-api.com allows 45 requests per minute from each IP address for non-commercial use, and bans IPs that keep going over for 1 hour. HTTPS returns 403 "SSL unavailable for this endpoint", and Chromium 147 on https://example.com blocked the HTTP URL: Mixed Content: The page at 'https://example.com/' was loaded over HTTPS, but requested an insecure resource 'http://ip-api.com/json/'. This request has been blocked; the content must be served over HTTPS.
  • ipapi.co is a free trial: up to 30,000 lookups per month, 1,000 in 24 hours.
  • ipinfo.io: https://ipinfo.io/json works without a token at city level, but this legacy free API is limited to 50,000 requests per month. IPinfo Lite needs a free token and is unlimited, with country and ASN only.
  • freeipapi.com allows 60 requests per minute, commercial use included. https://freeipapi.com/api/json now redirects (307) to https://free.freeipapi.com/api/v1/json, so call the new URL.
  • ipdata: api.ipdata.co needs a key, while https://ipdata.free/ needs none and gives 1,000 city-level lookups a day, then country only.

Country lookup on a Cloudflare site

If your site runs behind Cloudflare, fetch /cdn-cgi/trace from your own domain: same origin, no CORS, no third party. It returns key=value lines including ip, loc (country code) and colo (data center). On cors.dev from our datacenter it returned ip=162.19.169.95, loc=DE, colo=FRA. Cloudflare documents it for troubleshooting which data center serves a request, not as an API.

Country from /cdn-cgi/trace on your own Cloudflare-proxied site
const response = await fetch('/cdn-cgi/trace');

if (!response.ok) {
  throw new Error(`/cdn-cgi/trace failed: ${response.status}`);
}

const trace = Object.fromEntries(
  (await response.text())
    .trim()
    .split('\n')
    .map((line) => [line.slice(0, line.indexOf('=')), line.slice(line.indexOf('=') + 1)]),
);

console.log({ ip: trace.ip, country: trace.loc, dataCenter: trace.colo });

How accurate IP location is

Use IP location for a country default and treat the city as a guess. Our datacenter IP 162.19.169.95 was in Germany for ipwho.is, ipinfo.io and ipapi.co, and in France for GeoJS, ip.sb, ipapi.is and ipdata.free. For 65.191.24.190, ipinfo.io and ipapi.is said Laurinburg, North Carolina, while ipquery said New York City.

On a VPN or mobile data, the API sees the VPN exit or the carrier's gateway and reports that location. For an exact position, ask for navigator.geolocation permission and turn the coordinates into a place name with a free geocoding API.

Good questions.

Can I use a CORS proxy for IP geolocation?

No. The API locates whatever IP connects to it, so through proxy.cors.dev it found a Cloudflare data center in Ashburn, Virginia instead of our visitor in North Carolina. All the cross-origin APIs we tested send CORS headers, so call them from the visitor's browser.

Which free IP geolocation API works over HTTPS without a key?

Eight of the APIs we tested: ipwho.is, ipapi.co, ipinfo.io/json, free.freeipapi.com, get.geojs.io, api.db-ip.com, ipdata.free and api.ipify.org, which returns the IP only. GeoJS says it has no rate limits yet.

Why does ip-api.com fail on my HTTPS site?

Its free endpoint is HTTP only; https://ip-api.com/json returns 403 "SSL unavailable for this endpoint". Browsers block HTTP requests from HTTPS pages as mixed content, so use an HTTPS provider.

Why does ipwho.is return 429 for my site?

Browser calls to ipwho.is count against one daily pool of 1,000 requests per site, shared by every visitor. When it runs out, every visitor gets 429 with a Retry-After header until the pool resets.

How accurate is IP geolocation with a VPN or mobile data?

The API sees the VPN exit server or the mobile carrier's gateway and returns that location. Even on a home connection, providers disagreed: Laurinburg, North Carolina for two of them, New York City for a third.

Is an IP address personal data under the GDPR?

In the EU, IP addresses are commonly treated as personal data, and a browser call sends the visitor's IP to the provider. Name the provider in your privacy notice and get advice for your case; this is not legal advice.