Coverage API¶
The Coverage API provides programmatic access to MeshMapper coverage grid-square data. Use it to draw coverage grids on your own maps or integrate MeshMapper data into external tools and dashboards.
Authentication¶
Access requires a Coverage API key. Each key is scoped to a specific region or multiregion group and has a daily rate limit of 100 requests.
Generating a Key¶
Regional administrators can generate their own API key directly from the admin panel:
- Log in to your region's admin panel
- Go to User Settings
- Scroll to the API Access section
- Enter a description/reason for the key (mandatory)
- Click Generate API Key
Each administrator is limited to one API key per region. The key is automatically scoped to your region with a fixed rate limit of 100 requests per day. If you need to replace your key, use the Regenerate button — this invalidates the old key immediately.
Unauthorized Access
MeshMapper utilizes API keys and rate limits to protect server resources and prevent access to data that regions do not wish to have shared externally. As such, accessing unauthorized API's, scraping for data, etc., is strictly prohibited and will result in action taken to protect the server and data (which may include IP or origin bans, removal of a region, etc.). The MeshMapper team is happy to review requests for data not provided in the API's below.
Endpoint¶
GET https://meshmapper.net/coverage.php?key=YOUR_API_KEY
Query Parameters¶
| Parameter | Required | Description |
|---|---|---|
key |
Yes | Your Coverage API key. |
include |
No | Comma-separated list of optional sections to add to the response. Currently supports repeaters (e.g. ?include=repeaters) — see Repeater Fields. |
Response Format¶
{
"success": true,
"region": "[IATA]",
"region_name": "Ottawa, CA",
"grid_size": { "lat": 0.0027, "lon": 0.00384 },
"schema_version": 2,
"generated_at": 1710547200,
"data_age_seconds": 312,
"total_squares": 1234,
"point_count": 48210,
"coverage_type_counts": { "BIDIR": 540, "TX": 60, "RX": 410, "DISC": 90, "DEAD": 12, "DROP": 122 },
"type_bits": { "BIDIR": 1, "TX": 2, "RX": 4, "DISC": 8, "DEAD": 16, "DROP": 32 },
"bbox": { "minLat": 45.108, "minLon": -76.351, "maxLat": 45.621, "maxLon": -75.299 },
"grid_squares": [
{
"grid_id": "16816_-19718",
"bounds": {
"south": 45.4032,
"west": -75.7177,
"north": 45.4059,
"east": -75.7138
},
"coverage_type": "BIDIR",
"fill_color": "#1e7e34",
"border_color": "#14522d",
"snr": 8.5,
"timestamp": 1710547200,
"count": 14,
"snr_min": -2.1,
"snr_max": 11.3,
"status_mask": 37,
"first_seen": 1710201600,
"noise": 17.4,
"effective": 2.43
}
]
}
Backward compatibility
The fields documented here are additive — every field present in earlier versions of the API (grid_id, bounds, coverage_type, fill_color, border_color, snr, timestamp, and the original top-level fields) is unchanged. Existing integrations continue to work without modification; simply ignore any fields you do not need.
Field Reference¶
Top-Level Fields¶
| Field | Type | Description |
|---|---|---|
success |
boolean | true if the request succeeded. |
region |
string | The region or group code this key is scoped to. |
region_name |
string | Human-readable name of the region (falls back to the code for groups). |
grid_size |
object | Grid square dimensions in degrees (lat and lon). |
schema_version |
integer | Response schema version. New fields are added without breaking the existing contract. |
generated_at |
integer | Unix timestamp of when this response was built (see Caching). |
data_age_seconds |
integer or null | Seconds between the most recent ping in the dataset and when the response was built — a freshness indicator. null if the region has no data. |
total_squares |
integer | Number of grid squares returned. |
point_count |
integer | Total number of pings aggregated across all grid squares. |
coverage_type_counts |
object | Number of grid squares per dominant coverage_type. |
type_bits |
object | Legend mapping each coverage type to the bit value used in a square's status_mask. |
bbox |
object or null | Bounding box covering all returned squares (minLat, minLon, maxLat, maxLon). null if empty. |
grid_squares |
array | Array of grid square objects (see below). |
repeaters |
array | Only present when ?include=repeaters is set — see Repeater Fields. |
Grid Square Fields¶
| Field | Type | Description |
|---|---|---|
grid_id |
string | Unique identifier for the grid cell in "latIndex_lonIndex" format. |
bounds |
object | Bounding box with south, west, north, east in decimal degrees. |
coverage_type |
string | Dominant type for the cell. One of: BIDIR, TX, RX, DISC, DEAD, DROP. |
fill_color |
string | Hex fill colour matching MeshMapper's map rendering. |
border_color |
string | Hex border colour matching MeshMapper's map rendering. |
snr |
float or null | Average signal-to-noise ratio (dB) across the cell's pings, if available. |
timestamp |
integer or null | Unix timestamp of the dominant (most recent, highest-priority) ping colouring this square. |
count |
integer | Number of pings aggregated into this cell — a confidence/density indicator. |
snr_min |
float or null | Lowest SNR (dB) among the cell's pings. |
snr_max |
float or null | Highest SNR (dB) among the cell's pings. |
status_mask |
integer | Bitmask of all coverage types present in the cell (OR of type_bits). See Cell Quality and Status Mask. |
first_seen |
integer or null | Unix timestamp of the oldest ping in the cell (timestamp is the newest/dominant). |
noise |
float or null | Average noise level (dB above the receiver's noise floor) across the cell's pings. |
effective |
float | Average coverage-quality score for the cell, 0–3 (see below). |
Repeater Fields¶
Returned in the top-level repeaters array only when the request includes ?include=repeaters. Each entry describes a repeater known to the region:
| Field | Type | Description |
|---|---|---|
hex |
string | Repeater hex ID (prefix). |
name |
string or null | Repeater name. |
lat |
float or null | Latitude. |
lon |
float or null | Longitude. |
last_heard |
integer or null | Unix timestamp the repeater was last heard. |
enabled |
integer | 1 = active, 2 = flagged for an ID collision (still listed). |
advert_bytes |
integer or null | Advertised path-ID width in bytes. |
Coverage Types¶
Each grid square's coverage_type is the dominant ping in that square — when multiple pings exist, the highest-priority type wins.
| Type | Colour | Priority | Description |
|---|---|---|---|
BIDIR |
Green (#1e7e34) |
6 | Two-way confirmed link. |
DISC |
Cyan (#17a2b8) |
5 | Discovery or trace packet. |
TX |
Orange (#fd7e14) |
4 | Transmitted but not heard back. |
RX |
Purple (#6f42c1) |
3 | Heard traffic, without transmitting. |
DEAD |
Grey (#6c757d) |
2 | Repeater heard but no route. |
DROP |
Red (#bd2130) |
1 | No connection. |
Cell Quality and Status Mask¶
coverage_type reflects only the single dominant ping in a square. The effective and status_mask fields summarise the whole mix of pings in the cell.
effective — average quality (0–3)¶
effective is the mean, over every ping in the cell, of a per-ping quality score:
| Ping type | Score |
|---|---|
BIDIR |
3 |
TX, RX, DISC |
2 |
DEAD |
1 |
DROP |
0 |
A cell that is entirely BIDIR scores 3.0; a cell that is mostly BIDIR with some failed pings scores lower. This makes effective ideal for a smooth red→green quality gradient, where coverage_type alone would only show the single best ping.
status_mask — which types are present¶
status_mask is a bitwise-OR of the type_bits for every type that appears anywhere in the cell. Use the top-level type_bits legend to decode it:
BIDIR=1 TX=2 RX=4 DISC=8 DEAD=16 DROP=32
status_mask = 37 → 1 (BIDIR) + 4 (RX) + 32 (DROP)
i.e. the cell contains BIDIR, RX, and DROP pings.
Drawing Grid Squares¶
Each grid square can be reconstructed as a rectangle using the bounds object:
// Leaflet.js example
data.grid_squares.forEach(sq => {
L.rectangle(
[[sq.bounds.south, sq.bounds.west], [sq.bounds.north, sq.bounds.east]],
{
fillColor: sq.fill_color,
color: sq.border_color,
fillOpacity: 0.6,
weight: 1
}
).addTo(map);
});
The grid uses fixed cell sizes of 0.0027 degrees latitude by 0.00384 degrees longitude (approximately 300m squares). These match MeshMapper's Simplified Mode rendering.
HTTP Caching and Compression¶
The API is built for efficient, low-frequency polling. Coverage data does not change second-to-second, so please poll sparingly.
- Compression. Responses are gzip-compressed. Send
Accept-Encoding: gzip(most HTTP clients do this automatically) to receive compressed data — payloads are roughly 9× smaller. - Server-side cache. Responses carry
Cache-Control: public, max-age=900and are cached for up to 15 minutes. Polling more often than that returns identical data (and still counts toward your daily limit), so a poll interval of 15 minutes or longer is recommended.generated_attells you when the cached data was built. - Conditional requests. Each response includes an
ETag(andLast-Modified). Send theETagvalue back in anIf-None-Matchheader; if nothing has changed since, you'll get a304 Not Modifiedwith an empty body, saving you the download.
# First request — note the ETag header
curl -s --compressed -D - "https://meshmapper.net/coverage.php?key=YOUR_API_KEY" -o coverage.json
# Later — only download if the data changed
curl -s --compressed -H 'If-None-Match: "THE_ETAG_VALUE"' \
"https://meshmapper.net/coverage.php?key=YOUR_API_KEY"
Rate Limits¶
Each API key has a daily request limit (100 requests). Counters reset daily. Every request that reaches your data — including cache hits and 304 Not Modified responses — counts toward this limit.
When you exceed your limit, the API returns HTTP 429:
{
"success": false,
"error": "rate_limit_exceeded",
"message": "Daily request limit reached",
"limit": 100,
"used": 100,
"resets_in_hours": 12.5
}
A separate short-term, per-IP throttle protects against bursts; exceeding it also returns HTTP 429 (with error: rate_limited). Spacing requests out (see Caching) avoids both.
Error Responses¶
| HTTP Code | Error | Description |
|---|---|---|
| 400 | missing_key |
No API key provided. |
| 400 | invalid_region |
Region code on key not found. |
| 401 | invalid_key |
API key not found. |
| 403 | no_region |
No region assigned to this key. |
| 429 | rate_limit_exceeded |
Daily request limit reached. |
| 429 | rate_limited |
Too many requests in a short period (per-IP throttle). |
| 500 | server_error |
Internal error (database not found, etc.). |
Managing Your Key¶
If you have a Coverage API key assigned to your admin account, you can view your current usage and regenerate your key from the User Settings tab in your region's Admin Portal. Regenerating a key invalidates the old one immediately.