Appearance
HTTP Endpoints
This reference is aligned with server/src/routes/http.ts.
Application JSON endpoints use the root-absolute /api/v1/ namespace. This is a hard cut: the former unprefixed application paths are not aliases. Operational and device-facing paths remain outside that namespace, including /ws, /.well-known/jwks.json, /eink/display, /lld/*, /grafana/*, and /integration/docs.
Auth labels in this page:
None: no authAuth: authenticated userAdmin: admin-onlyToken/Auth: navigation token or authenticated user
Authentication and users
| Methods | Path | Auth |
|---|---|---|
| GET | /.well-known/jwks.json | None |
| POST | /api/v1/login | None |
| POST | /api/v1/register | Admin |
| POST | /api/v1/change-password | Auth |
| GET, DELETE | /api/v1/users/:userId | Admin |
| GET | /api/v1/users | Admin |
Interface templates
| Methods | Path | Auth |
|---|---|---|
| GET, POST | /api/v1/interface-templates | Admin |
| GET, PUT, DELETE | /api/v1/interface-templates/:id | Admin |
| POST | /api/v1/interface-templates/:id/clone | Admin |
| GET | /api/v1/user/session | Auth |
POST /interface-templates requires name, role (user or maintenance), and a versioned definition. A template's role is immutable after creation. PUT updates only the name and definition; clone to create a copy for another role.
Clone accepts name, role, and optional remove_incompatible. A cross-role clone returns 409 with incompatible_components until explicit removal is requested.
The visual designer's live preview uses the internal X-SafeCall-Preview-User-Id header. It is accepted only with an administrator JWT, only for the allowlisted component GET endpoints, and resolves the selected user's normal role and location scope. It is rejected for writes, exports, unrelated routes, and WebSockets. This header is an internal UI mechanism, not a general integration API.
Beacons and gateways
Path segments historically named :mac (or tracking :id) for asset lookup must be asset_uid (Phase 6c). Device MAC remains the wire identity for MQTT/gmac and replace-device bodies. Gateways also expose asset_uid (GW-…); admin MAC changes use replace-device (chunk 7c). E-ink displays expose asset_uid (EK-… / LEGACY-…); poll stays GET /eink/display?mac= (chunk 7d). Layout cells bind to the sensor asset_uid, not the device MAC.
| Methods | Path | Auth |
|---|---|---|
| GET | /api/v1/beacon/:mac/info | Admin (:mac = asset_uid) |
| GET | /api/v1/assets/:asset_uid | Admin |
| GET | /api/v1/assets/:asset_uid/info | Admin |
| GET | /api/v1/beacon/:id/intervals | Auth |
| GET, POST, DELETE | /api/v1/beacon/:id | Admin for mutating methods (MAC not editable via POST; use replace-device) |
| POST | /api/v1/beacon/:id/replace-device | Admin (optional replaced_at + retire_donor for a backdated sensor split) |
| POST | /api/v1/beacon/:id/replace-device/plan | Admin (preview keep/move counts for a backdated sensor replace) |
| POST | /api/v1/beacon/:id/merge/plan | Admin (ordered source_ids; read-only preview) |
| POST | /api/v1/beacon/:id/merge | Admin (irreversible source retirement + history stitch) |
| GET | /api/v1/beacon/:id/assignments | Admin |
| POST | /api/v1/assets/replace-device/batch | Admin |
| GET | /api/v1/devices | Admin (query: status, calibration=due|expired|ok, asset_kind=beacon|gateway|eink|all) |
| GET | /api/v1/beacons/:limit | Admin |
| GET, POST | /api/v1/beacons | Auth, Admin for POST |
| GET, POST, DELETE | /api/v1/gateway/:id | Admin for mutating methods (MAC not editable via POST; use replace-device) |
| POST | /api/v1/gateway/:id/replace-device | Admin |
| GET, POST | /api/v1/gateways | Auth, Admin for POST |
| GET, POST | /eink/display | None (device poll ?mac=) |
| GET | /api/v1/eink/displays | Admin |
| POST, DELETE | /api/v1/eink/display/:id | Admin |
| POST | /api/v1/eink/display/:id/replace-device | Admin |
| POST | /api/v1/eink/display/:id/state | Admin |
Locations, logs, and events
| Methods | Path | Auth |
|---|---|---|
| GET, POST | /api/v1/locations | Auth, Admin for POST |
| GET | /api/v1/logs | Auth |
| GET | /api/v1/events | Auth |
| GET | /api/v1/events/export | Admin |
Maps
| Methods | Path | Auth |
|---|---|---|
| GET | /api/v1/maps/overview | Auth |
| GET | /api/v1/maps/image | Auth |
| GET | /api/v1/maps/:id/overview | Auth |
| GET | /api/v1/maps/:id/zones | Auth |
| GET | /api/v1/maps/:id/gateways | Auth |
| GET, POST, DELETE | /api/v1/map/:id | Admin for mutating methods |
| POST | /api/v1/maps/upload | Admin |
| GET, POST | /api/v1/maps | Auth, Admin for POST |
LLD and status
| Methods | Path | Auth |
|---|---|---|
| GET | /lld/beacons | None ({#ASSET_UID}, {#MAC} = current device) |
| GET | /lld/gateways | None ({#ASSET_UID}, {#MAC} = current device) |
| GET | /api/v1/version | None |
| GET | /api/v1/status/kiss | Auth |
| GET | /api/v1/status/mqtt | Auth |
| GET | /api/v1/status/dashboard | Auth |
| POST | /api/v1/status/recheck | Admin |
GET /status/mqtt and GET /status/kiss read the cached dashboard snapshot (no live KISS login). GET /status/dashboard returns MQTT, KISS, ntfy, fleet counts, and software-detected issues. POST /status/recheck with { "target": "mqtt" | "kiss" | "ntfy" } forces that probe and returns the snapshot. Dashboard and recheck responses are Cache-Control: no-store.
ntfy health checks and all notification POSTs use a bounded curl transport (not Bun fetch). The ntfy object keeps a summary ok / message plus independent reachability and delivery probes (ok, checked_at, http_status, duration_ms, error_code, message) and transport: "curl". Periodic probes update reachability only; Recheck updates both; site publishes update delivery. A successful publish does not hide a failed health check, and vice versa.
Import and deploy
| Methods | Path | Auth |
|---|---|---|
| GET | /api/v1/import/beacons | Admin |
| GET | /api/v1/import/gateways | Admin |
| POST | /api/v1/deploy | None |
Tracking
| Methods | Path | Auth |
|---|---|---|
| GET | /api/v1/tracking/start/:alg | Admin |
| GET | /api/v1/tracking/stop | Admin |
| GET | /api/v1/tracking/status | Auth |
| GET | /api/v1/tracking/algorithms/current | Auth |
| GET | /api/v1/tracking/algorithms | Auth |
| GET | /api/v1/tracking/:id | Auth (non-admin: active trackable assets only; admins: any asset_uid; :id = asset_uid) |
Simple-user RTLS (read-only)
These routes require the rtls server feature. They return 400 when RTLS is disabled.
| Methods | Path | Auth |
|---|---|---|
| GET | /api/v1/user/rtls/trackable-beacons | Auth |
| GET | /api/v1/user/rtls/beacons/:mac/zone-intervals | Auth (:mac = asset_uid) |
Query parameters for zone intervals: limit (default 30, max 200), cursor (offset into newest-first interval list; next_cursor in the response is the value to pass for the next page, or null when exhausted).
Integration API (external systems)
Versioned read-only API for api-role accounts (admins may also call it); see the Integration API reference. Interactive Swagger UI docs are self-hosted by the server.
| Methods | Path | Auth |
|---|---|---|
| GET | /api/v1/integration/beacons | api or Admin |
| GET | /api/v1/integration/beacons/locations | api or Admin |
| GET | /api/v1/integration/beacons/:mac/location | api or Admin (:mac = asset_uid, Phase 6c) |
| GET | /api/v1/integration/alerts | api or Admin |
| GET | /api/v1/integration/zones | api or Admin |
| GET | /api/v1/integration/openapi.json | None |
| GET | /integration/docs | None |
| GET | /integration/docs/:filename | None |
Navigation (floors, graph, mobile payload)
| Methods | Path | Auth |
|---|---|---|
| GET, POST | /api/v1/floors | Auth, Admin for POST |
| GET, POST, DELETE | /api/v1/floor/:id | Admin for mutating methods |
| GET, POST | /api/v1/floor/:id/nodes | Admin for POST |
| POST, DELETE | /api/v1/node/:id | Admin |
| GET, POST | /api/v1/floor/:id/edges | Admin for POST |
| POST, DELETE | /api/v1/edge/:id | Admin |
| GET, POST | /api/v1/floor/:id/graph | Auth for GET, Admin for POST |
| GET, POST | /api/v1/navigation/tokens | Admin |
| DELETE | /api/v1/navigation/token/:id | Admin |
| GET | /api/v1/navigation/mobile-key | Auth |
| GET | /api/v1/navigation/mobile/snapshot | Token/Auth |
| GET | /api/v1/navigation/mobile/catalog | Token/Auth |
| GET | /api/v1/navigation/mobile/project/:id | Token/Auth |
| GET | /api/v1/navigation/project/:id | Token/Auth |
| GET | /api/v1/navigation/project/:id/sha | Token/Auth |
| GET | /api/v1/navigation/floor/:id/map | Token/Auth |
| POST | /api/v1/navigation/pathfind | Token/Auth |
Features, sensors, and Grafana datasource
| Methods | Path | Auth |
|---|---|---|
| GET | /api/v1/features | Auth |
| GET, PATCH | /api/v1/settings | Admin |
| GET | /api/v1/sensors/alerts | Admin |
| GET | /api/v1/sensors/devices | Auth |
| GET | /api/v1/sensors/history | Auth |
Long since/until spans are downsampled on the server (latest reading per time bucket per device and metric) so responses stay bounded; short spans return raw rows (subject to a row cap). | GET | /grafana | Localhost or Auth | | POST | /grafana/search | Localhost or Auth | | POST | /grafana/metrics | Localhost or Auth | | POST | /grafana/query | Localhost or Auth |
Static frontend assets
| Methods | Path |
|---|---|
| GET | / |
| GET | /index.html |
| GET | /css/:filename |
| GET | /js/:filename |
| GET | /images/:filename |
| GET | /webfonts/:filename |