Verified coverage API
FreqMapper can give any approved third-party application a separately labelled, read-only feed of verified wardrive coverage. This is a binary feed: every returned event is confirmed coverage. FreqMapper does not return partial, weak, failed, or not-heard events through this integration.
What counts as verified
An event appears only after an approved independent Watcher receives the exact tagged packet sent during a FreqMapper wardrive. The phone's mapping-test position is the coverage location.
The feed deliberately does not include:
- ordinary Meshtastic or MQTT Position Packets;
- a wardriver's route or background GPS samples;
- Watcher identities or locations;
- RSSI, SNR, signal grades, or receipt counts;
- failed, pending, simulated, or not-heard mapping tests.
Software-simulated phone locations and disqualified trips never appear, even if a packet receipt exists.
Use your API key
The API is currently available from the development site at:
https://dev.freqmapper.net:8443
Do not use https://freqmapper.net for API requests yet. The production site and API are not live.
FreqMapper will provide the application with a bearer key. Store the key as a server-side secret and send it only in the HTTPS Authorization header:
Authorization: Bearer fm_live_REPLACE_WITH_THE_ISSUED_KEY
Keys are never accepted in a query string.
The receiving application should keep the key in its server-side secret or environment configuration. Do not embed it in browser JavaScript, a mobile application, screenshots, public configuration, source control, or support logs.
Make a request
The application makes an HTTPS GET request to:
/api/v1/integrations/verified-coverage
Every request must contain its bearer key:
GET /api/v1/integrations/verified-coverage?limit=500 HTTP/1.1
Host: dev.freqmapper.net:8443
Authorization: Bearer fm_live_REPLACE_WITH_THE_ISSUED_KEY
Accept: application/json
An absent, expired, or revoked key returns 401. An unknown or inactive region code returns 404; an existing active region outside the key's assigned scope returns 403. An invalid cursor or parameter returns 422. Only an allowed, active region with no matching events returns an empty 200 response.
Check both the HTTP status and the JSON response. A successful feed response has Content-Type: application/json, schema_version: 1, and an events array. HTML is not an API response, even if its status is 200. The production and documentation-only hostnames return JSON 404 for API paths until they support those routes.
Read the feed
curl --fail-with-body \
--header "Authorization: Bearer $FREQMAPPER_PARTNER_TOKEN" \
"https://dev.freqmapper.net:8443/api/v1/integrations/verified-coverage?limit=500"
An optional region_iata=SLC filter is allowed only when the key includes that region. limit may be 1–1000 and defaults to 500.
{
"schema_version": 1,
"generated_at": "2026-09-01T19:30:00Z",
"coverage_rule": "independent_watcher_verified",
"has_more": false,
"next_cursor": "opaque-value",
"events": [
{
"verification_id": "a6f33a9a-d94b-46a1-9285-7b121aa6e528",
"verified_at": "2026-09-01T19:28:14Z",
"mapping_test_sent_at": "2026-09-01T19:27:55Z",
"latitude": 40.76078,
"longitude": -111.89105,
"region_iata": "SLC",
"radio_node_id": "!a1b2c3d4",
"verified": true
}
]
}
verification_id is stable and should be the partner's deduplication key. radio_node_id identifies the wardriver's enrolled Meshtastic radio so a game can credit the correct player. It is not a Watcher or MQTT gateway ID.
Coordinates, not map cells
Each event is evidence at the reported mapping-test coordinate, not a claim that an entire cell has coverage. Apply the latitude and longitude to your application's own grid. This feed intentionally does not include FreqMapper map-cell indices or polygons; do not depend on the browser map endpoint as a substitute for this integration contract.
FreqMapper's detailed coverage display uses approximately 100 m across-flats hexes; its overview uses approximately 300 m hexes. Blue MQTT position activity uses a separate 500 m reference grid and is not included in this feed. These are display grids, not equal-area measurements: their fixed angular lattice is calibrated near latitude 40 degrees, so physical widths vary with latitude. None of these display settings changes the coordinates returned by the API.
Cursor polling
The first request without a cursor reads existing verified history from oldest to newest. Process the events, save next_cursor, and request the next page:
https://dev.freqmapper.net:8443/api/v1/integrations/verified-coverage?cursor=THE_SAVED_CURSOR&limit=500
Continue while has_more is true, respecting the request budget below. When it becomes false, retain the returned cursor and use it on later polls. An empty page still returns a usable cursor. Polling every 30–60 seconds is normally enough for a live map.
Never derive a negative result from an empty response. It means only that no newly verified events exist after that cursor.
Request limits and retries
Keys default to 120 requests per 60-second window, but your key may have a different assigned limit. Read X-RateLimit-Limit rather than hard-coding 120. The budget is shared across the key's regions and all processes using that key. The window starts with the first request after the previous window has expired. Bursts are allowed within that budget, including paginated history downloads. Each authenticated request consumes budget, including requests with invalid filters or parameters. Use one sequential pagination loop and up to limit=1000 events per request for large imports.
Successful responses and 429 responses include:
| Header | Meaning |
|---|---|
X-RateLimit-Limit |
This key's current maximum requests per window (default 120). |
X-RateLimit-Remaining |
Remaining requests in the current window. |
X-RateLimit-Reset |
Window expiry as a Unix timestamp in seconds. |
If the budget is exhausted, the API returns JSON 429 Too Many Requests with Retry-After containing the number of seconds to wait. Wait at least that long, then retry the same URL and cursor. Do not reset or advance your saved cursor on an error. Rejected requests do not extend the window.
For network failures or temporary 5xx errors, retry the same request with increasing delays (for example 5, 10, 20, then 60 seconds). Save a new cursor only after successfully processing its page, and deduplicate replayed events by verification_id. Do not repeatedly retry 401, 403, 404, or 422 without correcting the key, host, region, or request.
When reporting a problem, include the response's X-Request-ID header if present, the time, and HTTP status. Never include your bearer key. Requests rejected before reaching the API may not include a request ID.
Example: MeshWars
MeshWars can reduce each returned event to: “verified coverage exists at this mapping-test coordinate.” It can paint the corresponding game square green and use verification_id to avoid adding it twice. It must not infer signal quality, continuous road coverage, or a red square from this feed.