Skip to content

Partner coverage API

FreqMapper can give an approved third-party application a read-only combined coverage stream or the original type-specific feeds. Events always remain explicitly labelled so verified TX proof and passive RX evidence cannot be confused:

Feed Meaning
/api/v1/integrations/coverage-events Preferred. One cursor-paginated stream containing both event types. Every event has event_type: verified_tx or event_type: passive_rx.
/api/v1/integrations/verified-coverage An approved independent Watcher received a tagged FreqMapper mapping test. Every event is binary verified TX coverage.
/api/v1/integrations/received-coverage The connected wardriving radio heard an eligible Meshtastic packet at the reported driving location. Signal quality and privacy-safe Watcher corroboration are included, but this is never verified TX coverage.

The same bearer key and assigned region scope apply to all three feeds. A combined-feed cursor belongs only to that feed. Applications using the two older feeds must continue to save a separate cursor for each.

Use your API key

The production API is available at:

https://freqmapper.net

Use this production hostname for all partner API requests.

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.

Preferred combined feed

Use this endpoint when one application needs both TX and RX evidence:

curl --fail-with-body \
  --header "Authorization: Bearer $FREQMAPPER_PARTNER_TOKEN" \
  "https://freqmapper.net/api/v1/integrations/coverage-events?limit=500"

The envelope has one opaque next_cursor. Events are ordered by published_at, then their type and source UUID. Save the cursor only after processing the complete page.

Every event contains these common fields:

Field Meaning
event_id Globally discriminated deduplication key, prefixed with verified_tx: or passive_rx:.
event_type verified_tx or passive_rx. Branch on this before reading type-specific fields.
published_at Server ordering time used by the opaque cursor. Do not use it as the RF event time.
occurred_at Mapping-test send time for TX or driving-radio reception time for RX.
latitude, longitude The wardriving phone/radio location where the TX test was sent or RX packet was heard.
region_iata, radio_node_id FreqMapper region and enrolled wardriving radio.
watcher_count Distinct approved Watchers associated with this evidence.
same_region_watcher_count, cross_region_watcher_count Privacy-safe regional split. Verified TX currently requires same-region Watchers, so its cross-region count is zero.

A response can contain both shapes:

{
  "schema_version": 1,
  "generated_at": "2026-09-08T20:00:00Z",
  "feed": "combined_coverage_events",
  "event_types": ["verified_tx", "passive_rx"],
  "rx_comparison_window_seconds": 30,
  "rx_publication_delay_seconds": 60,
  "has_more": false,
  "next_cursor": "opaque-combined-value",
  "events": [
    {
      "event_id": "verified_tx:a6f33a9a-d94b-46a1-9285-7b121aa6e528",
      "event_type": "verified_tx",
      "published_at": "2026-09-08T19:58:14Z",
      "occurred_at": "2026-09-08T19:57:55Z",
      "latitude": 40.76078,
      "longitude": -111.89105,
      "region_iata": "SLC",
      "radio_node_id": "!a1b2c3d4",
      "watcher_count": 2,
      "same_region_watcher_count": 2,
      "cross_region_watcher_count": 0,
      "verification_id": "a6f33a9a-d94b-46a1-9285-7b121aa6e528",
      "verified_at": "2026-09-08T19:58:14Z",
      "mapping_test_sent_at": "2026-09-08T19:57:55Z",
      "watcher_corroborated": true,
      "evidence_type": "independent_watcher_verified",
      "verified": true,
      "verified_coverage": true
    },
    {
      "event_id": "passive_rx:f1df7296-12a0-4c32-a85b-262b6dd44bcb",
      "event_type": "passive_rx",
      "published_at": "2026-09-08T19:59:14Z",
      "occurred_at": "2026-09-08T19:58:14Z",
      "latitude": 40.76079,
      "longitude": -111.89106,
      "region_iata": "SLC",
      "radio_node_id": "!a1b2c3d4",
      "watcher_count": 3,
      "same_region_watcher_count": 2,
      "cross_region_watcher_count": 1,
      "reception_id": "f1df7296-12a0-4c32-a85b-262b6dd44bcb",
      "received_at": "2026-09-08T19:58:14Z",
      "location_at": "2026-09-08T19:58:13Z",
      "location_accuracy_meters": 7.2,
      "location_trust": "platform_unflagged",
      "packet_type": "position",
      "portnum": 3,
      "rssi_dbm": -96.4,
      "snr_db": 7.25,
      "quality": "strong",
      "hop_count": 0,
      "path_classification": "direct",
      "last_relay_node": null,
      "watcher_corroborated": true,
      "evidence_type": "device_receive",
      "verified_coverage": false
    }
  ]
}

