Users & Sync
Users & Sync
Everything a signed-in consumer app needs: register/login, profile & privacy settings, device/session management, GDPR export & deletion, saved places and shareable lists, search/visit history, and live location sharing with ETA. This is the same API the Navvo map app itself runs on.
Authorization: Bearer <accessToken> from POST /auth/login) — not your x-api-key. Endpoints marked public need no credentials at all (the share key/slug is the secret).Accounts & profile
POST /auth/register
Create a user account. Anonymous by definition.
| Field | Type | Notes |
|---|---|---|
email | string | required — must be a valid email, stored lowercased |
password | string | required — minimum 10 characters |
name | string | optional display name |
turnstileToken | string | browser-only: applies solely to Navvo's own web sign-in form. API clients calling with an x-api-key are never captcha-checked and can omit this entirely |
curl -s -X POST "https://navvo.io/api/v1/auth/register" \
-H "content-type: application/json" \
-d '{ "email": "lina@example.com", "name": "Lina", "password": "superStrongPass123!" }'
{
"user": { "id": "…", "email": "lina@example.com", "name": "Lina", "roles": ["user"] },
"sessionId": "3f6f2c1e-…",
"accessToken": "eyJhbGciOi…"
}
accessToken is a long-lived Bearer JWT bound to sessionId — store it securely and send it on every user-scoped call. Revoking the session (logout, device management) kills the token server-side.
POST /auth/login
Same body (email, password) and same response envelope as register. A disabled account or wrong credentials returns 400. As with register, no captcha is ever required when calling with an API key — turnstileToken exists only for Navvo's first-party browser form.
GET /auth/me
The signed-in user + the current session.
curl -s "https://navvo.io/api/v1/auth/me" -H "authorization: Bearer $NAVVO_USER_TOKEN"
{
"user": { "id": "…", "email": "lina@example.com", "name": "Lina", "roles": ["user"],
"profile": { "avatar": { "type": "emoji", "value": "🌟" }, "privacy": { "saveHistory": true } } },
"session": { "id": "…", "sessionId": "…", "lastSeenAt": "…", "expiresAt": "…", "revokedAt": null }
}
PATCH /auth/me
Update the editable profile. All fields optional; only what you send changes.
| Field | Type | Notes |
|---|---|---|
name | string | display name (trimmed, max 160 chars) |
avatar | object | null | { type: "image" | "emoji" | "letter", value, bgColor? }; null clears the avatar |
privacy | object | null | privacy toggles — whitelisted boolean keys saveHistory, personalization, shareAnalytics, useLocation; null clears the bag |
curl -s -X PATCH "https://navvo.io/api/v1/auth/me" \
-H "authorization: Bearer $NAVVO_USER_TOKEN" -H "content-type: application/json" \
-d '{ "privacy": { "saveHistory": false, "personalization": true } }'
→ { "ok": true, "user": { … } }. Clients read the settings back from GET /auth/me → user.profile.privacy. Honour saveHistory: false by not calling POST /me/history.
POST /auth/logout
Signs out the current session server-side (the JWT's session id is revoked, so the token stops working everywhere). → { "ok": true }
GET /auth/sessions · DELETE /auth/sessions
Device management — list the user's sessions and revoke them.
{
"items": [
{ "sessionId": "…", "current": true, "ipAddress": "94.…", "userAgent": "Mozilla/5.0 …",
"lastSeenAt": "…", "createdAt": "…", "expiresAt": "…", "revokedAt": null }
]
}
DELETE /auth/sessions/{sessionId}— revoke one session →{ "ok": true, "revoked": 1 }DELETE /auth/sessions?all=1— "sign out everywhere else": revokes every session except the current one →{ "ok": true, "revoked": n }
GET /auth/me/export
GDPR-style data takeout — everything Navvo stores under the account, as one JSON document: { exportedAt, account, savedLocations, mapHistory, liveShares, signDisregards, sessions }.
DELETE /auth/me
Self-serve account deletion (app-store requirement). Cascades the user's own data — sessions, saved places & history, live shares — then removes the account.
| Field | Type | Notes |
|---|---|---|
confirm | string | required — must be the literal string DELETE |
password | string | required when the account has a password |
→ { "deleted": true }
Social sign-in (OAuth)
Providers are configured by the platform operator — always render buttons from the live list:
GET /auth/providers(public) →{ "providers": [ { "id": "google", "label": "Google", … } ], "turnstile": { "enabled": true, "siteKey": "…" } }GET /auth/oauth/{provider}/start?next=/map— 302-redirects the browser to the provider's consent page.GET /auth/oauth/{provider}/callback— the provider redirects back here; on success the browser lands on/map#nvauth=ok&token=<accessToken>. If the email already belongs to a password account, the fragment isnvauth=linkand the client confirms the link viaPOST /auth/oauth/link{ linkToken, password? }.
POST /auth/firebase
Firebase sign-in exchange — the recommended sign-in path for apps using Firebase Authentication (email/password, Google, Apple, Microsoft, Facebook, GitHub…). Sign the user in with the Firebase SDK, then post the Firebase ID token; Navvo verifies it against Google's public certificates (issuer/audience pinned to the Navvo project), finds or creates the account by verified email, and returns the standard auth envelope (user, sessionId, accessToken) — so the user immediately has full access to member services (navigation, Atlas, saved places…).
| Field | Type | Notes |
|---|---|---|
idToken | string | required — Firebase ID token from getIdToken() |
curl -s -X POST "https://navvo.io/api/v1/auth/firebase" \
-H "content-type: application/json" \
-d '{ "idToken": "eyJhbGciOiJSUzI1NiIs…" }'
Access model: guest vs signed-in
The base map (tiles, styles, browsing, search, place details, geocoding reads) is open. Member services require a signed-in user's Bearer token — or your developer API key: routing/navigation (POST /routing/*, POST /traffic/route), the Atlas agent, saved places/lists, history, live sharing, reporting, and push tokens. Anonymous calls to those return 401.
Push notification tokens
Register the device's FCM token so your backend (or Navvo, when the operator enables server-side sending) can reach the user:
POST /me/push-tokens— body{ "token": "fcm-registration-token", "platform": "web" | "android" | "ios" }(Bearer auth). Re-posting an existing token re-binds it to the current user.DELETE /me/push-tokens/{token}— unregister on sign-out.
Saved places
A user's favorites with custom name, icon, note, list, and tags — synced across devices.
GET · POST /me/saved-places
GET returns the newest 100 favorites. POST saves a point:
| Field | Type | Notes |
|---|---|---|
name | string | required — the place/point name |
lat / lng | number | required |
detail | string | optional subtitle/address |
placeId | string | optional Navvo place id — also files the place into the user's "Saved places" list |
customName | string | optional user-chosen display name |
note | string | optional private note |
icon | string | optional icon/emoji |
listName | string | optional grouping label |
tags | string | optional comma-separated tags (max 12) |
PATCH /me/saved-places/{id}— updatecustomName,note,icon,listName,tags.DELETE /me/saved-places/{id}→{ "deleted": true, "id" }.
GET · POST /me/locations
Pinned anchors like home and work: { "type": "home" | "work" | "favorite" | "custom", "label", "lat", "lng" }. GET lists them (stored geometry is WKT POINT(<lng> <lat>) — longitude first).
Lists — GET · POST /me/lists
Named collections of catalog places, optionally shareable.
POST /me/lists{ "name": "Weekend cafés", "public": false }— every list gets a uniqueshareSlug; set"public": trueto make its share link resolve.GET /me/lists— the user's lists with their places.POST /me/lists/{id}/places{ "placeId": "…", "note"? }— add a catalog place to a list.
GET /shared-lists/{shareSlug} — public
The shared-list viewer — no auth; the slug is the secret, and only lists the owner marked public resolve. The response is scrubbed (no user ids):
curl -s "https://navvo.io/api/v1/shared-lists/weekend-cafes-a1b2c3"
{
"found": true, "name": "Weekend cafés", "updatedAt": "…",
"places": [
{ "slug": "jordan-beans-cafe", "nameEn": "Jordan Beans Cafe", "nameAr": "…",
"categories": ["cafe"], "rating": 4.6, "center": "POINT(35.913 31.953)", "note": null }
]
}
An unknown/private slug returns { "found": false }.
GET · POST · DELETE /me/history
The user's search/visit history (newest first, capped at 100 returned).
GET /me/history→{ "items": [ … ], "total" }POST /me/history{ "name", "lat", "lng", "detail"?, "placeId"? }— record an entryDELETE /me/history— clear all →{ "deleted": true }DELETE /me/history/{id}— remove one entry
Commutes (leave-time advice + alarms)
Saved home→work-style commutes, each with day-of-week repeat, a permanent or temporary lifespan, and any number of alarm lead-times. The server evaluates each enabled commute every minute against live traffic and, when the moment to leave approaches, sends a push notification (via the user's registered push tokens) — e.g. "Leave in ~15 min for Work."
All routes are Bearer-authenticated and scoped to the signed-in user.
GET /me/commutes→{ "commutes": Commute[] }POST /me/commutesCommute(withoutid) →{ "commute" }(max 20 per user)PUT /me/commutes/{id}(partialCommute) →{ "commute" }DELETE /me/commutes/{id}→{ "ok": true }
// Commute
{
"id": "…",
"name": "Work",
"from": { "lat": 31.95, "lng": 35.91, "label": "Home" },
"to": { "lat": 31.99, "lng": 35.95, "label": "Office" },
"arriveBy": "09:00", // HH:MM, local (Asia/Amman)
"days": [0,1,2,3,4], // 0=Sun … 6=Sat; [] = every day
"permanent": true, // false = temporary (auto-expires)
"expiresAt": null, // "YYYY-MM-DD" when temporary
"notifications": [{ "minutesBefore": 30 }, { "minutesBefore": 15 }],
"enabled": true
}
Live location sharing
Share a live position under a rotating, unguessable key. A share auto-expires 2 hours after the last ping; each ping extends it.
POST /me/live-share
Start a share: { "label"? } → { "shareKey": "…", "active": true, "expiresAt": "…" }. The viewer URL to hand out is https://navvo.io/map?live=<shareKey>.
GET /me/live-share
The user's active shares: { "items": [ { "shareKey", "label", "lastPingAt", "expiresAt" } ] }.
POST /me/live-share/{key}/ping
Push the current position (call every few seconds while sharing):
| Field | Type | Notes |
|---|---|---|
lat / lng | number | required |
heading | number | optional course in degrees |
accuracy | number | optional GPS accuracy in metres |
destName | string | Share-ETA — destination name while navigating |
destLat / destLng | number | Share-ETA — destination coordinates |
etaSeconds | number | Share-ETA — seconds to arrival; resend on every ping while navigating |
→ { "active": true } ({ "active": false } once stopped/expired — stop pinging).
POST /me/live-share/{key}/stop
End the share → { "stopped": true }.
GET /live/{key} — public
The viewer read — no auth, poll it a few times a minute:
{
"active": true, "lat": 31.9539, "lng": 35.9106, "heading": 270, "accuracy": 8,
"label": "Lina", "updatedAt": "…",
"destination": { "name": "Airport", "lat": 31.7226, "lng": 35.9932 },
"etaSeconds": 1240, "arrivalTime": "2026-07-09T11:42:00.000Z"
}
destination, etaSeconds, and arrivalTime are only present while the sharer is navigating (they sent Share-ETA fields on the ping) — otherwise they are null. Ended/unknown keys return { "active": false }; a share started but not yet pinged returns { "active": true, "pending": true }.
Deep links into the Navvo map
Hand off from your app to the full Navvo map experience with plain URLs:
| URL | Opens |
|---|---|
https://navvo.io/map/place/{slug} | the place detail page for a catalog place |
https://navvo.io/map?pin=31.95,35.91&name=Meet%20here | a dropped pin at lat,lng with an optional label |
https://navvo.io/map?from=31.95,35.91&to=31.72,35.99&navigate=1&mode=auto | a route — with navigate=1 turn-by-turn starts immediately; mode is a routing profile (auto, pedestrian, bicycle, …) |
https://navvo.io/map?live=KEY | the live-share viewer following a share key |
GET /live/{key} for arrival, or your own logic over saved places and history).Atlas Agent (NL control)
Turn natural-language commands into structured map actions — search, directions, pins, layer control, and now day-plan itineraries and fleet route optimization.
Voice & Speech-to-Text
Turn spoken audio into text (English + Arabic) and call Navvo's direct AI helpers for query understanding and route labels.