Basics
Authentication & audiences
How each app gets a bearer token, why a token only works for its own audience, how a visitor shops before signing in, and how staff and logout behave.
Customers, drivers and merchants are separate accounts with separate logins. Each app authenticates against its own audience and receives a bearer token that only that audience's endpoints accept.
Getting a token#
Every audience logs in by phone and password:
POST /api/v1/customers/auth/login
Accept: application/json
Content-Type: application/json
{"phone": "+966520000001", "password": "password"}
Then send the token on every later request:
Authorization: Bearer 12|kJ8f…
| Audience | Register | Log in | Token key in the response |
|---|---|---|---|
| Customer | register | login | access_token |
| Driver | register | login | access_token on register, token on login |
| Merchant | register | login | access_token |
Wrong credentials return 422 with the message under errors.phone. A deleted account returns 403.
Login is rate limited: after 5 attempts for the same phone within a minute, or 60 from one network address, it returns 429 with a Retry-After header in seconds. Show the user how long to wait instead of retrying automatically.
Tokens do not expire. A token stays valid until the app logs out with it, so store it in secure storage and send it until the server answers 401.
One token, one audience#
Each audience has its own authentication guard, and a guard only accepts tokens minted for its own account type. A driver token sent to a customer endpoint returns 401, exactly as if no token were sent.
So an app never needs to check which kind of user it is: if the request succeeded, the token belongs to that app's audience.
The one deliberate exception is the Platform · Store Catalog group (parent categories and the endpoints next to it), which accepts a customer token or a merchant token.
Shopping before signing in#
A visitor does not need an account, or any token, to browse and fill a cart. There is no guest account: the server creates a customer only when someone registers.
Browsing. The store discovery endpoints, the service area and the store catalog (Platform · Store Catalog) answer without an Authorization header. A visitor has no saved address, so the "nearby" feeds need the device's position as ?lat= and ?lng=:
GET /api/v1/customers/auth/nearby-stores?lat=24.7136&lng=46.6753
Without coordinates and without a signed-in customer who has a default address, those feeds answer 404. A signed-in customer may send them too; they win over the default address.
Cart. The app generates a UUID (version 4) once, stores it on the device, and sends it on every cart call while nobody is signed in:
POST /api/v1/customers/auth/cart/add
X-Cart-Token: 6f1c2a9e-3b7d-4c11-9a52-0d8e4f7b6a21
The cart endpoints and their responses are the same as for a signed-in customer; see Cart & checkout.
Signing in. Send the same X-Cart-Token header on register or login. The server moves the cart onto the account, after which the app drops the UUID and uses the bearer token alone.
Things to know:
- Do not send an
Authorizationheader when nobody is signed in. A request that carries a token is held to it: an expired or wrong-audience token answers401even on these open endpoints. - Everything about an account (addresses, favourites, orders, checkout, returns, notifications) still needs a customer token and answers
401without one. Show the login screen there. - An anonymous cart that is not touched for 30 days is deleted.
Moving an app off guest login#
Guest login used to create a throwaway customer for every visitor. It is retired and answers 410. An app that still calls it changes as follows:
- Remove the guest-login call and the stored guest token. On first launch, call the browsing endpoints with no
Authorizationheader. - Keep the visitor's location on the device and send it as
latandlngtonearby-stores,stores/popularand the threenearby-…-offer-productsfeeds. A guest can no longer save an address. - Generate a UUID, persist it, and send it as
X-Cart-Tokenoncart,cart/add,cart/update/{cartItem},cart/remove/{cartItem}andcart/clear. - Replace register guest with plain register, sending
X-Cart-Token. Registration now returns a newaccess_token; store it. Note the password rule is the plain one (8 characters). - Send
X-Cart-Tokenon login too, then delete the stored UUID after either call succeeds. Generate a fresh one after logout. - Treat
is_guestas alwaysfalse. Gate ordering on "is a token stored", and handle401from the account endpoints instead of the old guest403.
Drivers#
A driver who registers is inactive until a Tote admin activates the account. Logging in before that returns 422 with the message under errors.account, so show that message and keep the driver on a "pending review" screen.
Merchants and staff#
A merchant account is either a store owner or a staff member (manager or employee) attached to an owner and scoped to one store. Both log in through the same merchant login.
Store-scoped endpoints take a {store} in the URL. The owner and any staff member of that store can call them; anyone else gets 403.
An inactive merchant or staff account cannot log in (403). When an owner deactivates a staff member, that person's device tokens are removed, so they stop receiving pushes.
Logging out#
Logout revokes only the token used for the call. Other devices stay signed in.
Pass the device's device_id in the body so the server also forgets that device's push token; otherwise the device keeps receiving notifications for an account it signed out of.
POST /api/v1/customers/auth/logout
Authorization: Bearer <token>
{"device_id": "c3a1…"}
Logout endpoints: customer · driver · merchant.
Push notification tokens#
After login, register the device's FCM token so the server can push to it. Each audience has the same endpoint shape under its own prefix — profile/notification-token with token, platform (android, ios or web) and device_id:
Calling it again with the same device_id replaces the stored token, so call it whenever FCM rotates the token.