Tote developers
All guides

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 Authorization header when nobody is signed in. A request that carries a token is held to it: an expired or wrong-audience token answers 401 even on these open endpoints.
  • Everything about an account (addresses, favourites, orders, checkout, returns, notifications) still needs a customer token and answers 401 without 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:

  1. Remove the guest-login call and the stored guest token. On first launch, call the browsing endpoints with no Authorization header.
  2. Keep the visitor's location on the device and send it as lat and lng to nearby-stores, stores/popular and the three nearby-…-offer-products feeds. A guest can no longer save an address.
  3. Generate a UUID, persist it, and send it as X-Cart-Token on cart, cart/add, cart/update/{cartItem}, cart/remove/{cartItem} and cart/clear.
  4. Replace register guest with plain register, sending X-Cart-Token. Registration now returns a new access_token; store it. Note the password rule is the plain one (8 characters).
  5. Send X-Cart-Token on login too, then delete the stored UUID after either call succeeds. Generate a fresh one after logout.
  6. Treat is_guest as always false. Gate ordering on "is a token stored", and handle 401 from the account endpoints instead of the old guest 403.

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.