Navvo
API Reference

Users & Sync

Accounts, profiles, saved places, history, and live location sharing — the personal-data API for building a signed-in consumer app on Navvo.

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.

Authentication model. Unlike the rest of the API reference, these endpoints act on behalf of an end user, so they authenticate with the user's Bearer JWT (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.

FieldTypeNotes
emailstringrequired — must be a valid email, stored lowercased
passwordstringrequired — minimum 10 characters
namestringoptional display name
turnstileTokenstringbrowser-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 keyturnstileToken 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.

FieldTypeNotes
namestringdisplay name (trimmed, max 160 chars)
avatarobject | null{ type: "image" | "emoji" | "letter", value, bgColor? }; null clears the avatar
privacyobject | nullprivacy 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/meuser.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.

FieldTypeNotes
confirmstringrequired — must be the literal string DELETE
passwordstringrequired 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 is nvauth=link and the client confirms the link via POST /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…).

FieldTypeNotes
idTokenstringrequired — Firebase ID token from getIdToken()
curl -s -X POST "https://navvo.io/api/v1/auth/firebase" \
  -H "content-type: application/json" \
  -d '{ "idToken": "eyJhbGciOiJSUzI1NiIs…" }'
Linking to an existing Navvo account requires the email to be verified with the auth provider (OAuth providers always are; raw email/password signups must confirm their email first) — this prevents account takeover via unverified addresses.

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:

FieldTypeNotes
namestringrequired — the place/point name
lat / lngnumberrequired
detailstringoptional subtitle/address
placeIdstringoptional Navvo place id — also files the place into the user's "Saved places" list
customNamestringoptional user-chosen display name
notestringoptional private note
iconstringoptional icon/emoji
listNamestringoptional grouping label
tagsstringoptional comma-separated tags (max 12)
  • PATCH /me/saved-places/{id} — update customName, 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 unique shareSlug; set "public": true to 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 entry
  • DELETE /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/commutes Commute (without id) → { "commute" } (max 20 per user)
  • PUT /me/commutes/{id} (partial Commute) → { "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
}
Alarm delivery uses Firebase Cloud Messaging and activates once the platform operator configures the server-side FCM credential; commute records and the schedule are stored regardless. Times are evaluated in Asia/Amman.

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

FieldTypeNotes
lat / lngnumberrequired
headingnumberoptional course in degrees
accuracynumberoptional GPS accuracy in metres
destNamestringShare-ETA — destination name while navigating
destLat / destLngnumberShare-ETA — destination coordinates
etaSecondsnumberShare-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 }.


Hand off from your app to the full Navvo map experience with plain URLs:

URLOpens
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%20herea 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=autoa route — with navigate=1 turn-by-turn starts immediately; mode is a routing profile (auto, pedestrian, bicycle, …)
https://navvo.io/map?live=KEYthe live-share viewer following a share key

Push notifications are not operated by Navvo. There is no Navvo-hosted push service — mobile apps should trigger their own FCM/APNS pushes from their backend, using these APIs as the data source (e.g. poll GET /live/{key} for arrival, or your own logic over saved places and history).
Copyright © 2026