# Network · Traffic map accuracy > How to get city/state-level truth on the /admin/traffic map — Cloudflare location headers, the places contract extension, and what the map does and does not claim today. --- .. toc:: ## What the map claims today (be honest with yourself first) Two very different data sources share one map: - **Hub visits (2plot.ai)** plot at **real coordinates** — ip-api.com returns lat/lon + city + region per public IP, and markers group into ~10 km cells. City-level, genuinely useful. - **Satellite reports** only carry `countries: {CC: hits}` — so their markers sit on **country centroids**. Every US visitor to dash-email becomes one marker in Kansas. The per-app ring offset around each centroid is *presentation* (so overlapping apps stay hoverable), not accuracy — the tooltip still names only the country. That is the ceiling of the current satellite contract, and why the map clusters at country centers. Raising the ceiling means satellites must *capture* finer location, then *report* it. ## Step 1 — turn on Cloudflare visitor-location headers (free) For every zone proxied through Cloudflare (2plot.media, 2plot.xyz, 2plot.dev, …): **Dashboard → your zone → Rules → Settings → Managed Transforms → enable "Add visitor location headers".** Available on all plans, including Free. Every request then arrives with: ``` CF-IPCity: Denver CF-IPLatitude: 39.739 CF-Region: Colorado CF-IPLongitude: -104.984 CF-Region-Code: CO CF-IPCountry: US CF-IPContinent / CF-Timezone / CF-PostalCode also available ``` No API calls, no latency, no rate limits — the accuracy problem becomes a header read. **The headers only appear on proxied (orange-cloud) records**, and proxying is decided **per DNS record, not per zone** — which matters for this network: - **Clerk's records must stay DNS-only** (`clerk.`, `accounts.`, and the email DKIM CNAMEs) — proxying those breaks Clerk's TLS provisioning. But those are Clerk's own subdomains; the app host record (`2plot.media`, `www`, …) is a separate record and CAN be proxied without touching them. In this network only the hub carries Clerk DNS anyway — satellites use the redirect model (sign-in happens on 2plot.ai), so their zones have no Clerk records at all. - **Render behind the Cloudflare proxy works** with SSL/TLS mode set to *Full (Strict)* on the zone. Test the sign-in round trip after flipping any record. **If you'd rather not proxy anything, that's fine too** — the map does not depend on Cloudflare. The hub already gets city-level lat/lon from ip-api.com per public IP (free tier ~45 lookups/min over plain HTTP: cache per IP, resolve outside the request path), and that is what powers the map's "Precise" layer today. Cloudflare headers are the zero-cost upgrade path, not a requirement; without them, satellites simply stay on the country-centroid layer. ## Step 2 — the `places` contract extension (v2.1) Alongside `countries`, satellites report a capped list of place rollups: ```json "places": [ {"cc": "US", "region": "CO", "city": "Denver", "lat": 39.7, "lon": -105.0, "hits": 41}, {"cc": "DE", "region": "BE", "city": "Berlin", "lat": 52.5, "lon": 13.4, "hits": 9} ] ``` Rules that keep it truthful and private: - **Round lat/lon to one decimal (~10 km)** — enough to see a metro, impossible to see a house. Never ship raw coordinates. - Cap at ~30 places, most-visited first; everything below the cap stays country-level in `countries` (which remains the complete count). - `region`/`city` optional — send what you have. A place with only `cc` + rounded lat/lon still beats a centroid. - Humans only, same as every other v2 field. Hub-side this rides the same accumulate-across-restarts reader as pages/countries; the map prefers a report's `places` and falls back to centroid-`countries` per app. (Reader + map support is the next hub change; satellites can start sending `places` first — unknown fields are ignored, nothing breaks.) ## Step 3 — reading the map like a marketing tool - **Visits mode** answers "where is my audience" — size ads/regions by marker area, which scales with √hits so big markers don't lie. - **Session-length mode** answers "where is my *engaged* audience" — a small marker with long sessions is a better ad target than a big marker of bounces. Cross-check both before spending. - Satellite session-length markers all use the app's overall median (that's the reported granularity); only hub markers have per-place session medians until `places` ships with per-place durations. - Bot filtering happens before any of this, but scrapers with browser UAs still slip through — a "city" showing hundreds of hits, seconds of session, from one visitor is a crawler, not a market. ## Current limitations, stated plainly | Limitation | Cause | Fix | |---|---|---| | Satellite traffic clusters at country centers | contract sends countries only | Steps 1–2 | | Ring offsets around centroids | presentation, not data | tooltip names the country; ignore the spread | | Hub geo misses some visitors | private/CGNAT IPs, ip-api misses | unavoidable; counts stay truthful, only the map thins | | One app median for satellite session markers | reported granularity | per-place durations in a later `places` rev | --- *Source: /docs/traffic-map*