REFERENCE
How Wi-Fi positioning works
Everything you need to read a euqlid.space response correctly — the physics, the fields, the accuracy, and the rules of the road.
This is the reference served from the API host. The product documentation, in every supported language, lives at euqlid.space/help. The interactive schema browser (/docs, /openapi.json) is not exposed publicly.
Wi-Fi positioning 101
Every Wi-Fi access point continuously broadcasts a globally-unique hardware address — its BSSID. Access points rarely move. That makes the set of BSSIDs a device can hear a remarkably stable fingerprint of where that device physically is.
Mapping services (Apple, Google, Skyhook and others) have spent years observing which BSSIDs appear at which coordinates. euqlid.space operates a large, US-focused observation graph of that relationship: given a BSSID we know roughly where it lives; given a coordinate we know which BSSIDs to expect nearby.
That unlocks three directions of inference:
- Forward (verify): a device reports the BSSIDs it currently sees → we return where that points, and whether it matches a claimed location.
- Reverse (locate): you give a coordinate or ZIP → we return the access points expected around it.
- Postal (v2): you give a country and a postal code → we sample a coordinate from inside that area and, optionally, return the access points around it. See postal sampling.
BSSID & OUI
A BSSID looks like 02:00:5e:10:00:01 — six bytes. The first three (the OUI, Organizationally Unique Identifier) are assigned by the IEEE to a manufacturer, so they reveal the vendor (Cisco, Ubiquiti, eero, a phone hotspot chipset, …). euqlid.space resolves the OUI to a vendor and a device_class, and flags addresses whose OUI bit marks them locally administered — the hallmark of a randomized or virtual MAC. (The address above is one of those: every BSSID printed in this document is synthetic.)
hacc, vacc & altitude
Each stored access point carries the positioning system's own error estimates:
hacc_m— horizontal accuracy in metres. The radius of the circle the AP is believed to sit within. Smaller is better; single-digit values are common in dense areas.vacc_m— vertical accuracy in metres, when an altitude solution exists.alt_m— estimated altitude. Useful to separate floors in tall buildings; pair it with the altitude filter on/v1/aps/nearbyor/v2/nearby.
Treat hacc_m as the per-point confidence. A verify result aggregates many points, so its spread_m (how far the matched points scatter) is the better signal of overall certainty.
Randomized MACs & twins
Phones and some APs rotate their MAC address for privacy. These randomized MACs are marked is_random: true — they are poor location anchors and should be discounted. euqlid.space also detects twins: the same BSSID observed at two distinct locations (a device that physically moved, or address reuse). When present, the twin object reports the second location and how far it sits from the primary, so you can decide whether the anchor is trustworthy.
ZIP vs coordinate precision
On /v1/aps/nearby you can query by exact lat/lon or by zip. They are not the same thing, and euqlid.space never pretends they are. Each response reports which one it used in origin.source:
| origin.source | Typical error | Meaning |
|---|---|---|
| coordinates | 5–30 m | You supplied lat+lon. Address-level. Safe for verification. |
| zip_centroid | kilometres | You supplied a ZIP. One fixed reference point for the whole ZIP. Coarse enrichment only — never proof. |
A zip_centroid lookup also appends an explicit string to the response's warnings array. If you make risk decisions, branch on origin.source and read warnings — a ZIP-derived answer must never be treated as a fix.
The zip_centroid value changed. The enum name is unchanged and the lookup is still deterministic — the same ZIP always resolves to the same coordinate — but the coordinate itself now comes from the public-domain Census ZCTA internal point rather than the previous third-party table. It is a reference point inside the area, not a geometric centre. See what changed.
Confidence & freshness
Access points move, get replaced, or vanish. A point last seen two years ago is weaker evidence than one seen last week. euqlid.space exposes this directly:
first_seen/last_seen— observation window for the point.confidence(on verify) — 0…1, combining how many submitted BSSIDs matched and how tightly they cluster.spread_m/accuracy_m— the geographic scatter of matched points; low spread = high certainty.accuracy_band(on verify) —accept,softorabstain. When the service cannot produce a trustworthy fix it says so viadegradedrather than guessing.
Rule of thumb: confidence > 0.7 with spread < 100 m is a strong on-site match. Anything ZIP- or postal-derived is contextual, not conclusive.
Postal sampling v2
The v2 endpoints take a country and a postal code and return a coordinate from inside that postal area — with /v2/nearby, the access points around it as well.
The coordinate is not a geometric draw over the polygon. It is sampled uniformly over the Wi-Fi access points that have actually been observed inside the area — a density-proportional draw — and then displaced uniformly within a disc before being rounded. Two consequences follow, and both are deliberate:
- The point lands where devices actually are: in built-up parts of the area, not in a lake or an empty field. A polygon-uniform draw would weight rural APs roughly a hundredfold and land in nothing.
- The displacement is a control, not cosmetics. An undisplaced AP coordinate would make the endpoint a router-location oracle, because a follow-up nearby lookup at a one-metre radius would then return a single household's BSSID. The result is deliberately area-level.
Every response carries a provenance object naming the geometry source, vintage, licence, attribution, the sampling method, and the pool version and size that produced the draw. The same information is available unbilled and without a key at GET /v2/geo/sources.
| Endpoint | Parameters |
|---|---|
| GET /v2/geo/random-point billable |
country (required, ISO 3166-1 alpha-2 or alpha-3) · postal_code (required; formatting ignored) · seed (optional, ≤64 chars) · count 1–10, default 1 · precision 4–7 decimal places, default 5 (~1.1 m) |
| GET /v2/nearby billable |
country, postal_code, seed as above · radius_m 1–1000, default 150 · limit 1–200, default 50 · the same altitude filters as v1 (alt_m + alt_tolerance_m, or alt_min_m/alt_max_m) · enrich |
| GET /v2/geo/countries free · no auth |
None. Returns the served countries, the countries whose postal syntax is validated but not served, and the ones deliberately withheld. |
| GET /v2/geo/sources free · no auth |
None. Returns geometry sources, vintages, licences, attribution and the definition of every method value. |
/v2/nearby is a strict superset of /v1/aps/nearby: the aps array comes from the same serializer and keeps the v1 shape field for field, so an existing v1 parser reads a v2 response unchanged. v2 only adds — postal_area, provenance, and the extra fields on origin. Every v2 response, including every error, carries the header X-API-Version: v2.
curl -s "https://api.euqlid.space/v2/geo/random-point?country=US&postal_code=90210&seed=order-1042" \
-H "X-API-Key: $EUQLID_KEY"
# → 200 OK (values below are illustrative)
{
"country": "US", "postal_code": "90210", "count": 1,
"lat": 34.10214, "lon": -118.41093,
"method": "ap_pool",
"accuracy_m": 60.0,
"inside_postal_area": true,
"ap_backed": true,
"seeded": true,
"points": [ { "...": "one entry per count, each with its own method" } ],
"postal_area": {
"type": "zcta5",
"centroid_lat": 34.100517, "centroid_lon": -118.41463,
"centroid_source": "census_zcta",
"bbox": [34.06, -118.44, 34.14, -118.38],
"city": "Beverly Hills", "state": "CA"
},
"provenance": {
"geometry_source": "US Census Bureau TIGER/Line ZIP Code Tabulation Areas (2020 delineation)",
"geometry_vintage": "tiger_2025 / gazetteer_2024",
"licence": "public domain (17 U.S.C. 105)",
"sampling": "uniform over observed Wi-Fi APs inside the area (density-proportional), displaced uniformly within a disc",
"pool_version": 1, "pool_size": 4096
},
"warnings": []
}
The centroid_* pair is the published Census internal point for the area, not a sampled result. The sampled lat/lon above is illustrative — a live call returns a different coordinate.
The resolution ladder
The service walks four rungs in order and stops at the first that yields a coordinate inside the postal polygon. Every point reports the rung it came from in method, and whether that rung was backed by real access points in ap_backed. Nothing is invented to fill a gap; a degraded answer is labelled, not disguised.
| method | ap_backed | How the coordinate was produced |
|---|---|---|
| ap_pool | true | Precomputed density-proportional draw over the APs inside the area, then displaced. The normal path. |
| ap_live | true | Nearest real AP to a probe that landed inside the area, then displaced. Used when no pool exists for the area. |
| census_internal_point | false | No usable AP inside the area — the Census internal point, displaced within a disc. |
| centroid | false | The Census internal point itself, undisplaced. The terminator: it is defined to fall inside the area and on land, so the ladder always ends on a real published coordinate. |
Each drop down a rung appends a plain-language entry to warnings — no pool for the area, a live probe was used instead, the point is not AP-backed, or (after rounding) it fell outside the polygon. Read warnings. A centroid answer is a whole postal area, not a location.
accuracy_m reports the honest uncertainty for the rung that answered: the displacement radius on the AP-backed rungs, and the area's own equivalent-circle radius on the fallback rungs.
Seeds & reproducibility
Omit seed and every call returns a fresh coordinate; the response reports seeded: false. Pass a seed (up to 64 characters) and the draw becomes reproducible: the same (API key, country, postal code, pool version, seed) returns the same coordinate.
Both halves of that tuple matter and both are easy to trip over:
- Reproducibility is scoped to the API key. Rotating a key changes the coordinate for the same seed. Two keys on the same account do not agree. Do not use a seeded coordinate as a cross-key identifier.
- It is scoped to the pool version. When an area's AP pool is rebuilt, previously seeded coordinates for that area change.
provenance.pool_versiontells you which generation answered; store it alongside anything you persist. - A blank or whitespace-only seed is treated as no seed at all, and the response honestly reports
seeded: falserather than claiming a reproducibility it cannot deliver.
Seeds exist so a workflow can re-read the same answer, not so a caller can walk an area. Automating seeds to enumerate postal areas, recover access-point positions or reverse-engineer the displacement is prohibited — see acceptable use.
Coverage & known gaps
The postal endpoints serve the United States only — the 50 states, DC, and the USPS-served territories (PR, VI, GU, AS, MP), which share the same ZIP/ZCTA geometry. Everything else returns 404 unsupported_country, even where the postal syntax is recognised and validated. Query GET /v2/geo/countries for the authoritative list rather than hard-coding one.
- Geometry. 33,791 US Census TIGER/Line 2025 ZIP Code Tabulation Area polygons, public domain under 17 U.S.C. §105. Internal points come from the 2024 Census Gazetteer; all 33,791 were verified to fall inside their own polygon.
- Vintage. ZCTA geometry is frozen at the 2020 delineation and will not change until the 2030 Census. Boundaries do not track ZIP changes the Postal Service makes in between.
- ZIPs with no area. Roughly 12,723 of 42,724 USPS ZIP codes are PO BOX, UNIQUE or MILITARY types. They are routing labels with no delivery geography, so they have no ZCTA polygon at all and degrade to the
centroidrung with a warning. - Withheld.
CNreturns422 postal_geometry_unavailablewithreason: legal_restriction. Chinese postal geometry is regulated under the PRC Surveying and Mapping Law and is not distributed by this API.
A ZCTA is a Census tabulation area built from census blocks. It approximates a USPS ZIP; it is not identical to one, and neither is a municipal boundary. Do not use it as an authority on jurisdiction.
Error catalogue (v2)
Errors are JSON objects with a stable error string — branch on that, not on the message. Several carry a see field pointing at the unbilled endpoint that answers the question.
| Status | error | When |
|---|---|---|
| 404 | unsupported_country | Unknown country code, or a country whose postal geometry is not served. See /v2/geo/countries. |
| 404 | unknown_postal_code | Valid syntax, but no postal area exists with that code. |
| 422 | invalid_postal_code | Not a valid postal code for that country. The response echoes the expected pattern and an example. |
| 422 | postal_geometry_unavailable | Geometry deliberately not distributed, with a reason. Currently CN / legal_restriction. |
| 422 | conflicting_altitude_filter | Both alt_m and alt_min_m/alt_max_m were sent. Use one form. |
| 422 | invalid_altitude_filter | alt_tolerance_m without alt_m, or alt_min_m above alt_max_m. |
| 401 | — | No X-API-Key and no Authorization: Bearer header. |
| 429 | — | Per-key rate limit. Honour Retry-After. |
A failed request on a billable endpoint is still a request: build your retries around the error string, not around a loop.
Response field glossary
Fields on the AP records returned by /v1/nearby, /v1/aps/nearby, /v1/aps/lookup, /v1/aps/{bssid} and /v2/nearby.
| Field | Type | Description |
|---|---|---|
| bssid | string | Normalised AP hardware address. |
| lat, lon | float | Estimated AP location (WGS-84). |
| hacc_m / vacc_m | int | Horizontal / vertical accuracy, metres. |
| alt_m | int | Estimated altitude, metres. |
| distance_m | float | Distance from the query point (nearby queries). |
| vendor | string | Manufacturer resolved from the OUI. |
| device_class | string | Inferred hardware type (router, AP, hotspot…). |
| is_random | bool | Locally-administered / randomized address. |
| twin | object | Second observed location for this BSSID, if any. |
| first_seen / last_seen | datetime | Observation window — your freshness signal. |
| zip_code / zip_city / zip_state | string | Reverse-geocoded ZIP context. |
| phone_area_codes | string[] | Area codes covering the location. |
Envelope fields added by the v2 postal endpoints:
| Field | Type | Description |
|---|---|---|
| method | string | Which rung of the resolution ladder answered. |
| ap_backed | bool | Whether the coordinate originated from an observed access point. |
| accuracy_m | float | Uncertainty for the rung that answered — displacement radius, or the area's equivalent-circle radius. |
| jitter_m | float | Radius of the displacement disc actually applied. Null on the undisplaced centroid rung. |
| inside_postal_area | bool | Whether the returned coordinate is covered by the polygon after rounding. |
| precision | int | Decimal places kept on the coordinate (4–7). |
| seeded | bool | Whether a usable seed was supplied and the draw is reproducible. |
| postal_area | object | The area itself: type, Census internal point and its source, bbox, land/water area in km², city and state. |
| provenance | object | Geometry source, vintage, licence, attribution, sampling method, pool version/size and build time. |
| warnings | string[] | Every degradation, in plain language. Never empty by accident. |
Quickstart
Authenticate with X-API-Key (or Authorization: Bearer). Base URL https://api.euqlid.space.
# Verify observed BSSIDs (synthetic addresses shown)
curl -s https://api.euqlid.space/v1/verify \
-H "X-API-Key: $EUQLID_KEY" \
-H "Content-Type: application/json" \
-d '{"bssids":["02:00:5e:10:00:01","06:00:5e:10:00:02"]}'
# Reverse lookup by coordinate (precise) or ZIP (coarse)
curl -s "https://api.euqlid.space/v1/aps/nearby?lat=40.7321&lon=-73.9874&radius_m=50&limit=25" \
-H "X-API-Key: $EUQLID_KEY"
# v2: sample a coordinate inside a postal area, plus the APs around it
curl -s "https://api.euqlid.space/v2/nearby?country=US&postal_code=10003&radius_m=150&limit=25" \
-H "X-API-Key: $EUQLID_KEY"
# Unbilled, no key required
curl -s https://api.euqlid.space/v2/geo/countries
curl -s https://api.euqlid.space/v2/geo/sources
Recipe: a login consistency check
Collect the BSSIDs the client sees (mobile SDK / browser permission), then weigh the session:
verdict = api.post("/v1/verify", json={"bssids": observed}).json()
claimed = (user.profile_lat, user.profile_lon)
fix = (verdict["centroid_lat"], verdict["centroid_lon"])
near = haversine_m(claimed, fix) if verdict["matched"] else 1e9
if verdict["confidence"] > 0.7 and near < 500:
risk -= 30 # network environment supports the claim
elif verdict["matched"] and near > 50_000:
risk += 40 # device sees networks far from where it claims to be
Use the result as one factor among many, never as the deciding one. matched is a count, not a boolean, and centroid_lat/centroid_lon are null when nothing matched. See acceptable use.
What changed
v1 response shapes are unchanged. No field was renamed, removed or retyped. Two things did change underneath, and both can be observed:
- The ZIP coordinate moved.
origin.sourceis stillzip_centroidand the lookup is still deterministic, but the coordinate now comes from the public-domain Census ZCTA internal point instead of the previous third-party table, whose own data file attributed its geometry to a mapping provider. For90210the reference point moved from34.09, -118.41to34.100517, -118.41463— roughly 1.2 km. It is now an internal reference point rather than a geometric centre. If you cached ZIP-derived coordinates, refresh them. /v1/nearbygot much faster. A bounded-KNN rewrite took aradius_m=5000query from 23.6 s to 21 ms. Same columns, same order, same types — only rows at exactly equal distance can reorder relative to each other.
The v2 postal endpoints are additive; nothing about v1 depends on them.
Acceptable use
euqlid.space is a B2B risk and verification signal. By using the API you agree to all of the following:
- Do not use the service to identify, track, surveil, contact, or infer the home or current location of a person or household, or to link a Wi-Fi identifier to an identifiable individual.
- Do not use service outputs to misrepresent physical presence, evade geographic, payment, account-access or security controls, or facilitate fraud or unauthorised access.
- Do not automate seeds or requests to enumerate postal areas, recover precise access-point locations, reverse-engineer displacement, or reconstruct service coverage.
- Do not scrape, mirror, bulk-export, sublicense, resell or republish the underlying Wi-Fi or postal datasets except under a separate written agreement.
- Do not treat a sampled coordinate or a nearby-network result as sole evidence of identity, residence, physical presence, eligibility, or any other high-impact decision.
- Use the service only with a lawful basis and in compliance with applicable privacy, telecommunications and data-protection law.
Violations may result in throttling, suspension or key revocation.
Privacy & opt-out
Access-point owners can request suppression. Networks whose SSID ends in _nomap are excluded by upstream providers and are not collected.
To remove a specific BSSID or report misuse, email privacy@euqlid.space with the address; verified requests are suppressed promptly.
Postal boundaries and internal points are U.S. Census Bureau TIGER/Line and Gazetteer files, public domain under 17 U.S.C. §105. Attribution and vintages are published unbilled at /v2/geo/sources.
euqlid.space is not affiliated with, endorsed by, or connected to Apple Inc., the U.S. Census Bureau, the United States Postal Service, or any network operator.