Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions docs/api-keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# API keys and access

MeshMapper uses the same **Coverage API key** for Coverage and the read APIs below. Existing keys keep their values, geographic assignments and Coverage quota. There is no need to regenerate a key for the new endpoints.

!!! info "Server v1.5.117 rollout"
Zones, boundaries, scopes and channels require keys when the updated server endpoints are deployed. Send your key now as part of the migration. Repeater authentication has a separate switch, off by default until app 1.4.1 is shipped and the update is forced. This page describes the new contract, not a confirmation that every region has deployed it.

## Endpoints and key types

| API | Credential | Geographic access |
| --- | --- | --- |
| [Coverage](coverage-api.md), `coverage.php` | Coverage key in `X-API-Key` or legacy `?key=` | Assigned region, group, selected regions or GLOBAL |
| [Region directory](zones-api.md), `get_zones.php` | Coverage key in `X-API-Key` | Authorized regions within the requested country |
| [Boundaries](zones-api.md#region-boundary), `get_geojson.php` | Coverage key in `X-API-Key` | Requested region or fully authorized group |
| [Scopes](scopes-api.md), `get_scopes.php` | Coverage key in `X-API-Key` | Requested region or fully authorized group |
| [Channels](channels-api.md), `get_channels.php` | Coverage key in `X-API-Key` | Requested region or fully authorized group |
| Repeaters, `get_repeaters.php` | Scoped Coverage key or existing mobile App key in `X-API-Key`; optional during transition | Coverage keys stay within their assignments; App keys support the app's cross-region repeater reads |

Only Coverage accepts a query-string key. App keys do not grant access to Coverage, zones, boundaries, scopes or channels. The mobile app can send its existing App key for repeaters before connecting a radio or starting a session.

## Generate or reuse a key

In your region or group's admin panel, open **User Settings > API Access**. Generate a key with a description, or show your existing key. Each administrator can have one self-service key per region or group. Regeneration invalidates the old value immediately.

Group keys cover the group's current enabled member IATAs, including membership changes without issuing a new key. A key for just one member cannot fetch a whole group's boundaries, scopes or channels unless it covers every enabled member. Repeater exports are filtered to the Coverage key's authorized members, even where a member URL normally expands to its group.

Contact the MeshMapper team for custom integrations, selected-region or GLOBAL access, endpoint permissions, or larger limits. Master admins can allow only the endpoints an integration needs. GLOBAL permits geographic access across regions; it does not remove required parameters or turn every endpoint into an all-region feed. For example, `get_zones.php` still requires `country`.

## Send the key

Keep integration keys in your backend configuration, outside source control and visitor-facing JavaScript. For example, with `MESHMAPPER_API_KEY` set in your environment:

```bash
curl --fail-with-body --compressed \
-H "X-API-Key: $MESHMAPPER_API_KEY" \
'https://yow.meshmapper.net/get_scopes.php'
```

Send the header on every request, including conditional requests. CORS preflights allow `X-API-Key` and `If-None-Match`.

## Limits and caching

The five read APIs share a default budget of **1,000 admitted requests per UTC day and 30 per fixed minute per Coverage key**. Custom keys may have different limits. Coverage retains its separate daily quota, normally 100 calls for self-service keys. Read calls do not consume Coverage calls.

Every admitted read counts, including a `304` and a downstream data error. Authentication and authorization failures do not consume the read budget. `OPTIONS` preflights do not count. Existing IP burst protection also applies; App repeater reads do not put all phones behind one shared daily key budget.

Authenticated reads replace the previous once-per-target IP interval. Key quota exhaustion returns `503 key_rate_limited` with `Retry-After`; wait at least that long before retrying. The daily allowance resets at midnight UTC. A zones/boundaries/scopes/channels IP burst rejection returns `503 slow_down`; repeaters may return `429 rate_limited`. Coverage retains its own documented `429` responses.

Protected responses use `Cache-Control: private, no-store`. Keep your backend's parsed data and any ETag separately for each credential and geographic scope, and discard data when access changes. Where supported, `If-None-Match` saves bandwidth but not quota. Do not expose keys in shared cache URLs. Coverage's server-side grid cache is unchanged.

## Read API errors

| Status | Error | Action |
| --- | --- | --- |
| 401 | `missing_key`, `invalid_key` | Supply a current key in `X-API-Key`. |
| 403 | `wrong_key_type` | Use a Coverage key; App keys only support repeaters. |
| 403 | `api_not_allowed` | Ask the key owner to review endpoint permissions. |
| 403 | `no_region`, `region_not_allowed` | Check the key's region/group assignment. |
| 503 | `key_rate_limited` | Wait for `Retry-After`. |
| 503 | `auth_unavailable` | Authentication storage is temporarily unavailable; respect `Retry-After`. |

Endpoint-specific errors remain documented on each API page. Coverage keeps its existing missing/invalid-key errors and adds `403 api_not_allowed` and `503 auth_unavailable`.
62 changes: 22 additions & 40 deletions docs/channels-api.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Channels API

The Channels API lists the hashtag channels a region's admins have set up for it. That's the same list the MeshMapper app listens on for passive RX in that region. Use it to show a region's local channels, or to set them up in your own client, without scraping the map. No key is needed.
The Channels API lists the hashtag channels a region's admins have set up for it. That's the same list the MeshMapper app listens on for passive RX in that region. Use it to show a region's local channels, or to set them up in your own client, without scraping the map. Send your Coverage API key in the `X-API-Key` header.

```
GET https://yow.meshmapper.net/get_channels.php
Expand All @@ -11,11 +11,11 @@ GET https://yow.meshmapper.net/get_channels.php

There are no parameters.

!!! warning "One call per day; keep calling early and you're banned"
Each region site's answer can be fetched **once every 23.5 hours per IP address**. A second request to the same region site inside that window is refused with `429`, and **the third refused request within the window bans your IP address from all of meshmapper.net for a day**. Every repeat doubles the ban, up to 30 days. See [Call limits](#call-limits). Accessing anything that isn't a published API, or scraping pages for data, is not allowed; see the warning on the [Coverage API](coverage-api.md) page.
## Authentication

!!! warning "Call this from your server, not your visitors' browsers"
Fetch this API from your own backend, store the result, and serve your own copy to visitors. Don't call it from client-side JavaScript in a visitor's browser: everyone behind the same home router or mobile carrier shares one public IP address, so one visitor's fetch uses the call and the next visitor's fetch bans that whole IP address from all of meshmapper.net, including every MeshMapper app user on that network.
Send your existing **Coverage API key** in the `X-API-Key` header. No new key is needed. See [API keys and access](api-keys.md) for generation, group permissions, custom GLOBAL integrations and the v1.5.117 rollout.

Fetch from your backend and serve your own parsed data to visitors. Keep the key out of client-side JavaScript.

## Response

Expand Down Expand Up @@ -69,51 +69,33 @@ print(k.hex(), hashlib.sha256(k).hexdigest()[:2]) # 7871ec72b45617696c35c970bd

## Call limits

- **One call per region site every 23.5 hours per IP address.** A group site counts as its own site, separate from its members.
- **An early call is refused with `429`; the third refusal within the window bans your IP address.** Your IP address is banned from all of meshmapper.net (the map included, and every device behind that IP address) for 1 day. Each repeat doubles the ban: 2 days, 4 days, 8 days, up to 30 days.
- **At most 30 calls a minute from one IP address, across all these APIs together.** A call over that gets `503` with `{"error":"slow_down","retry_after":N}`. It doesn't use your call and never counts toward a ban: wait `Retry-After` seconds and carry on. Pausing 2 seconds between calls keeps you under it.
- **Only certain JSON errors hand your call back.** A `404` `zone_not_found`, a `503` `unavailable`, or a `503` `slow_down` (after waiting `Retry-After`) doesn't use your call, so retrying after one of those is safe. Anything else, a cut-off or unparseable body, a timeout on your side, an HTML `5xx` error page, a connection reset, may already have used your call: keep your previous copy and wait for your next scheduled run instead of retrying.
- **Only `GET` counts.** A browser's CORS preflight (`OPTIONS`) doesn't. `HEAD` and other methods get `405` and don't count either.
- **Don't rely on the clock, track your last call.** Store the time of your last served call for each region site and skip the call if it was less than 23.5 hours ago. Schedule in UTC: a local-time cron job gets a 23-hour day at the DST change, which is enough to trip the limit.
- **Use a generous client timeout, 60 to 120 seconds.** A short timeout on your side can cut the connection before the server finishes, and that counts against you as an "anything else" error above, not a safe-to-retry one.
- **While testing, don't open the URL twice.** Save the response to a file once and work from the file; every reload after the first is refused and counts toward a ban.
These reads use the key's shared read budget, normally **1,000 requests per UTC day and 30 per fixed minute**, independently of Coverage's daily quota. Authenticated reads no longer use the old once-per-target IP interval. IP burst protection still applies. Every admitted request counts, including `304` responses and downstream failures. See [limits and caching](api-keys.md#limits-and-caching).

## Caching

Sends `Cache-Control: public, max-age=84600` (23.5 hours) and a strong `ETag`, so a standard HTTP cache won't ask again before your next call is allowed. On your next daily call, send the `ETag` back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed. `generated_at` isn't part of the `ETag`. A `304` uses your call for that window, just like a `200`.

`ETag` and `Retry-After` are readable from browser JavaScript (`Access-Control-Expose-Headers`), for server-side tools and your own debugging, not so you can embed this call directly in a page your visitors load; see the warning above.

Responses are gzip-compressed.
Responses use `Cache-Control: private, no-store`. Keep your backend's parsed data and ETag separately by key and scope. Send both `X-API-Key` and `If-None-Match` on conditional requests; a `304` saves bandwidth but still counts. Responses support gzip compression.

## Errors

Errors are JSON: `{"error": "<code>"}`. A `429` also carries `retry_after`.
Authentication, permissions and quota errors are listed in [Read API errors](api-keys.md#read-api-errors).

| Status | `error` | Meaning |
| Status | Error | Meaning |
| --- | --- | --- |
| 404 | `zone_not_found` | The region is unknown, pending or turned off. |
| 405 | `method_not_allowed` | Only `GET` (and the `OPTIONS` preflight) are answered. |
| 429 | `rate_limited` | This region site was already fetched from your IP address in the last 23.5 hours. `Retry-After` and `retry_after` give the seconds left. Three of these within the window ban your IP address; see [Call limits](#call-limits). |
| 503 | `unavailable` | Temporary server problem. Try again later; it doesn't use your call. |
| 503 | `slow_down` | More than 30 calls in the last minute from your IP address, across all these APIs. `Retry-After` and `retry_after` give the seconds left. It doesn't use your call and never counts toward a ban. |
| 404 | `zone_not_found` | The region is unknown, pending or disabled. |
| 405 | `method_not_allowed` | Use GET or an OPTIONS preflight. |
| 503 | `unavailable` | Temporary data problem; retain your previous copy and retry later. An admitted request still consumes quota. |
| 503 | `slow_down` | IP burst protection; wait for Retry-After. |

A group request requires access to every enabled member.

## Example

Run this once a day from a scheduled job on your own server, never in a loop and never from a browser. It tracks the last served call so an early rerun skips instead of risking a ban:
With `MESHMAPPER_API_KEY` set in your backend environment:

```python
import json, os, time, httpx

STATE = "meshmapper-state.json"
last = json.load(open(STATE)) if os.path.exists(STATE) else {}

url = "https://yow.meshmapper.net/get_channels.php"
if time.time() - last.get(url, 0) >= 23.5 * 3600:
r = httpx.get(url, timeout=120)
if r.status_code == 200:
last[url] = time.time()
json.dump(r.json(), open("yow-channels.json", "w"))
# anything else: keep your previous copy, don't retry here
json.dump(last, open(STATE, "w"))
```bash
curl --fail-with-body --compressed \
-H "X-API-Key: $MESHMAPPER_API_KEY" \
'https://yow.meshmapper.net/get_channels.php'
```

Schedule polling to fit your key budget, retain the last successful data on failure, and respect `Retry-After`.
Loading
Loading