User Migration
Locate is moving its users and their points from the legacy Shukah system to its own services. This module documents, for the mobile app, what replaced each Shukah API the app calls in the user and points areas, field by field, and what is still missing.
Background
Dated notes on how the migration got here. Facts about the services were read on 2026-10-01 and checked again by the parity audit of 2026-10-02; the list of what the app calls is from the app snapshot dated 2026-02-26.
- 2026-06-25 to 2026-08-04, auth built. A bulk copy of users, addresses and tokens from Shukah
into the auth service, then Shukah-style client routes under
/api/v1/clients/*(moved from/maeda/api/*on 2026-07-10), then live sync in both directions. - 2026-07-06, the plan. Move all users and user points off Shukah onto the Locate (Merchant) platform, with both systems running in parallel so the move is gradual and can be rolled back. The plan estimated 3 to 4 weeks.
- 2026-07-15 to 2026-09-22, points built. Ledger copy and live sync (2026-07-15), the read APIs (2026-07-16), then the point-order write path in slices (2026-09-17 to 2026-09-22).
- 2026-10-01, what the app calls. The app snapshot calls the Shukah APIs listed on
Migration progress. Most have a Locate route; a few do not, among them
account deletion (
GET /maeda/client/delete), which stays on Shukah. Please confirm the list against today's app. - 2026-10-01, the update popup. The app shows its "update the app" popup from four response
headers (
new_version,old_version,has_new_version,is_required_update). No Locate service sends them, so the popup would stop working after the switch, if Shukah sends them today (Not documented yet). See Verify token. - 2026-10-01, both point-order flags are on for dev. Paying a point order with points and with
a card are each switched on by a server flag. Both answered as on for dev; production is not
confirmed, and an off flag answers
403. See Point orders. - 2026-10-01, tokens. The app never calls exchange-token. A user holding a Shukah token who
calls Locate verify-token gets
401, and the app then deletes the stored token and signs the user out. See Tokens. - 2026-10-01, only dev is documented. Hosts, captures and checks are for dev. Whether each Locate route is deployed to production is a separate question; most pages say so.
- 2026-10-02, Shukah is being ported to Laravel. Dev Shukah answers with Laravel error bodies
(
typevalues such asIlluminate\…andSymfony\…). The "Implemented in Laravel" column in the team's production traffic sheet tracks that port, at the same URLs. It does not track the move to Locate. - 2026-10-02, the parity audit. The same input was sent to Shukah and to Locate on dev for every
route that could be called safely. Main findings: no single token works on both services (the auth
service's client routes,
/api/v1/clients/*, take only the Locate JWT, the order-v2 point routes only the Shukah token, see Tokens); the address copy between Locate and Shukah is broken in both directions (see Address book); and Locate's point-order create answers without the order id the app keeps (confirmed by a live call later that day), so the pay calls cannot find the order (see Point orders). A follow-up call the same day found that points spent on one side are not taken off the other (within seconds of the run; whether it arrives later was not checked), so the app must read the balance where it creates orders. Balances on dev: on 2026-10-01 the same client had"514405.6788"points on Locate and"128712.41"on Shukah; on 2026-10-02 Shukah had"514853.64": the Shukah dev data had changed in between. Locate was then 447.96 points lower, which fits Locate's feed from Shukah lagging about two days (an inference; see Points wallet). The pages now carry these results, each with its date.
Who calls what
Mobile app ──► Auth service ◄──────────► Shukah (legacy, /maeda/...)
│ login, token, profile, ▲ still serves delete-account,
│ address book │ restaurant orders, payments
│ │
└──────► order-v2 (/api/v2/client) ─────┤ order-v2 also calls Shukah
│ passes the point routes │ for some point calls
▼ │
Point service ◄────────────────┘ ledger sync (designed both ways; see Point orders)
(not exposed to the app)
The services also call each other: order-v2 and the point service ask the auth service
GET /api/v1/users who the caller is, so the app does not send a header for that. The details of the
syncs are on the pages under Details (for backend).
| Area | The app calls | Served by | Shukah before |
|---|---|---|---|
| Login and registration | reset-otp, verify-otp | Auth service | GET /maeda/api/reset-otp, GET /maeda/api/verify-otp |
| Session and profile | verify-token, client lookup, update profile, push setting, add device | Auth service | /maeda/api/verify-token, /maeda/client, PUT /maeda/client/{id}, /maeda/client/add-device-id |
| Address book | list, get, create, update, delete | Auth service | /maeda/address-book and /maeda/address-book/{id} |
| Token exchange | exchange-token (the app does not call it yet) | Auth service | Nothing: a Locate-only endpoint |
| Points | balance, history, FAQ, offers, occasions | order-v2, which passes them to the point service | /maeda/client/points, /maeda/client/point-history, /maeda/points-faq, /maeda/point-offer, /maeda/point-occasion |
| Point orders | create, read, description, pay by card or session | order-v2 and the point service | /maeda/point-order and the checkout-payment routes |
| Earn rates, gift-card code | point rates, gift-card code | order-v2 itself | /maeda/point-rate, /maeda/giftcard-code/use |
| Delete account | Not replaced | Stays on Shukah | GET /maeda/client/delete |
Migration progress has the full list with each route's status, and Shukah vs Locate sets each Shukah API against its replacement.
Base URLs
The app builds a URL from a base URL, a / and the endpoint. The Locate pages write curl
examples with $AUTH_BASE_URL, $ORDER_BASE_URL and $SHUKAH_BASE_URL, which are the host only.
The prefix goes in the path: /api/v1 on the auth host, /api/v2/client on the order-v2 host and
/maeda on Shukah.
Dev
| Service | Host | Prefix |
|---|---|---|
| Auth service | https://dev.authentication.locate.sa | /api/v1 |
| order-v2 | https://dev.v2.api.order.locate.sa | /api/v2/client |
| Point service | https://dev-point-api.locate.sa (the app does not call it) | /api/client |
| Shukah | https://dev-api.shukah.com | /maeda |
UAT
| Service | Host | Prefix |
|---|---|---|
| Auth service | Not documented yet — ask backend | Not documented yet — ask backend |
| order-v2 | https://dev.v2.api.order.locate.sa (the same as dev in the app's UAT settings) | /api/v2/client |
| Point service | Not documented yet — ask backend | Not documented yet — ask backend |
| Shukah | https://dev-api.shukah.com (the same as dev in the app's UAT settings) | /maeda |
Production
| Service | Host | Prefix |
|---|---|---|
| Auth service | Not documented yet — ask backend | Not documented yet — ask backend |
| order-v2 | https://v2.api.order.locate.sa | /api/v2/client |
| Point service | Not documented yet — ask backend | Not documented yet — ask backend |
| Shukah | https://api.shukah.com | /maeda |
The app has no base URL for the auth service in any of its environment settings today, so it needs one. The hosts above come from the app's own settings and the services' docs; the auth service's UAT and production hosts are in deployment settings that research did not see.
Tokens
- Shukah token. An opaque string. Shukah issues it at login.
- Locate token. A JWT that the auth service issues. It has no expiry and nothing revokes it.
Both go in Authorization: Bearer <token>. Which pages take which:
| Pages | Token |
|---|---|
| OTP login, Exchange token, and the add-device-id call on Profile and settings | None needed. |
| Verify token, Client lookup, Address book, and update profile and update setting on Profile and settings | A Locate JWT only. A Shukah token gets 401. |
| The points pages (Points wallet, Point history, Point offers, Point orders, Points FAQ) | A Shukah token only, on dev today. A Locate JWT gets 401 Invalid or expired token (captured on dev, 2026-10-02; on 2026-10-01 it still got 200). The earn-rate route is public. A page says when a route needs no token. |
| User detail | With x-auth-from: SHUKAH, dev passes the token on to Shukah, so a Shukah token works and a Locate JWT is expected to get 401 (inference, 2026-10-02: the point routes that call it refused the JWT; this route was not called with x-auth-from that day). The points routes depend on this call, which is why a Locate JWT fails there. |
So the app needs both tokens today: the Locate JWT for the auth service and the Shukah token for order-v2. Shukah vs Locate explains why, and why this matters when the app switches.
Getting a test token
The auth pages' Try it needs a Locate JWT in $TOKEN, except User detail,
which needs a Shukah token. The order-v2 points pages' Try it needs a Shukah token in $SHUKAH_TOKEN
today, because a Locate JWT gets 401 there (dev, 2026-10-02). Use dev only. There are two ways to
get a Locate JWT (A and B below), and one way to get a Shukah token from it (C).
A. Log in with an OTP
Two calls: reset-otp asks for the code, verify-otp trades the code for a token.
export AUTH_BASE_URL=https://dev.authentication.locate.sa
export PHONE='<test-mode-phone>' # placeholder: replace it with a test-mode number
curl -s -G "$AUTH_BASE_URL/api/v1/clients/reset-otp" --data-urlencode "contact_phone=$PHONE"
curl -s -G "$AUTH_BASE_URL/api/v1/clients/verify-otp" --data-urlencode "contact_phone=$PHONE" --data-urlencode "code=2022"
The second answer has the token in access_token. Keep it as $TOKEN.
reset-otp sends a real SMS unless the account is a test-mode account (account_mode: TEST).
A test-mode account gets the fixed code 2022 and no SMS. The service has no API to make an account
test-mode: backend sets it in the database. Ask backend for a test-mode number; whether one exists
on dev: Not documented yet.
The hard-coded number +966500000000 also accepts 2022, but the service still tries to send an
SMS to it, so do not use it. Never put a real person's number in a test.
Notes: a code lasts 3 minutes, and while one is still valid reset-otp sends nothing new (it
still answers {"status":true}). Backend can read the latest OTP through an admin debug route
that is protected by an API key; it is for backend only, so ask backend. See
OTP login for the full rules.
B. Exchange an existing Shukah token
If you already hold a Shukah token for a dev client, trade it for a Locate JWT. A Shukah token comes from the existing Shukah login; how to get one without the app: Not documented yet.
export AUTH_BASE_URL=https://dev.authentication.locate.sa
export SHUKAH_TOKEN='<shukah-token>'
curl -s -X POST "$AUTH_BASE_URL/api/v1/clients/exchange-token" -H "Content-Type: application/json" -d "{\"token\":\"$SHUKAH_TOKEN\",\"target_platform\":\"locate\"}"
The Locate JWT is in data.token. Keep it as $TOKEN.
This call writes: each time it stores a new token for the client and pushes it to Shukah. Do not send two for the same client in the same second. See Exchange token.
C. Get a Shukah token from a Locate JWT
The same exchange-token call with your Locate JWT and target_platform set to shukah returns the
Shukah token stored for that client:
curl -s -X POST "$AUTH_BASE_URL/api/v1/clients/exchange-token" -H "Content-Type: application/json" -d "{\"token\":\"$TOKEN\",\"target_platform\":\"shukah\"}".
The Shukah token is in data.token. Keep it as $SHUKAH_TOKEN. The JWT is checked first. The answer
can be {"status":200,"data":{}} when the stored Shukah token has no raw copy; then this way does not
work for that client (see Exchange token).
The variables the pages use
export AUTH_BASE_URL=https://dev.authentication.locate.sa
export ORDER_BASE_URL=https://dev.v2.api.order.locate.sa
export SHUKAH_BASE_URL=https://dev-api.shukah.com
export TOKEN='<locate-jwt>'
export SHUKAH_TOKEN='<shukah-token>'
Replace the two token placeholders with your own. Do not commit a real token to a file or paste it into a ticket or a chat.
How to read these pages
An endpoint page has this shape:
- Background and Feature: what the app does today on Shukah, and what the feature is, in plain words.
- One block per endpoint, in this order:
- the TL;DR box (headed "Replaces Shukah"): the Shukah API it replaces and its status; the verdict with the number of breaking and to-watch differences; one line per difference, 🔴 for breaking and 🟡 for to watch; the service that serves it, which tokens worked, how and when it was verified, and a link to the Shukah side. The Migration progress legend explains the verdicts and marks;
- Request and Response: tables of every field;
- Errors: the real bodies;
- Try it: a
curlusing the variables above; - Differences from Shukah: only real differences, each with its impact on the app. A route
with none says
Drop-in — no differences found, with the date.
- Watch out: at most three one-line warnings.
- A line, then Details (for backend): business rules, hazards and the Sources, which name the repository, commit and file for each fact.
The TL;DR box and the progress table use these statuses:
| Mark | Meaning |
|---|---|
| ✅ Replaced | A Locate route exists. |
| ❌ Not replaced | No Locate route exists yet. |
| ➖ Not migrating | The API stays on Shukah on purpose. |
| 🚩 flag | Implemented, but a switch (a server flag or an app setting) decides whether it is used. |
Migration progress adds a Dev check: one request to the Locate route on dev without a token, on the date shown. It shows that the route is deployed and answers, not that it works for a signed-in client.
Not documented yet means no code, doc or capture showed the answer. It is not a guess and not "no". Treat it as a question for the backend or mobile team.
Samples say where they come from: captured on dev on a date (personal data replaced with
illustrative values), or taken from a service's doc, with its path and date. Capture file names under Sources refer to the uncommitted 2026-10-01 dev capture set and the 2026-10-02 parity-audit capture set (names like points--shukah), which the docs author keeps; they are not in this repository.
An exception to the other modules. Most modules never restate field names or response shapes, because the service's rendered OpenAPI is the source of truth. The services here publish no OpenAPI, so these pages document the fields themselves, from the code. A field table can therefore go stale: check the commit named under Sources, and when a page and the service disagree, the service wins.
Tags
Pages carry tags. Each links to every page that has it.
| Tag | Meaning |
|---|---|
auth-service | Served by the auth service. The app calls it directly. |
order-v2 | Reached through order-v2's client API. |
point-service | Served by the point service, behind order-v2. |
authentication | Logging in, tokens and sessions. |
profile | The client's profile, settings and devices. |
address-book | The client's saved delivery addresses. |
points | Balance, history, offers, occasions and FAQ. |
point-orders | Buying or gifting points, and paying for them. |
shukah-replacement | A Locate endpoint that replaces a Shukah API. |
new-endpoint | A Locate endpoint with no Shukah equivalent. |
overview | Module-level pages: background, progress and the comparison. |
mobile | Written for the mobile app team. |
backend | Called by backend services, or written for backend engineers. |
client | Called by the client app with the client's own token. |
admin | Called from an admin tool, not by the client app. No page has this tag yet. |
server-to-server | Called by another backend service, not by an app. |
Status is not a tag. It lives in Migration progress.
Pages
Start here if you are switching the app: the Switch checklist.
- Switch checklist: every change the app must make to move from Shukah to Locate.
- Migration progress: every Shukah API the app calls, with its status and dev check.
- Shukah vs Locate: the Shukah side of each API, set against its replacement.
Auth service
- OTP login: log in or register with a phone number and a 4-digit code.
- Verify token: check the token and get the signed-in client's profile.
- Exchange token: trade a Shukah token for a Locate token, or the other way.
- User detail: resolve a token into a profile; the route order-v2 and the point service call.
- Client lookup: find a client by phone number.
- Address book: list, read, create, edit and delete saved addresses.
- Profile and settings: update the profile, switch push on or off, register a device.
Points
- Points wallet: balance and expiry, earn rates, gift-card code.
- Point history: the client's point transactions.
- Point offers: the packages a client can buy or gift, and the gift occasions.
- Point orders: create, read and pay a point gift order.
- Points FAQ: the questions and answers in English and Arabic.