API & sync
The deliberately small REST API — sessions, offline-first sync, and sharing — with OpenAPI built in.
The API is small on purpose, and stays that way: anything that can be a static artifact published by the pipeline must be one, so the server's job reduces to what genuinely needs a database — accounts, sync, and sharing. It's one Rust binary (axum) against Postgres.
Map tiles are never served by the API.
- Base path:
/v1 - Spec: the server publishes OpenAPI at
/v1/openapi.json - Health:
GET /healthz - Auth: session cookie (web) or the same session token as a bearer
header (mobile). Cookie-authenticated writes are origin-checked against
ALLOWED_ORIGINS.
Endpoints
| Method & path | What it does |
|---|---|
POST /v1/auth/register | Create an account. |
POST /v1/auth/login | Start a session. |
POST /v1/auth/logout | End it. |
POST /v1/auth/request-verify · POST /v1/auth/verify | Email verification. |
POST /v1/auth/request-password-reset · POST /v1/auth/reset-password | Password reset. |
GET·PATCH·DELETE /v1/me | The signed-in user: profile, settings, entitlements — and account deletion. |
GET /v1/sessions · DELETE /v1/sessions/{id} | List and revoke device sessions. |
GET·POST /v1/shares · GET·DELETE /v1/shares/{token} | Create, resolve, and revoke share links. |
GET·POST /v1/sync | Pull and push for the sync protocol below. |
The sync protocol
Clients are local-first: the device store (IndexedDB on web, SQLite on mobile) is authoritative in the field, and the server reconciles.
- Client-generated IDs. Objects get UUIDv7 ids on the device, so creating a waypoint never waits on the network.
- Push sends an outbox of changed objects as versioned envelopes;
an
Idempotency-Keyheader makes retries safe on a flaky connection. - Pull streams changes since a cursor; the client applies them and advances the cursor.
- Conflict resolution is per-object last-write-wins. Waypoints and routes are coarse objects edited by one hunter at a time in practice; deletes travel as tombstones.
The same protocol serves web and mobile from one implementation — the mobile sync client is a line-for-line port of the web's.
Entitlements
Plans flow through one seam: the session's entitlements object, read
from /v1/me. Clients never scatter plan checks — they read what the
server says this account can do. Self-hosted deployments run ungated.
Errors and versioning
Breaking a payload schema requires a version bump and a migration note,
even pre-1.0. The OpenAPI spec at /v1/openapi.json is the contract — the
web client's typed API layer is generated from it.