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.

Friends on the phone​

A request reaches the other rider's phones as a push, written in their phone's language; accepting sends one back to whoever asked. A tap opens the app's Friends screen (More → Riding together), where the requests waiting for an answer sit on top. Declining a request or removing a friend sends no push and shows no "declined": the other rider's app simply updates its list at once.

Asking by username tells you nothing about who exists: POST /live/friends/request answers an unknown name exactly like a known one, so a typo is not reported and names cannot be listed one by one. Thirty requests an hour per account, then 429.

The sure way to add someone is the invite link: POST /live/friends/invite-links returns a link (fastgis://friends/invite/…) to send through the share sheet or show as a QR code; the token is shown once and only its hash is kept. Whoever opens it in the app signed in sees who invites them (GET /live/friends/invite/{token}) and becomes their friend on one tap (POST /live/friends/invite/{token}) — several riders may use the same link until it expires (1–30 days, 14 by default). GET /live/friends/invite-links lists the links you handed out with how many riders each befriended, and DELETE /live/friends/invite-links/{link_id} revokes one at once; friends it already made stay friends.

More than a dot: captures and the bike's numbers​

A share session can carry two more things, each on its own switch and off unless the rider ticks it for that session:

  • Captures — a photo, a video, a voice note or a note appears to the audience the moment it is taken, as a small preview, and the full file once it has uploaded. The audience sees them for the session plus 24 hours; after that only the rider's Library share reaches them.
  • Telemetry — altitude, lean angle and g-forces from the phone, and rpm, gear, throttle, coolant, fuel and the odometer from an OBD dongle, riding on each position update. Nothing is kept beyond the last update.

A public link never carries either: it shows the position only. An event's organiser can ask riders to share them while the event is live; only the rider can switch them on. The calls and frames are on the developer guide's realtime page. The server supports both today; the phone's switches and the peer cards that show them come with the apps' next builds.

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.

The two ways out of a ring are not interchangeable. POST /live/voice/decline is the callee's verb: it clears that one rider's invite. POST /live/voice/cancel is the caller's: it clears every pending invite and tells the whole room the ring is over — so it answers 403 to anyone but the rider who rang (including an SOS room, whose caller is the rider whose phone raised the alarm). A helper whose join failed steps out; they cannot hang the ring up for everyone.

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).

On the phone the alarm is a 30-second countdown first — a siren through the silent switch plus vibration — and only silence sends. A manual SOS is a hold-to-confirm button on the Navigate map and the turn-by-turn HUD (about a second and a half), as well as the row in Settings; a confirmed hold still starts the same countdown, so a mis-tap is recoverable. Each incident carries a client_id minted on the phone: if the alert cannot be sent — a dead zone is exactly where crashes happen — the phone keeps retrying on a backoff, again whenever it returns to the foreground, and by hand from the red screen, and the server answers a repeat with the same incident instead of alarming anyone twice. The countdown, an unsent alert and a live incident all survive an app restart, so the stand-down button is still there afterwards.

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.