Skip to main content

Riding together

Four features share one realtime plane: seeing where each other are, riding as a convoy, talking, and getting help when something goes wrong. They are built on the /live and /sos endpoints and a single WebSocket.

Sharing your location

Sharing is opt-in, per session, and to a chosen audience — it is not a setting you leave on.

StepEndpoint
Start sharingPOST /live/share/start
Choose who sees youPOST /live/share/audience
Check what is currently sharedGET /live/share/status
StopPOST /live/share/stop

Your audience is built from friends and sharing groups. Friendship is a two-sided request — POST /live/friends/request, then the other rider accepts (/live/friends/accept) or declines. Groups work the same way: create one with POST /live/groups, invite with POST /live/groups/{gid}/invite, and the invitee accepts or declines. Nobody appears in your audience because you typed their name.

For someone who has no account — a partner following the ride from home — create an expiring link:

POST /live/share/link

The returned token is the credential. GET /live/view/{token} renders a map page and needs no sign-in; GET /live/public/{token} and /live/public/{token}/peers back it with data. An expired or unknown token answers 410, not 404, so the viewer can tell "this link has ended" from "this link never existed". Revoke early with DELETE /live/share/links/{token}, and list what you have handed out with GET /live/share/links.

The realtime stream

Positions move over a WebSocket rather than polling, so a peer's marker moves as they move rather than jumping every thirty seconds. When the app is backgrounded it falls back to sending positions over HTTP, and the experience for everyone watching is the same.

The stream is deliberately forgiving in one direction and strict in the other: a malformed frame is ignored rather than tearing down the connection, and a client sending far too fast is throttled before it is disconnected. A dropped frame is cheaper than a dropped rider.

:::info Building your own client? The full protocol — frame types, close codes, reconnect semantics — is in the developer documentation. :::

Group ride

A group ride turns a sharing group into a convoy with a leader and a shared route.

POST /live/ride

The caller becomes leader, their route becomes the shared route, and every other group member receives a ride invite. From there:

ActionEndpoint
Join an invited ridePOST /live/ride/{gid}/join
Update the shared routePOST /live/ride/{gid}/route
Hand over leadershipPOST /live/ride/{gid}/leader
Decline leadershipPOST /live/ride/{gid}/leader/decline
Stay visiblePOST /live/ride/{gid}/heartbeat
LeavePOST /live/ride/{gid}/leave
End for everyonePOST /live/ride/{gid}/end

GET /live/rides lists the rides you can join; GET /live/ride/{gid} returns one. Convoy size is capped by GROUP_RIDE_MAX, which the deployment sets.

Talking

Two modes, deliberately different.

Push-to-talk is a short clip fanned out to the group — everyone except the speaker — and nothing is stored. POST /live/ptt sends one; recipients get it as a ptt WebSocket event. POST /live/ptt/floor claims the floor so two riders do not talk over each other.

Full-duplex calls run over a self-hosted LiveKit SFU rather than the WebSocket. The lifecycle is explicit so a phone can ring properly:

ring → (joined | declined | cancelled) → left

POST /live/voice/ring rings a friend or every group member; offline riders simply have no socket, so the fan-out is a quiet no-op rather than an error — there is no presence oracle to consult. POST /live/voice/token mints the LiveKit room token. GET /live/voice/active and GET /live/voice/calls report what is in progress. Ringing is rate-limited per caller.

SOS

If a crash is detected — or you trigger it yourself — POST /sos/alert raises an incident. Everyone in your convoy is notified, and your emergency contacts are alerted through the channels the deployment has configured (push, Telegram, and SMS where an SMS_PROVIDER is set).

Manage contactsPUT /sos/contacts, GET /sos/contacts
Suggest contacts from your friendsGET /sos/contacts/candidates
Invite someone to be a contactPOST /sos/contacts/invite
Link a contact's TelegramPOST /sos/contacts/telegram-link
Acknowledge an incidentPOST /sos/{incident_id}/ack
Close itPOST /sos/{incident_id}/resolve
HistoryGET /sos/incidents

Acknowledgement matters: an unacknowledged alert escalates, so a contact tapping I'm on it changes what happens next rather than just marking it read.

:::caution Not a substitute for emergency services SOS alerts people who chose to be your contacts. It does not call an ambulance. :::

Where to look next

Every endpoint above, with request and response shapes, is in the API reference under the live and sos tags — available to developer subscribers.