WikiTwist

Build a Free WiFi Geolocation API with Cloudflare Workers, powered by Apple’s WiFi database

WiFi geolocation pin with concentric signal waves, symbolizing BSSID based positioning

WiFi geolocation with Cloudflare Workers and Apple’s WiFi database

Stop paying per request for WiFi geolocation. This Cloudflare Worker is a free, production-ready alternative to paid providers like Google Geolocation API.

It proxies Apple’s WiFi positioning service, accepts one or many BSSIDs, validates inputs, and can compute a weighted-centroid fix when you include RSSI values.

Add optional reverse geocoding and a smart auto-upgrade that retries broader lookups, and you get a fast, low-latency API that runs on the Cloudflare Free plan.

Why this project

If your app or device can observe nearby WiFi access points, you can approximate location without GPS.

Commercial APIs often charge per call and require keys, rate planning, and billing.

This Worker runs at the edge on Cloudflare, calling Apple’s WiFi database and returning coordinates with an optional human-readable address.

For multi-AP inputs with signal strengths, it computes a weighted centroid to estimate the device position more accurately.

You can use it in an MDM like as a Jamf Extension Attribute or script to trigger actions depending on where a device is etc.

Key features

Public repo

Code, examples, and configuration live here: wifi-geolocate-worker on GitHub

How it compares to paid geolocation APIs

This Worker is a cost-saving alternative to:

With Cloudflare’s generous free tier and this Worker’s design, many small to mid-scale workloads can run at negligible cost while remaining very fast.

Prerequisites

Quick start

Install Wrangler

# Local (recommended)
npm install -D wrangler@latest

# Or global
npm install -g wrangler@latest

# Verify
npx wrangler --version

Clone and setup

git clone https://github.com/gonzague/wifi-geolocate-worker.git
cd wifi-geolocate-worker
npm install

# Authenticate once
npx wrangler login
npx wrangler whoami

Run locally

npx wrangler dev
# Default local endpoint: http://127.0.0.1:8787

Deploy to Cloudflare

npx wrangler deploy
# wrangler.toml already points to worker/index.js and enables Smart Placement

API reference

GET /

Lookup a single access point by query string:

GET https://<your-worker>.workers.dev/?bssid=34:DB:FD:43:E3:A1&all=true&reverseGeocode=true

POST /

Batch query BSSIDs with optional RSSI in dBm:

{
  "accessPoints": [
    { "bssid": "34:DB:FD:43:E3:A1", "signal": -52 },
    { "bssid": "34:DB:FD:43:E3:B2", "signal": -60 },
    { "bssid": "34:DB:FD:40:01:10", "signal": -70 }
  ],
  "all": false,
  "reverseGeocode": true
}

Response shape

{
  "query": { "accessPoints": [{ "bssid": "34:db:fd:43:e3:a1", "signal": -52 }], "all": false },
  "found": true,
  "results": [
    {
      "bssid": "34:db:fd:43:e3:a1",
      "latitude": 48.856613,
      "longitude": 2.352222,
      "mapUrl": "https://www.google.com/maps/place/48.856613,2.352222",
      "signal": -52,
      "signalCount": 1,
      "signalMin": -52,
      "signalMax": -52,
      "address": {
        "displayName": "Champs-Élysées, Paris, Île-de-France, France",
        "address": {
          "road": "Champs-Élysées",
          "city": "Paris",
          "state": "Île-de-France",
          "country": "France"
        }
      }
    }
  ],
  "triangulated": {
    "latitude": 48.8571,
    "longitude": 2.3519,
    "pointsUsed": 3,
    "weightSum": 6.84,
    "method": "weighted-centroid",
    "signalWeightModel": "10^(dBm/10)"
  }
}

When Apple returns nothing, the Worker adds a Cloudflare IP geolocation fallback:

{
  "found": false,
  "fallback": {
    "latitude": 40.7128,
    "longitude": -74.0060,
    "accuracyRadius": 1000,
    "country": "US",
    "region": "NY",
    "city": "New York",
    "postalCode": "10001",
    "timezone": "America/New_York",
    "isp": "Cloudflare",
    "asOrganization": "Cloudflare, Inc."
  }
}

Smart auto-upgrade

If you request all=false and Apple has no exact match, the Worker retries with all=true and sets autoUpgraded: true in the response.

GET /?bssid=f8:ab:05:03:e9:40&all=false
{
  "query": { "accessPoints": [{"bssid": "f8:ab:05:03:e9:40", "signal": null}], "all": true },
  "found": true,
  "autoUpgraded": true,
  "results": [ ... ]
}

Reverse geocoding strategy

This respects Nominatim’s 1 req per second guideline, reduces latency, and avoids redundant calls when nearby APs share the same address.

Data handling and math

Caveats

Credits and prior work

Useful links


Exit mobile version