Coop API reference
REST + SSE, fully documented
Coop serves plain JSON over HTTP plus one Server-Sent Events stream. Perch is just its
best-known client — everything below is fair game for your own scripts and tools.
Base URL in a default install: http://127.0.0.1:2667.
Authentication model
- Reads (GET) are open — discovery and dashboards work for anyone who can reach the port.
- Writes require privilege: from loopback you always have it; remote clients present
the control key via
X-Coop-Keyheader or?key=. The key defaults to the web port number (a LAN convenience, not internet-grade security — put real exposure behind Tailscale or a reverse proxy). Set a real key viaMESH_API_KEYorcoop.json'sapiKey;"off"disables the gate. - Observe-only mode (
MESH_READONLY=1or"readOnly": true) refuses all transmit/config regardless of key — for public or shared viewers.
Read endpoints
| Endpoint | Returns |
|---|---|
GET /api/bootstrap | Everything for first paint: device, full node roster, recent packets/messages/signal, port counts, waypoints, home, server time. |
GET /api/stream | SSE live feed — see events below. |
GET /api/health | KPIs: link status, node/packet counts, rate, MQTT stats, capture-DB stats. Includes "engine":"coop" so probes can identify a real Coop. |
GET /api/counts?window=SECS | Windowed packet/message/active-node counts (DB-backed when capture is on — full history, not just memory). |
GET /api/db | Capture stats: rows, size, span, path — or {"enabled":false}. |
GET /api/db/config | Capture settings (enabled, path, retention, platform default path). |
GET /api/serialports | Visible serial ports (for connect UIs). |
GET /api/mqtt | Observer status + rx/decoded/decrypt-fail counters. |
GET /api/channels/export, GET /api/qr | Current channel set as URL / QR. |
GET /api/weather | Local weather for home (Open-Meteo, cached, keyless). |
GET /api/clubs | Known community MQTT presets. |
Write endpoints
| Endpoint | Does |
|---|---|
POST /api/send | Transmit text. {"text","dest","channel","wantAck","emoji","replyId"} — dest empty/omitted broadcasts; emoji:1 + replyId is a tapback. Returns the packet id for delivery tracking. 233-byte payload cap. |
POST /api/traceroute | Route discovery to a node — hop chain with per-link SNR, both directions. Takes up to a minute; see Mesh services. |
POST /api/request_position | Ask a node for a fresh fix: {"dest":"!id"}. |
POST /api/config/owner | Set node long/short name. |
POST /api/config/lora | Region / modem preset (reboots the radio). |
POST /api/channels/import | Apply a channel URL (reboots the radio). |
POST /api/db/config | Capture settings: {"enabled","dbPath","retentionDays"} — applies live, persists to coop.json. |
POST /api/clubs/join | Re-point the MQTT observer at a club. |
The SSE stream
One connection, JSON frames {"type", "data", "t"}:
| type | data |
|---|---|
packet | Each decoded packet (portnum, from/to, SNR/RSSI, text if any, source mqtt when observed). |
message | New chat message (rx and your own tx echoes). |
msgstatus | Delivery updates for sent messages: sent → delivered / failed / no-ack. |
node | Roster upsert — names, position, telemetry, neighbors as they're learned. |
device | Your radio's state (connection, config, battery…). |
waypoints | Full current list of mesh-shared waypoints whenever it changes. |
# watch your mesh from a terminal
curl -N http://127.0.0.1:2667/api/stream
# send to the primary channel
curl -X POST http://127.0.0.1:2667/api/send \
-H 'Content-Type: application/json' -d '{"text":"hello from the API"}'
# remote, with a control key
curl -X POST https://coop.example.ts.net/api/send \
-H 'X-Coop-Key: my-secret' -d '{"text":"hi"}'
/api/connect and /api/wifi currently answer with a friendly
"not implemented yet" — transport switching and WiFi provisioning from the API are on the
roadmap.