Conventions
Response envelope
Every JSON success or error looks like this:
{
"success": true,
"statusCode": 200,
"statusMessage": "SUCCESS",
"results": {},
"error": null
}On failure, results is null and error holds the payload (message and optional field errors):
{
"success": false,
"statusCode": 401,
"statusMessage": "AUTHENTICATION_REQUIRED",
"results": null,
"error": {
"message": "Authentication required"
}
}statusMessage is a stable enum. See Errors. Do not branch on localized message text.
Auth classes
| Label | Meaning |
|---|---|
| Public | No JWT required. Guests allowed. |
| Customer | Authorization: Bearer <accessToken> from OTP verify or refresh. Missing / expired → 401. Refresh once, then treat as logged out. |
| Staff | Authorization: Bearer <accessToken> from staff login or refresh. Missing / expired → 401. |
Staff + orders:read | GET fulfillment |
Staff + orders:update | POST fulfillment actions |
Customer attach is optional on catalog routes: a JWT, if present, is loaded, but guests can still browse and use a cart.
IDs and slugs
- Mongo ids are 24-char hex strings.
- Catalog reads by slug (
GET /v1/products/:slug). - Mutations that target a product usually take
productId. - Order paths use
:orderId.
Bilingual objects
Admin-shaped documents use:
{ "en": "Milk", "ar": "حليب" }Public catalog cards usually expose a single resolved name string for the request language.
SSE exceptions
These endpoints are not the JSON envelope:
GET /v1/notifications/sse—text/event-streamPOST /v1/assistant/messages—text/event-stream
Rate limits
The Public API applies Redis-backed rate limits. Assistant list is capped (60/min) and send-message is capped (20/min). Treat 429 + RATE_LIMITED / TOO_MANY_ATTEMPTS as back-off.
Idempotency
Fulfillment POSTs are not advertised as idempotent. Re-scan of an already picked line or a second ready will fail with a domain error. Refresh the order after each action.