RX publication waits 60 seconds from server acceptance. This covers the 30-second comparison window and allows normal Watcher delivery time. The cursor uses server acceptance rather than received_at, so a phone uploading an older offline reception does not place that event behind an already saved cursor.

Consumer processing

An integration can consume both evidence types with one polling loop and one combined cursor:

  1. Deduplicate using event_id.
  2. Apply the configured date gate to occurred_at.
  3. Associate radio_node_id with the consumer's existing radio record, if needed.
  4. Validate and apply latitude and longitude to the consumer's own map or grid.
  5. Branch on event_type. Treat verified_tx as verified mapping-test coverage. Treat passive_rx as traffic heard by the wardriving radio, with optional confidence from the Watcher counts and quality.

Both event types may appear on the same map, but passive RX must not be relabelled as verified TX. A practical display priority is verified TX over passive RX, with passive RX allowed to fill an untested or negative area.

Verified TX coverage

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 individual Watcher measurements;
  • failed, pending, simulated, or not-heard mapping tests.

Software-simulated phone locations and disqualified trips never appear, even if a packet receipt exists.

Make a verified TX 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: freqmapper.net
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 verified TX feed

curl --fail-with-body \
  --header "Authorization: Bearer $FREQMAPPER_PARTNER_TOKEN" \
  "https://freqmapper.net/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",
      "watcher_count": 2,
      "verified": true
    }
  ]
}

verification_id is stable and should be the partner's deduplication key when using this TX-only feed. radio_node_id identifies the wardriver's enrolled Meshtastic radio so an integration can associate the evidence with its own radio record. It is not a Watcher or MQTT gateway ID. watcher_count is the number of distinct approved Watchers that verified this mapping test; identities and individual measurements remain private.

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 cells; its overview uses approximately 300 m cells. Position Packet coverage remains a separate evidence source and is not included in this controlled-test feed. These coverage cells are display grids, not equal-area measurements: their fixed angular lattice is calibrated near latitude 40 degrees, so physical widths vary with latitude. These display choices do not change the coordinates returned by the API.

Passive RX coverage

The passive RX feed returns accepted Received here events from an active FreqMapper wardrive. The coordinates are where the driving phone/radio heard the packet, not the sender's reported position and not a Watcher's location.

FreqMapper accepts only eligible text, position, node-info, or telemetry packets heard on any configured channel the driving radio can decode, with a fresh, accurate, non-simulated phone location and plausible radio metadata. Mapping-test packets, self echoes, MQTT-path reports, unsupported packet types, stale locations, and disqualified trips are excluded.

Make an authenticated request with the same key:

curl --fail-with-body \
  --header "Authorization: Bearer $FREQMAPPER_PARTNER_TOKEN" \
  "https://freqmapper.net/api/v1/integrations/received-coverage?limit=500"

The optional region_iata and limit parameters behave exactly as they do on the verified TX feed.

{
  "schema_version": 1,
  "generated_at": "2026-09-07T19:30:00Z",
  "coverage_rule": "driving_radio_received",
  "watcher_comparison_rule": "same_sender_and_packet_id_within_30_seconds",
  "comparison_window_seconds": 30,
  "has_more": false,
  "next_cursor": "opaque-rx-value",
  "events": [
    {
      "reception_id": "f1df7296-12a0-4c32-a85b-262b6dd44bcb",
      "received_at": "2026-09-07T19:28:14Z",
      "location_at": "2026-09-07T19:28:13Z",
      "location_accuracy_meters": 7.2,
      "location_trust": "platform_unflagged",
      "latitude": 40.76078,
      "longitude": -111.89105,
      "region_iata": "SLC",
      "radio_node_id": "!a1b2c3d4",
      "packet_type": "position",
      "portnum": 3,
      "rssi_dbm": -96.4,
      "snr_db": 7.25,
      "quality": "strong",
      "hop_count": 0,
      "path_classification": "direct",
      "last_relay_node": null,
      "watcher_corroborated": true,
      "watcher_count": 3,
      "same_region_watcher_count": 2,
      "cross_region_watcher_count": 1,
      "evidence_type": "device_receive",
      "verified_coverage": false
    }
  ]
}

RX field meanings

Field Meaning
reception_id Stable deduplication ID for this accepted reception. It does not expose the trip or installation ID.
received_at Time the connected driving radio reported receiving the packet.
location_at Time of the phone location fix paired with the reception.
location_accuracy_meters Reported phone horizontal accuracy. Accepted values are no greater than 50 m.
location_trust Always platform_unflagged; software-simulated locations are rejected before storage.
latitude, longitude Driving radio's reception location. These are not the sender's coordinates.
radio_node_id Enrolled Meshtastic node used by the wardriver, suitable for associating evidence with the mapper.
packet_type, portnum Eligible Meshtastic application type: text/1, position/3, node_info/4, or telemetry/67. Packet contents are never returned.
rssi_dbm, snr_db Signal measured by the driving radio. snr_db can be null. These are not Watcher measurements.
quality strong, fair, or weak, calculated from the weaker available RSSI/SNR band. It describes the received signal, not delivery probability.
hop_count Header-derived hop estimate when available; otherwise null.
path_classification direct, relayed, or unknown, derived only from hop_count. It is not a complete route.
last_relay_node Optional one-byte last-relay header hint. It is not a full node identity and can be null.
watcher_corroborated Whether at least one distinct approved Watcher reported the same sender/packet identity within the comparison window.
watcher_count Total distinct corroborating approved Watchers.
same_region_watcher_count Corroborating Watchers assigned to the reception's region.
cross_region_watcher_count Corroborating Watchers assigned only to another active region. This can help sparse regions without changing the RX meaning.
verified_coverage Always false. RX evidence must never be mixed into the verified TX feed or treated as independent mapping-test proof.

The comparison says the driving radio and a Watcher reported the same packet identity near the same time. It does not prove a common RF path, the driver's distance from a Watcher, the sender's position, or that the Watcher heard the driving radio. Zero Watcher matches does not invalidate the reception; it means only that the RX event was not corroborated by an approved Watcher.

The partner feed deliberately omits sender IDs, packet IDs, message text or payloads, Watcher identities and measurements, trip and installation IDs, routes, channel keys, and raw packet bytes. Those identifiers may be used privately for matching but are not returned.

Cursor polling

For the preferred combined stream, save and return only its combined cursor:

https://freqmapper.net/api/v1/integrations/coverage-events?cursor=THE_SAVED_COMBINED_CURSOR&limit=500

The first request without a cursor reads the selected feed's existing history from oldest to newest. Process the events, save that feed's next_cursor, and request its next page:

https://freqmapper.net/api/v1/integrations/verified-coverage?cursor=THE_SAVED_CURSOR&limit=500

For passive RX, use its separately saved cursor:

https://freqmapper.net/api/v1/integrations/received-coverage?cursor=THE_SAVED_RX_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 new events exist in that selected feed after its 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. Deduplicate verified TX with verification_id and passive RX with reception_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.

Type-specific feed compatibility

Older integrations can consume both type-specific feeds without confusing their evidence:

  • A verified TX event means “independent Watcher-verified mapping-test coverage exists at this coordinate.” It can use verification_id for deduplication and apply its own display or processing rules.
  • A passive RX event means “this enrolled driving radio heard eligible mesh traffic here with this measured signal.” It can use reception_id for deduplication and the quality/corroboration fields for a separate RX map or mechanic.

Passive RX must not be promoted to verified TX, even when several Watchers corroborate it. Neither feed supports inferring continuous road coverage or a failed/red result from the absence of events.

New integrations that need both evidence types should normally use the combined endpoint above instead.

Last updated: 2026-09-08