{"info":{"name":"Mashhour API","description":"One API for the three Mashhour clients: the customer app (`/v1/customer`), the merchant app (`/v1/merchant`), and the admin control panel (`/v1/admin`).\n\n**Authentication.** Each namespace has its own opaque session token, sent as `Authorization: Bearer <token>`. Tokens are revocable server-side; a token from one namespace is rejected by the others. Routes without a lock are public: guests can call them, and some recognise a signed-in caller too.\n\n**Errors.** Every error has the same shape: `{ code, message, details?, requestId }`. Branch on `code`, which is stable; `message` is translated for display. For `VALIDATION_FAILED`, `details` lists each field problem as `{ field, code, message }`, where `code` is the rule that failed (e.g. `isSaudiMobileRequired` for an empty mobile, `isSaudiMobile` for an invalid one), and `message` is the first of them, ready to show, with the field's name in front when the text doesn't say it (\"Mobile number: A valid number must be entered.\").\n\n**Mobile numbers** are accepted as typed (`05XXXXXXXX`, `+9665…`, Arabic-Indic digits) and returned in E.164 (`+9665XXXXXXXX`).\n\n**Language.** Messages are Arabic by default. Send `Accept-Language: en` (or `?lang=en`) for English. When signed in, the account's saved language is used if the request names none. Error responses carry `Content-Language`.\n\n**Request IDs.** Every response has an `X-Request-Id` header; quote it when reporting a problem.","schema":"https://schema.getpostman.com/json/collection/v2.1.0/collection.json"},"variable":[{"key":"baseUrl","value":"https://mashhour-api.dev-moltaqa.cloud","description":"API origin, without /v1."},{"key":"customerToken","value":"","description":"Session token for /v1/customer; filled by its sign-in request."},{"key":"merchantToken","value":"","description":"Session token for /v1/merchant; filled by its sign-in request."},{"key":"adminToken","value":"","description":"Session token for /v1/admin; filled by its sign-in request."},{"key":"lang","value":"ar","description":"Accept-Language: ar or en."}],"item":[{"name":"Customer","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{customerToken}}","type":"string"}]},"item":[{"name":"Notifications","item":[{"name":"My notifications","request":{"method":"GET","description":"The Notifications screen (BRD): title, content, date and time, read or unread, newest first, in the request language, paged. `group` picks the icon; `target` is the page a tap opens (null: just show the text). Tapping one: `POST notifications/{id}/read`, then open the target. Empty: \"No notifications currently available\".","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/notifications","host":["{{baseUrl}}"],"path":["v1","customer","notifications"],"query":[{"key":"page","value":"1","disabled":true,"description":"1-based page number."},{"key":"limit","value":"20","disabled":true,"description":"Items per page (1–100)."}]}}},{"name":"Unread count","request":{"method":"GET","description":"For the bell badge (also on the home screen).","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/notifications/unread-count","host":["{{baseUrl}}"],"path":["v1","customer","notifications","unread-count"]}}},{"name":"Mark one read","request":{"method":"POST","description":"When the notification is opened (BRD). Idempotent; someone else’s is `NOT_FOUND`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/notifications/:id/read","host":["{{baseUrl}}"],"path":["v1","customer","notifications",":id","read"],"variable":[{"key":"id","value":"string"}]}}},{"name":"Mark all read","request":{"method":"POST","description":"Returns how many were unread.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/notifications/read-all","host":["{{baseUrl}}"],"path":["v1","customer","notifications","read-all"]}}}]},{"name":"Devices","item":[{"name":"Register this device for push","request":{"method":"PUT","description":"Call after signing in, whenever the messaging SDK gives a new token, and when the app's language changes (pushes are written in `locale`). One device per sign-in: calling again replaces it. Signing out (or any other end of the session) removes it.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/customer/devices","host":["{{baseUrl}}"],"path":["v1","customer","devices"]},"body":{"mode":"raw","raw":"{\n  \"token\": \"x\",\n  \"platform\": \"android\",\n  \"locale\": \"ar\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Stop push on this device","request":{"method":"DELETE","description":"Removes this sign-in's device; the session stays.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/devices","host":["{{baseUrl}}"],"path":["v1","customer","devices"]}}},{"name":"Send a test push","request":{"method":"POST","description":"Sends \"Test notification · إشعار تجريبي\" to this sign-in's registered device, to check the app's push setup. 5 per 10 minutes. `NOT_FOUND` until the device is registered.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/devices/test","host":["{{baseUrl}}"],"path":["v1","customer","devices","test"]}}}]},{"name":"Auth","item":[{"name":"Register","request":{"method":"POST","description":"Checks the registration form, saves it as an unverified sign-up and sends a 4-digit code to the mobile. The account works only once `register/verify` confirms the code; until then it can’t log in. Registering the same number again replaces the unverified form. `MOBILE_ALREADY_REGISTERED` means the app should offer login.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/customer/auth/register","host":["{{baseUrl}}"],"path":["v1","customer","auth","register"]},"body":{"mode":"raw","raw":"{\n  \"name\": \"سارة أحمد\",\n  \"mobile\": \"0551234567\",\n  \"email\": \"sara@example.com\",\n  \"acceptTerms\": true\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}}},{"name":"Resend the registration code","request":{"method":"POST","description":"Once `resendAfterSeconds` have passed: a new code for the `verificationId` from `register`, which stays the same.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/customer/auth/register/resend-otp","host":["{{baseUrl}}"],"path":["v1","customer","auth","register","resend-otp"]},"body":{"mode":"raw","raw":"{\n  \"verificationId\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}}},{"name":"Verify the registration","request":{"method":"POST","description":"The `verificationId` from `register` and the code: activates the account and starts a session (the app then shows Home).","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/customer/auth/register/verify","host":["{{baseUrl}}"],"path":["v1","customer","auth","register","verify"]},"body":{"mode":"raw","raw":"{\n  \"verificationId\": \"vrf_Zk3n0x8Qy1u4b2V7s9Lr5mT6pWcA0dEfGhIjKlMnOpQ\",\n  \"code\": \"1234\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["// Saves the new session token for the requests that follow.","if (pm.response.code >= 200 && pm.response.code < 300) {","  const token = pm.response.json()[\"accessToken\"];","  if (token) pm.collectionVariables.set(\"customerToken\", token);","}"]}}]},{"name":"Log in","request":{"method":"POST","description":"Sends a 4-digit code to a registered, active number and answers its `verificationId`. `MOBILE_NOT_REGISTERED` means the app should offer registration.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/customer/auth/login","host":["{{baseUrl}}"],"path":["v1","customer","auth","login"]},"body":{"mode":"raw","raw":"{\n  \"mobile\": \"0551234567\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}}},{"name":"Resend the login code","request":{"method":"POST","description":"Once `resendAfterSeconds` have passed: a new code for the `verificationId` from `login`, which stays the same.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/customer/auth/login/resend-otp","host":["{{baseUrl}}"],"path":["v1","customer","auth","login","resend-otp"]},"body":{"mode":"raw","raw":"{\n  \"verificationId\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}}},{"name":"Verify the login","request":{"method":"POST","description":"The `verificationId` from `login` and the code: starts a session.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/customer/auth/login/verify","host":["{{baseUrl}}"],"path":["v1","customer","auth","login","verify"]},"body":{"mode":"raw","raw":"{\n  \"verificationId\": \"vrf_Zk3n0x8Qy1u4b2V7s9Lr5mT6pWcA0dEfGhIjKlMnOpQ\",\n  \"code\": \"1234\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["// Saves the new session token for the requests that follow.","if (pm.response.code >= 200 && pm.response.code < 300) {","  const token = pm.response.json()[\"accessToken\"];","  if (token) pm.collectionVariables.set(\"customerToken\", token);","}"]}}]},{"name":"Log out","request":{"method":"POST","description":"Ends the session of the token sent; it stops working immediately. Other devices stay signed in.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/auth/logout","host":["{{baseUrl}}"],"path":["v1","customer","auth","logout"]}}}]},{"name":"Account","item":[{"name":"My account","request":{"method":"GET","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/account","host":["{{baseUrl}}"],"path":["v1","customer","account"]}}},{"name":"Change my language","request":{"method":"PATCH","description":"The \"Change language\" screen. Requests without `Accept-Language` or `?lang=` are answered in it from the next request, and SMS messages use it.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/customer/account","host":["{{baseUrl}}"],"path":["v1","customer","account"]},"body":{"mode":"raw","raw":"{\n  \"language\": \"en\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Delete my account","request":{"method":"DELETE","description":"Call after the app's confirmation dialog; there is no SMS code. Your details and files are removed, every device is signed out (this one included), and your number, email (and CR number) can register a new, empty account.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/account","host":["{{baseUrl}}"],"path":["v1","customer","account"]}}},{"name":"Send a code to a new number","request":{"method":"POST","description":"Step 1 of changing the mobile number: the code goes to the new number, and the answer carries the `verificationId` for step 2. Resend with `POST account/change-mobile/resend-otp`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/customer/account/change-mobile","host":["{{baseUrl}}"],"path":["v1","customer","account","change-mobile"]},"body":{"mode":"raw","raw":"{\n  \"mobile\": \"0551234567\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Resend the code to the new number","request":{"method":"POST","description":"Once `resendAfterSeconds` have passed: a new code for the `verificationId` from `account/change-mobile`, which stays the same.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/customer/account/change-mobile/resend-otp","host":["{{baseUrl}}"],"path":["v1","customer","account","change-mobile","resend-otp"]},"body":{"mode":"raw","raw":"{\n  \"verificationId\": \"string\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Change my mobile number","request":{"method":"POST","description":"Step 2: the `verificationId` and the code sent to the new number; swaps it in, and the old number no longer signs in. Every other device is signed out; this one stays signed in. A wrong code keeps the old number.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/customer/account/change-mobile/verify","host":["{{baseUrl}}"],"path":["v1","customer","account","change-mobile","verify"]},"body":{"mode":"raw","raw":"{\n  \"verificationId\": \"vrf_Zk3n0x8Qy1u4b2V7s9Lr5mT6pWcA0dEfGhIjKlMnOpQ\",\n  \"code\": \"1234\"\n}","options":{"raw":{"language":"json"}}}}}]},{"name":"Profile","item":[{"name":"My profile","request":{"method":"GET","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/profile","host":["{{baseUrl}}"],"path":["v1","customer","profile"]}}},{"name":"Save my profile","request":{"method":"PATCH","description":"Send only the fields that changed. A new photo goes in the `avatar` part (the old one is deleted). The mobile number is changed through `account/change-mobile`.\n\n**Files with the form:** send it as `multipart/form-data` (fields as text; lists and objects in brackets, e.g. `specifications[0][name]`) with `avatar` as file parts.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/profile","host":["{{baseUrl}}"],"path":["v1","customer","profile"]},"body":{"mode":"formdata","formdata":[{"key":"name","type":"text","value":"سارة أحمد","disabled":true,"description":"Letters and digits, 2–100 characters."},{"key":"email","type":"text","value":"sara@example.com","disabled":true,"description":"Unique; sent empty, removes it."},{"key":"avatarFileId","type":"text","value":"string","disabled":true,"description":"Leave out to keep the current file; send it empty to remove it. To replace it, send the `avatar` file instead."},{"key":"avatar","type":"file","description":"PNG, JPG, WEBP, up to 10 MB."}]}}}]},{"name":"Cities","item":[{"name":"List cities","request":{"method":"GET","description":"The active cities, for city pickers (ads, filters), each with its region, sorted by name in the request language. Public. Send the chosen `id`; an inactive or unknown city is refused with `CITY_NOT_AVAILABLE`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/cities","host":["{{baseUrl}}"],"path":["v1","customer","cities"]},"auth":{"type":"noauth"}}}]},{"name":"Categories","item":[{"name":"Main categories","request":{"method":"GET","description":"The active main categories (home \"Categories\"), in display order. Public. Open one with `/categories/{id}/children`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/categories","host":["{{baseUrl}}"],"path":["v1","customer","categories"]},"auth":{"type":"noauth"}}},{"name":"The whole category tree","request":{"method":"GET","description":"Active main categories with their active subcategories, for the ad form and filters. Public.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/categories/tree","host":["{{baseUrl}}"],"path":["v1","customer","categories","tree"]},"auth":{"type":"noauth"}}},{"name":"One category","request":{"method":"GET","description":"An active category with its `path` from the main category (breadcrumbs). Public. A hidden or unknown category is `NOT_FOUND`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/categories/:id","host":["{{baseUrl}}"],"path":["v1","customer","categories",":id"],"variable":[{"key":"id","value":"string"}]},"auth":{"type":"noauth"}}},{"name":"Subcategories","request":{"method":"GET","description":"The active subcategories of a main category. Empty (and `hasChildren: false` on the parent) means the app goes straight to its ads. Public. A hidden or unknown category is `NOT_FOUND`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/categories/:id/children","host":["{{baseUrl}}"],"path":["v1","customer","categories",":id","children"],"variable":[{"key":"id","value":"string"}]},"auth":{"type":"noauth"}}}]},{"name":"Settings","item":[{"name":"Read app settings","request":{"method":"GET","description":"The admin’s contact channels (More → Contact Us): only the configured ones are included (BRD); `phones` is always a list. Public and cached.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/settings","host":["{{baseUrl}}"],"path":["v1","customer","settings"]},"auth":{"type":"noauth"}}}]},{"name":"Content","item":[{"name":"Read a page","request":{"method":"GET","description":"About, Terms or Privacy (More menu, and the Terms link on sign-up), as rich text (HTML). Shared by both apps. When the text is empty in the request language, the other language is used. Public and cached.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/content/pages/:key","host":["{{baseUrl}}"],"path":["v1","customer","content","pages",":key"],"variable":[{"key":"key","value":"about"}]},"auth":{"type":"noauth"}}}]},{"name":"Contact","item":[{"name":"Contact Us screen","request":{"method":"GET","description":"The configured contact channels, and the types for a message and for a complaint, in the request language. Public.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/contact","host":["{{baseUrl}}"],"path":["v1","customer","contact"]},"auth":{"type":"noauth"}}},{"name":"Send a message or a complaint","request":{"method":"POST","description":"To the administration. `kind` picks the `type` list (`message` by default). Guests can send; with a valid token of this app the message is linked to the account, and a missing or expired token sends as a guest. At most 3 an hour per account (or mobile, for guests) and 5 an hour per network. On 201 the app shows `message` (\"Your message has been successfully sent\").","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/customer/contact/messages","host":["{{baseUrl}}"],"path":["v1","customer","contact","messages"]},"body":{"mode":"raw","raw":"{\n  \"kind\": \"message\",\n  \"type\": \"inquiry\",\n  \"name\": \"سارة العتيبي\",\n  \"email\": \"sara@example.com\",\n  \"mobile\": \"0551234567\",\n  \"message\": \"كيف أضيف إعلانًا لمتجري في قسم المطاعم؟\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}}}]},{"name":"Home","item":[{"name":"Home","request":{"method":"GET","description":"For guests and signed-in customers (BRD). Send `lat` and `lng` (the user’s or a chosen location) for nearest-first lists and distances. The active categories, the advertising space (the latest ads near you, D16), the 3 newest ads, and \"Mashhour ads\" for the `categoryId` tab (all when left out). `header` is null for guests. Only live ads appear.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/home","host":["{{baseUrl}}"],"path":["v1","customer","home"],"query":[{"key":"page","value":"1","disabled":true,"description":"1-based page number."},{"key":"limit","value":"20","disabled":true,"description":"Items per page (1–100)."},{"key":"categoryId","value":"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b","disabled":true,"description":"The \"Mashhour ads\" tab: a main category; all when left out."},{"key":"lat","value":"24.7136","disabled":true},{"key":"lng","value":"46.6753","disabled":true}]},"auth":{"type":"noauth"}}}]},{"name":"Ads","item":[{"name":"Browse and search ads","request":{"method":"GET","description":"Live ads as cards, for guests and customers. Filters: `categoryId` (a main category, or a subcategory), `q` (part of the title, up to 100 characters), `cityId`, `dateFrom`/`dateTo` (publish date, Saudi days). With `lat` and `lng` they are sorted nearest first (BRD) with `distanceKm`; else newest first (`sort=newest` forces it). No match: `items: []` (the app shows \"No results available\").","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/ads","host":["{{baseUrl}}"],"path":["v1","customer","ads"],"query":[{"key":"page","value":"1","disabled":true,"description":"1-based page number."},{"key":"limit","value":"20","disabled":true,"description":"Items per page (1–100)."},{"key":"categoryId","value":"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b","disabled":true,"description":"A main category or a subcategory."},{"key":"q","value":"مشويات","disabled":true,"description":"Part of the ad's title, up to 100 characters."},{"key":"cityId","value":"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b","disabled":true},{"key":"dateFrom","value":"string","disabled":true,"description":"Published on or after this Saudi day, `YYYY-MM-DD`."},{"key":"dateTo","value":"string","disabled":true,"description":"Published on or before this Saudi day, `YYYY-MM-DD`."},{"key":"sort","value":"nearest","disabled":true,"description":"`nearest` (the default with a location, BRD) or `newest` (the default\nwithout one). One of: `nearest`, `newest`."},{"key":"lat","value":"24.7136","disabled":true},{"key":"lng","value":"46.6753","disabled":true}]},"auth":{"type":"noauth"}}},{"name":"Ad details","request":{"method":"GET","description":"For guests and customers (BRD): title, categories, description, images and video, prices and discount, expiry, views, the store (opens `GET merchants/{id}`), call and WhatsApp, email, map location, `isFavorite` and `shareUrl` (for the share sheet and its QR code). Send `lat`/`lng` for `distanceKm`. Counts a view once per viewer per day. An ad that is gone, expired or hidden is `AD_NOT_AVAILABLE`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/ads/:id","host":["{{baseUrl}}"],"path":["v1","customer","ads",":id"],"query":[{"key":"page","value":"1","disabled":true,"description":"1-based page number."},{"key":"limit","value":"20","disabled":true,"description":"Items per page (1–100)."},{"key":"lat","value":"24.7136","disabled":true},{"key":"lng","value":"46.6753","disabled":true}],"variable":[{"key":"id","value":"string"}]},"auth":{"type":"noauth"}}},{"name":"Count a call, WhatsApp or share tap","request":{"method":"POST","description":"Send when the customer taps call, WhatsApp or share on an ad (guests too): the merchant’s statistics count each once per viewer per day (D2). Always 204, even for an ad that is no longer live, so a stale screen never shows an error; at most 60 an hour per network.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/customer/ads/:id/events","host":["{{baseUrl}}"],"path":["v1","customer","ads",":id","events"],"variable":[{"key":"id","value":"string"}]},"body":{"mode":"raw","raw":"{\n  \"type\": \"call\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}}},{"name":"A merchant’s activity page","request":{"method":"GET","description":"For guests and customers: the store, its contact and its live ads (nearest first with `lat`/`lng`). Opened from an ad, send `fromAdId`: the visit counts for that ad’s \"activity page visits\" once per viewer per day (BRD, D2). An inactive or deleted merchant is `MERCHANT_NOT_AVAILABLE`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/merchants/:id","host":["{{baseUrl}}"],"path":["v1","customer","merchants",":id"],"query":[{"key":"page","value":"1","disabled":true,"description":"1-based page number."},{"key":"limit","value":"20","disabled":true,"description":"Items per page (1–100)."},{"key":"lat","value":"24.7136","disabled":true},{"key":"lng","value":"46.6753","disabled":true},{"key":"fromAdId","value":"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b","disabled":true,"description":"The ad the page was opened from: the visit counts for that ad's\n\"activity page visits\" (BRD, D2)."}],"variable":[{"key":"id","value":"string"}]},"auth":{"type":"noauth"}}},{"name":"Report an ad","request":{"method":"POST","description":"The Report ad window (BRD): a reason and an explanation, both required. Signed-in customers only (D11). One open report per ad (`ALREADY_REPORTED`); at most 10 reports an hour. An ad customers can’t see is `AD_NOT_AVAILABLE`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/customer/ads/:id/reports","host":["{{baseUrl}}"],"path":["v1","customer","ads",":id","reports"],"variable":[{"key":"id","value":"string"}]},"body":{"mode":"raw","raw":"{\n  \"reason\": \"سعر مضلل\",\n  \"text\": \"السعر في الإعلان يختلف عن السعر في المحل.\"\n}","options":{"raw":{"language":"json"}}}}}]},{"name":"Favorites","item":[{"name":"My favorites","request":{"method":"GET","description":"The ads you saved that are live now (D19), newest saved first, as cards (`isFavorite: true`). Empty: the app shows its empty state. Signed-in customers only (BRD).","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/favorites","host":["{{baseUrl}}"],"path":["v1","customer","favorites"],"query":[{"key":"page","value":"1","disabled":true,"description":"1-based page number."},{"key":"limit","value":"20","disabled":true,"description":"Items per page (1–100)."}]}}},{"name":"Save an ad","request":{"method":"PUT","description":"The heart on a card or the details. Saving again changes nothing. Only a live ad can be saved (`AD_NOT_AVAILABLE`). Guests get 401 (sign in first, BRD).","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/favorites/:adId","host":["{{baseUrl}}"],"path":["v1","customer","favorites",":adId"],"variable":[{"key":"adId","value":"string"}]}}},{"name":"Remove an ad from favorites","request":{"method":"DELETE","description":"Removing again changes nothing; the ad itself is never touched (BRD).","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/favorites/:adId","host":["{{baseUrl}}"],"path":["v1","customer","favorites",":adId"],"variable":[{"key":"adId","value":"string"}]}}}]}]},{"name":"Merchant","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{merchantToken}}","type":"string"}]},"item":[{"name":"Notifications","item":[{"name":"My notifications","request":{"method":"GET","description":"The Notifications screen (BRD): title, content, date and time, read or unread, newest first, in the request language, paged. `group` picks the icon; `target` is the page a tap opens (null: just show the text). Tapping one: `POST notifications/{id}/read`, then open the target. Empty: \"No notifications currently available\".","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/notifications","host":["{{baseUrl}}"],"path":["v1","merchant","notifications"],"query":[{"key":"page","value":"1","disabled":true,"description":"1-based page number."},{"key":"limit","value":"20","disabled":true,"description":"Items per page (1–100)."}]}}},{"name":"Unread count","request":{"method":"GET","description":"For the bell badge (also on the home screen).","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/notifications/unread-count","host":["{{baseUrl}}"],"path":["v1","merchant","notifications","unread-count"]}}},{"name":"Mark one read","request":{"method":"POST","description":"When the notification is opened (BRD). Idempotent; someone else’s is `NOT_FOUND`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/notifications/:id/read","host":["{{baseUrl}}"],"path":["v1","merchant","notifications",":id","read"],"variable":[{"key":"id","value":"string"}]}}},{"name":"Mark all read","request":{"method":"POST","description":"Returns how many were unread.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/notifications/read-all","host":["{{baseUrl}}"],"path":["v1","merchant","notifications","read-all"]}}}]},{"name":"Devices","item":[{"name":"Register this device for push","request":{"method":"PUT","description":"Call after signing in, whenever the messaging SDK gives a new token, and when the app's language changes (pushes are written in `locale`). One device per sign-in: calling again replaces it. Signing out (or any other end of the session) removes it.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/merchant/devices","host":["{{baseUrl}}"],"path":["v1","merchant","devices"]},"body":{"mode":"raw","raw":"{\n  \"token\": \"x\",\n  \"platform\": \"android\",\n  \"locale\": \"ar\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Stop push on this device","request":{"method":"DELETE","description":"Removes this sign-in's device; the session stays.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/devices","host":["{{baseUrl}}"],"path":["v1","merchant","devices"]}}},{"name":"Send a test push","request":{"method":"POST","description":"Sends \"Test notification · إشعار تجريبي\" to this sign-in's registered device, to check the app's push setup. 5 per 10 minutes. `NOT_FOUND` until the device is registered.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/devices/test","host":["{{baseUrl}}"],"path":["v1","merchant","devices","test"]}}}]},{"name":"Auth","item":[{"name":"Register","request":{"method":"POST","description":"The registration form as `multipart/form-data`: the fields as text, and the files as parts `storeLogo` (optional), `crImage` (optional) and `workDocument` (needed when no `crNumber` is given). Saves the application unverified and sends a 4-digit code to the mobile; `register/verify` confirms it. A rejected merchant registers again with the same mobile: the new application replaces the rejected one once verified, and is reviewed again.\n\n**Files with the form:** send it as `multipart/form-data` (fields as text; lists and objects in brackets, e.g. `specifications[0][name]`) with `storeLogo`, `crImage`, `workDocument` as file parts.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/auth/register","host":["{{baseUrl}}"],"path":["v1","merchant","auth","register"]},"body":{"mode":"formdata","formdata":[{"key":"name","type":"text","value":"محمد عبدالله","description":"Letters and numbers only, 2–100 characters."},{"key":"username","type":"text","value":"albait.alshami","description":"Used to log in: 3–30 English letters, numbers, `.` or `_`; unique,"},{"key":"password","type":"text","value":"mashhour2026","description":"At least 8 characters with a number."},{"key":"passwordConfirmation","type":"text","value":"mashhour2026","description":"Must equal `password`."},{"key":"mobile","type":"text","value":"0551234567","description":"Saudi mobile, `05XXXXXXXX`; a code is sent to it."},{"key":"email","type":"text","value":"store@example.com","description":"Unique, at most 100 characters."},{"key":"storeName","type":"text","value":"مطعم البيت الشامي للمشاويات","description":"2–100 characters."},{"key":"crNumber","type":"text","value":"1010123456","disabled":true,"description":"Commercial registration number, 10 digits, unique. Required unless a"},{"key":"acceptTerms","type":"text","value":"true","description":"The Terms and Conditions checkbox; must be `true`."},{"key":"storeLogo","type":"file","description":"PNG, JPG, WEBP, up to 10 MB."},{"key":"crImage","type":"file","description":"PNG, JPG, WEBP, up to 10 MB."},{"key":"workDocument","type":"file","description":"PDF, PNG, JPG, up to 5 MB."}]},"auth":{"type":"noauth"}}},{"name":"Resend the registration code","request":{"method":"POST","description":"Only the `verificationId`, after the 60-second countdown.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/merchant/auth/register/resend-otp","host":["{{baseUrl}}"],"path":["v1","merchant","auth","register","resend-otp"]},"body":{"mode":"raw","raw":"{\n  \"verificationId\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}}},{"name":"Verify the registration","request":{"method":"POST","description":"Confirms the mobile. The application is then `under_review` until an admin approves it (the app shows the \"under review\" screen); no session is issued.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/merchant/auth/register/verify","host":["{{baseUrl}}"],"path":["v1","merchant","auth","register","verify"]},"body":{"mode":"raw","raw":"{\n  \"verificationId\": \"vrf_Zk3n0x8Qy1u4b2V7s9Lr5mT6pWcA0dEfGhIjKlMnOpQ\",\n  \"code\": \"1234\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}}},{"name":"Log in","request":{"method":"POST","description":"Username (any case) and password. Only approved merchants get a session; `ACCOUNT_UNDER_REVIEW` and `ACCOUNT_REJECTED` (with the reason in `details`) tell the app which screen to show. Repeated failures are refused for a while (`TOO_MANY_REQUESTS`, `Retry-After`).","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/merchant/auth/login","host":["{{baseUrl}}"],"path":["v1","merchant","auth","login"]},"body":{"mode":"raw","raw":"{\n  \"username\": \"albait.alshami\",\n  \"password\": \"mashhour2026\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["// Saves the new session token for the requests that follow.","if (pm.response.code >= 200 && pm.response.code < 300) {","  const token = pm.response.json()[\"accessToken\"];","  if (token) pm.collectionVariables.set(\"merchantToken\", token);","}"]}}]},{"name":"Forgot password: send a code","request":{"method":"POST","description":"The registered mobile; a code is sent to it. Then `password/forgot/verify`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/merchant/auth/password/forgot","host":["{{baseUrl}}"],"path":["v1","merchant","auth","password","forgot"]},"body":{"mode":"raw","raw":"{\n  \"mobile\": \"0551234567\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}}},{"name":"Forgot password: resend the code","request":{"method":"POST","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/merchant/auth/password/forgot/resend-otp","host":["{{baseUrl}}"],"path":["v1","merchant","auth","password","forgot","resend-otp"]},"body":{"mode":"raw","raw":"{\n  \"verificationId\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}}},{"name":"Forgot password: check the code","request":{"method":"POST","description":"A right code returns a one-time `resetToken`, valid for 10 minutes, for `password/reset`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/merchant/auth/password/forgot/verify","host":["{{baseUrl}}"],"path":["v1","merchant","auth","password","forgot","verify"]},"body":{"mode":"raw","raw":"{\n  \"verificationId\": \"vrf_Zk3n0x8Qy1u4b2V7s9Lr5mT6pWcA0dEfGhIjKlMnOpQ\",\n  \"code\": \"1234\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}}},{"name":"Set a new password","request":{"method":"POST","description":"Uses the `resetToken` once. Every session of the merchant is signed out; they log in with the new password.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/merchant/auth/password/reset","host":["{{baseUrl}}"],"path":["v1","merchant","auth","password","reset"]},"body":{"mode":"raw","raw":"{\n  \"resetToken\": \"string\",\n  \"password\": \"mashhour2027\",\n  \"passwordConfirmation\": \"mashhour2027\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}}},{"name":"Log out","request":{"method":"POST","description":"Ends the session of the token sent; it stops working immediately. Other devices stay signed in.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/auth/logout","host":["{{baseUrl}}"],"path":["v1","merchant","auth","logout"]}}}]},{"name":"Account","item":[{"name":"My account","request":{"method":"GET","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/account","host":["{{baseUrl}}"],"path":["v1","merchant","account"]}}},{"name":"Change my language","request":{"method":"PATCH","description":"The \"Change language\" screen. Requests without `Accept-Language` or `?lang=` are answered in it from the next request, and SMS messages use it.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/merchant/account","host":["{{baseUrl}}"],"path":["v1","merchant","account"]},"body":{"mode":"raw","raw":"{\n  \"language\": \"en\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Delete my account","request":{"method":"DELETE","description":"Call after the app's confirmation dialog; there is no SMS code. Your details and files are removed, every device is signed out (this one included), and your number, email (and CR number) can register a new, empty account.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/account","host":["{{baseUrl}}"],"path":["v1","merchant","account"]}}},{"name":"Send a code to a new number","request":{"method":"POST","description":"Step 1 of changing the mobile number: the code goes to the new number, and the answer carries the `verificationId` for step 2. Resend with `POST account/change-mobile/resend-otp`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/merchant/account/change-mobile","host":["{{baseUrl}}"],"path":["v1","merchant","account","change-mobile"]},"body":{"mode":"raw","raw":"{\n  \"mobile\": \"0551234567\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Resend the code to the new number","request":{"method":"POST","description":"Once `resendAfterSeconds` have passed: a new code for the `verificationId` from `account/change-mobile`, which stays the same.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/merchant/account/change-mobile/resend-otp","host":["{{baseUrl}}"],"path":["v1","merchant","account","change-mobile","resend-otp"]},"body":{"mode":"raw","raw":"{\n  \"verificationId\": \"string\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Change my mobile number","request":{"method":"POST","description":"Step 2: the `verificationId` and the code sent to the new number; swaps it in, and the old number no longer signs in. Every other device is signed out; this one stays signed in. A wrong code keeps the old number.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/merchant/account/change-mobile/verify","host":["{{baseUrl}}"],"path":["v1","merchant","account","change-mobile","verify"]},"body":{"mode":"raw","raw":"{\n  \"verificationId\": \"vrf_Zk3n0x8Qy1u4b2V7s9Lr5mT6pWcA0dEfGhIjKlMnOpQ\",\n  \"code\": \"1234\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Change my password","request":{"method":"POST","description":"The current password and the new one. This device stays signed in; every other device is signed out.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/merchant/account/change-password","host":["{{baseUrl}}"],"path":["v1","merchant","account","change-password"]},"body":{"mode":"raw","raw":"{\n  \"currentPassword\": \"string\",\n  \"password\": \"mashhour2027\",\n  \"passwordConfirmation\": \"mashhour2027\"\n}","options":{"raw":{"language":"json"}}}}}]},{"name":"Profile","item":[{"name":"My profile","request":{"method":"GET","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/profile","host":["{{baseUrl}}"],"path":["v1","merchant","profile"]}}},{"name":"Save my profile","request":{"method":"PATCH","description":"Send only the fields that changed. New documents go in the parts `storeLogo`, `crImage` and `workDocument` (the replaced ones are deleted). A CR number or a work document must remain. The username can’t change; the mobile changes through `account/change-mobile`.\n\n**Files with the form:** send it as `multipart/form-data` (fields as text; lists and objects in brackets, e.g. `specifications[0][name]`) with `storeLogo`, `crImage`, `workDocument` as file parts.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/profile","host":["{{baseUrl}}"],"path":["v1","merchant","profile"]},"body":{"mode":"formdata","formdata":[{"key":"name","type":"text","value":"محمد عبدالله","disabled":true,"description":"Letters and digits, 2–100 characters."},{"key":"email","type":"text","value":"store@example.com","disabled":true,"description":"Required and unique."},{"key":"storeName","type":"text","value":"مطعم البيت الشامي للمشاويات","disabled":true,"description":"2–100 characters."},{"key":"crNumber","type":"text","value":"1010123456","disabled":true,"description":"10 digits, unique; sent empty, removes it (then a work document must"},{"key":"storeLogoFileId","type":"text","value":"string","disabled":true,"description":"Leave out to keep the current file; send it empty to remove it. To replace it, send the `storeLogo` file instead."},{"key":"crImageFileId","type":"text","value":"string","disabled":true,"description":"Leave out to keep the current file; send it empty to remove it. To replace it, send the `crImage` file instead."},{"key":"workDocumentFileId","type":"text","value":"string","disabled":true,"description":"Leave out to keep the current file; send it empty to remove it. To replace it, send the `workDocument` file instead."},{"key":"storeLogo","type":"file","description":"PNG, JPG, WEBP, up to 10 MB."},{"key":"crImage","type":"file","description":"PNG, JPG, WEBP, up to 10 MB."},{"key":"workDocument","type":"file","description":"PDF, PNG, JPG, up to 5 MB."}]}}}]},{"name":"Cities","item":[{"name":"List cities","request":{"method":"GET","description":"The active cities, for city pickers (ads, filters), each with its region, sorted by name in the request language. Public. Send the chosen `id`; an inactive or unknown city is refused with `CITY_NOT_AVAILABLE`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/cities","host":["{{baseUrl}}"],"path":["v1","merchant","cities"]},"auth":{"type":"noauth"}}}]},{"name":"Categories","item":[{"name":"Main categories","request":{"method":"GET","description":"The active main categories (home \"Categories\"), in display order. Public. Open one with `/categories/{id}/children`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/categories","host":["{{baseUrl}}"],"path":["v1","merchant","categories"]},"auth":{"type":"noauth"}}},{"name":"The whole category tree","request":{"method":"GET","description":"Active main categories with their active subcategories, for the ad form and filters. Public.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/categories/tree","host":["{{baseUrl}}"],"path":["v1","merchant","categories","tree"]},"auth":{"type":"noauth"}}},{"name":"One category","request":{"method":"GET","description":"An active category with its `path` from the main category (breadcrumbs). Public. A hidden or unknown category is `NOT_FOUND`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/categories/:id","host":["{{baseUrl}}"],"path":["v1","merchant","categories",":id"],"variable":[{"key":"id","value":"string"}]},"auth":{"type":"noauth"}}},{"name":"Subcategories","request":{"method":"GET","description":"The active subcategories of a main category. Empty (and `hasChildren: false` on the parent) means the app goes straight to its ads. Public. A hidden or unknown category is `NOT_FOUND`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/categories/:id/children","host":["{{baseUrl}}"],"path":["v1","merchant","categories",":id","children"],"variable":[{"key":"id","value":"string"}]},"auth":{"type":"noauth"}}}]},{"name":"Settings","item":[{"name":"Read app settings","request":{"method":"GET","description":"The admin’s contact channels (More → Contact Us): only the configured ones are included (BRD); `phones` is always a list. Public and cached.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/settings","host":["{{baseUrl}}"],"path":["v1","merchant","settings"]},"auth":{"type":"noauth"}}}]},{"name":"Content","item":[{"name":"Read a page","request":{"method":"GET","description":"About, Terms or Privacy (More menu, and the Terms link on sign-up), as rich text (HTML). Shared by both apps. When the text is empty in the request language, the other language is used. Public and cached.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/content/pages/:key","host":["{{baseUrl}}"],"path":["v1","merchant","content","pages",":key"],"variable":[{"key":"key","value":"about"}]},"auth":{"type":"noauth"}}}]},{"name":"Contact","item":[{"name":"Contact Us screen","request":{"method":"GET","description":"The configured contact channels, and the types for a message and for a complaint, in the request language. Public.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/contact","host":["{{baseUrl}}"],"path":["v1","merchant","contact"]},"auth":{"type":"noauth"}}},{"name":"Send a message or a complaint","request":{"method":"POST","description":"To the administration. `kind` picks the `type` list (`message` by default). Guests can send; with a valid token of this app the message is linked to the account, and a missing or expired token sends as a guest. At most 3 an hour per account (or mobile, for guests) and 5 an hour per network. On 201 the app shows `message` (\"Your message has been successfully sent\").","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/merchant/contact/messages","host":["{{baseUrl}}"],"path":["v1","merchant","contact","messages"]},"body":{"mode":"raw","raw":"{\n  \"kind\": \"message\",\n  \"type\": \"inquiry\",\n  \"name\": \"سارة العتيبي\",\n  \"email\": \"sara@example.com\",\n  \"mobile\": \"0551234567\",\n  \"message\": \"كيف أضيف إعلانًا لمتجري في قسم المطاعم؟\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}}}]},{"name":"Payments","item":[{"name":"Check a payment","request":{"method":"GET","description":"The status of one of your payments (a top-up, a subscription or an ad fee). When the result of a payment is unclear (a timeout, the app closed during checkout), check it here before trying again (BRD); retry only after `failed` or `cancelled`. Anyone else’s payment is `NOT_FOUND`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/payments/:id","host":["{{baseUrl}}"],"path":["v1","merchant","payments",":id"],"variable":[{"key":"id","value":"string"}]}}}]},{"name":"Wallet","item":[{"name":"My wallet","request":{"method":"GET","description":"The balance and the transaction log (BRD: amount, date and time, status, balance after), newest first: top-ups, ad fees and package subscriptions.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/wallet","host":["{{baseUrl}}"],"path":["v1","merchant","wallet"],"query":[{"key":"page","value":"1","disabled":true,"description":"1-based page number."},{"key":"limit","value":"20","disabled":true,"description":"Items per page (1–100)."}]}}},{"name":"Top up the wallet","request":{"method":"POST","description":"Pays `amount` through the gateway (PayMob). The balance is credited once the payment is confirmed: with the fake gateway at once; with PayMob, open `payment.checkoutUrl` and then check `GET payments/{id}`. Send an `Idempotency-Key` so a retry after a timeout charges nothing more.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/merchant/wallet/topups","host":["{{baseUrl}}"],"path":["v1","merchant","wallet","topups"]},"body":{"mode":"raw","raw":"{\n  \"amount\": 100\n}","options":{"raw":{"language":"json"}}}}}]},{"name":"Packages","item":[{"name":"Packages","request":{"method":"GET","description":"The active packages, each with its price, duration and benefits (BRD); `isCurrent` marks the package of your active subscription. `walletBalance` is shown before you confirm a subscription.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/packages","host":["{{baseUrl}}"],"path":["v1","merchant","packages"]}}}]},{"name":"Subscriptions","item":[{"name":"My current subscription","request":{"method":"GET","description":"The active subscription: package, status, start and end dates, remaining days and benefits (BRD). `subscription: null` means none (\"No active subscription\", with \"View packages\"). `trial` is the free trial (Figma’s free-package card). Reading never creates or charges anything.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/subscriptions/current","host":["{{baseUrl}}"],"path":["v1","merchant","subscriptions","current"]}}},{"name":"Subscription history","request":{"method":"GET","description":"Your current and past subscriptions, newest first (BRD: package, status, start, end, fee). An expired one has `canRenew` (the \"Renew subscription\" button opens the packages). Empty: \"No previous subscriptions\".","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/subscriptions","host":["{{baseUrl}}"],"path":["v1","merchant","subscriptions"],"query":[{"key":"page","value":"1","disabled":true,"description":"1-based page number."},{"key":"limit","value":"20","disabled":true,"description":"Items per page (1–100)."}]}}},{"name":"Subscribe to a package","request":{"method":"POST","description":"After the confirm dialog (package and fee). Only one active subscription at a time (`SUBSCRIPTION_ALREADY_ACTIVE`); renewing is this call once the last one expired. `wallet`: the fee is deducted and the subscription starts now, or `INSUFFICIENT_BALANCE` with nothing deducted (offer a top-up). `gateway`: open `payment.checkoutUrl` when set; the subscription starts when the payment is confirmed (at once with the fake gateway). Send an `Idempotency-Key` so a retry never pays twice.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/merchant/subscriptions","host":["{{baseUrl}}"],"path":["v1","merchant","subscriptions"]},"body":{"mode":"raw","raw":"{\n  \"packageId\": \"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b\",\n  \"method\": \"wallet\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"What publishing an ad costs","request":{"method":"GET","description":"Checked in the BRD’s order before an ad is created or reposted: `trial` (free during the free trial), `subscription` (free with an active package), or `fee_required` (pay `fee` from the wallet or by card). The ad form shows it up front; publishing checks it again.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/publish-gate","host":["{{baseUrl}}"],"path":["v1","merchant","publish-gate"]}}}]},{"name":"Ads","item":[{"name":"My ads","request":{"method":"GET","description":"Your ads, newest first, as cards (BRD: main image, title, category, a short description, the location as a Google Maps link, price): filter by `status` (the tabs) and main `categoryId`. An active ad past its expiry is in the `expired` tab. Empty: \"There are no ads currently\".","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/ads","host":["{{baseUrl}}"],"path":["v1","merchant","ads"],"query":[{"key":"page","value":"1","disabled":true,"description":"1-based page number."},{"key":"limit","value":"20","disabled":true,"description":"Items per page (1–100)."},{"key":"status","value":"active","disabled":true,"description":"One tab; all of them when left out. One of: `active`, `disabled`, `rejected`, `pending_review`, `expired`."},{"key":"categoryId","value":"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b","disabled":true,"description":"Ads of one main category."}]}}},{"name":"Create an ad","request":{"method":"POST","description":"The Create ad form as `multipart/form-data`: the fields as text (`subcategoryIds[]` repeated) and the files as parts `images` (1–5: JPG, PNG or WEBP) and `video` (optional, one MP4 or MOV, up to 100 MB). Publishing is checked first (BRD; `GET publish-gate`): free during the trial or with a subscription; otherwise the ad fee, paid with `paymentMethod` `wallet` (`INSUFFICIENT_BALANCE` creates nothing) or `gateway` (open `payment.checkoutUrl` when set; the ad is created once the payment succeeds). Without `paymentMethod` it is `AD_FEE_REQUIRED`. The new ad then waits for review (\"Your ad has been sent for review!\"). Send an `Idempotency-Key` so a retry never creates or charges twice.\n\n**Files with the form:** send it as `multipart/form-data` (fields as text; lists and objects in brackets, e.g. `specifications[0][name]`) with `images`, `video` as file parts.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/ads","host":["{{baseUrl}}"],"path":["v1","merchant","ads"]},"body":{"mode":"formdata","formdata":[{"key":"title","type":"text","value":"عرض المشويات المشكلة لشخصين","description":"Up to 100 characters (BRD)."},{"key":"description","type":"text","value":"مشويات مشكلة مع مقبلات وخبز طازج، العرض ساري حتى نهاية الشهر.","description":"Up to 500 characters; line breaks kept."},{"key":"price","type":"text","value":"50","description":"SAR, at most two decimals."},{"key":"previousPrice","type":"text","value":"60","disabled":true,"description":"The price before the discount, SAR (D3); above `price`. The discount"},{"key":"categoryId","type":"text","value":"string","description":"The main category (`GET categories`)."},{"key":"subcategoryIds[]","type":"text","value":"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b","disabled":true,"description":"One or more of the main category's subcategories (D4); required when"},{"key":"cityId","type":"text","value":"string","description":"`GET cities`."},{"key":"latitude","type":"text","value":"24.7136","description":"The map pin (BRD: the ad's location)."},{"key":"longitude","type":"text","value":"46.6753"},{"key":"address","type":"text","value":"حي العليا، طريق الملك فهد","disabled":true,"description":"The address line under the map; optional, up to 200 characters."},{"key":"contactMobile","type":"text","value":"0551234567","description":"The phone customers call (BRD: required, Saudi `05XXXXXXXX`)."},{"key":"contactEmail","type":"text","value":"store@example.com","disabled":true,"description":"Optional (Figma)."},{"key":"expiresOn","type":"text","value":"2026-12-31","disabled":true,"description":"The last day the ad runs, `YYYY-MM-DD` (Saudi calendar), today or"},{"key":"paymentMethod","type":"text","value":"wallet","disabled":true,"description":"How to pay the ad fee when there is no trial or subscription"},{"key":"images","type":"file","description":"PNG, JPG, WEBP, up to 5 MB. Up to 5, repeat the part."},{"key":"video","type":"file","description":"MP4, MOV, up to 100 MB."}]}}},{"name":"One of my ads","request":{"method":"GET","description":"The ad details screen (BRD): prices and discount, categories, status and expiry, images and video, the store’s contact, description, map location, the statistics cards (views, profile visits, call and WhatsApp clicks, shares, saves), and the `actions` its status allows. Another merchant’s or a deleted ad is `NOT_FOUND`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/ads/:id","host":["{{baseUrl}}"],"path":["v1","merchant","ads",":id"],"variable":[{"key":"id","value":"string"}]}}},{"name":"Edit an ad","request":{"method":"PATCH","description":"As `multipart/form-data`, only the fields that change, with the create rules. Images: `imageFileIds[]` lists the images to keep (their `images[].id`), in order, and new `images` parts are added after them (1–5 in all); send neither to leave them. `videoFileId` sent empty removes the video; a `video` part replaces it. Changing the title, description, prices, categories or media sends the ad back to review (D1; `message` says so). Not for a disabled ad (`AD_STATUS_INVALID`). A refused edit saves nothing.\n\n**Files with the form:** send it as `multipart/form-data` (fields as text; lists and objects in brackets, e.g. `specifications[0][name]`) with `images`, `video` as file parts.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/ads/:id","host":["{{baseUrl}}"],"path":["v1","merchant","ads",":id"],"variable":[{"key":"id","value":"string"}]},"body":{"mode":"formdata","formdata":[{"key":"title","type":"text","value":"عرض المشويات المشكلة لأربعة أشخاص","disabled":true},{"key":"description","type":"text","value":"string","disabled":true},{"key":"price","type":"text","value":"0","disabled":true,"description":"SAR."},{"key":"previousPrice","type":"text","value":"0","disabled":true,"description":"SAR; sent empty, removes the discount."},{"key":"categoryId","type":"text","value":"string","disabled":true},{"key":"subcategoryIds[]","type":"text","value":"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b","disabled":true,"description":"Replaces the subcategories; they must belong to the main category."},{"key":"cityId","type":"text","value":"string","disabled":true},{"key":"latitude","type":"text","value":"0","disabled":true},{"key":"longitude","type":"text","value":"0","disabled":true},{"key":"address","type":"text","value":"string","disabled":true,"description":"Sent empty, removes it."},{"key":"contactMobile","type":"text","value":"string","disabled":true},{"key":"contactEmail","type":"text","value":"string","disabled":true,"description":"Sent empty, removes it."},{"key":"expiresOn","type":"text","value":"string","disabled":true,"description":"`YYYY-MM-DD`, today or later."},{"key":"imageFileIds[]","type":"text","value":"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b","disabled":true,"description":"The current images to keep, in order (their ids; repeat `imageFileIds[]`). New `images` files are added after them; any not listed is removed. Leave both out to keep them as they are."},{"key":"videoFileId","type":"text","value":"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b","disabled":true,"description":"Leave out to keep the current file; send it empty to remove it. To replace it, send the `video` file instead."},{"key":"images","type":"file","description":"PNG, JPG, WEBP, up to 5 MB. Up to 5, repeat the part."},{"key":"video","type":"file","description":"MP4, MOV, up to 100 MB."}]}}},{"name":"Delete an ad","request":{"method":"DELETE","description":"After the app’s confirmation (BRD). The ad disappears from every list and its images and video are deleted; it can’t be restored.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/ads/:id","host":["{{baseUrl}}"],"path":["v1","merchant","ads",":id"],"variable":[{"key":"id","value":"string"}]}}},{"name":"Repost an expired ad","request":{"method":"POST","description":"The \"Repost ad\" button of an expired ad (`actions.canRepost`), with a new expiry day (30 days by default). Checked like creating (BRD): free in the trial or with a subscription, else the ad fee with `paymentMethod` (`AD_FEE_REQUIRED` without one). The ad goes back live, or to review if its content was edited while expired. Any other status is `AD_STATUS_INVALID`. Send an `Idempotency-Key` so a retry never pays twice.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/merchant/ads/:id/repost","host":["{{baseUrl}}"],"path":["v1","merchant","ads",":id","repost"],"variable":[{"key":"id","value":"string"}]},"body":{"mode":"raw","raw":"{\n  \"expiresOn\": \"2027-01-31\",\n  \"paymentMethod\": \"wallet\"\n}","options":{"raw":{"language":"json"}}}}}]},{"name":"Home","item":[{"name":"Home","request":{"method":"GET","description":"The header (name, store logo, unread notifications), the statistics cards summed over your ads (BRD: total ads, views, activity page visits, shares, saves), your 3 latest ads, and how publishing is covered (trial, subscription, or the fee).","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/merchant/home","host":["{{baseUrl}}"],"path":["v1","merchant","home"]}}}]}]},{"name":"Admin","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{adminToken}}","type":"string"}]},"item":[{"name":"Auth","item":[{"name":"Log in","request":{"method":"POST","description":"Logs in with mobile and password. After 5 failed attempts for a number (or 20 from one IP) within 15 minutes, logins answer `TOO_MANY_REQUESTS` until the window ends. A disabled admin gets `ACCOUNT_DISABLED`, only with the right password.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/admin/auth/login","host":["{{baseUrl}}"],"path":["v1","admin","auth","login"]},"body":{"mode":"raw","raw":"{\n  \"mobile\": \"0551234567\",\n  \"password\": \"Mashhour2026\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["// Saves the new session token for the requests that follow.","if (pm.response.code >= 200 && pm.response.code < 300) {","  const token = pm.response.json()[\"accessToken\"];","  if (token) pm.collectionVariables.set(\"adminToken\", token);","}"]}}]},{"name":"Send a password reset code","request":{"method":"POST","description":"Sends a 4-digit code by SMS to an active admin’s mobile (BRD: valid 3 minutes, the countdown) and answers its `verificationId`. Resend with `POST /auth/password/forgot/resend-otp` after `resendAfterSeconds`, which replaces the code. An unknown or disabled number gets `MOBILE_NOT_REGISTERED`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/admin/auth/password/forgot","host":["{{baseUrl}}"],"path":["v1","admin","auth","password","forgot"]},"body":{"mode":"raw","raw":"{\n  \"mobile\": \"0551234567\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}}},{"name":"Resend the password reset code","request":{"method":"POST","description":"Once `resendAfterSeconds` have passed: a new code for the `verificationId` from `password/forgot`, which stays the same.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/admin/auth/password/forgot/resend-otp","host":["{{baseUrl}}"],"path":["v1","admin","auth","password","forgot","resend-otp"]},"body":{"mode":"raw","raw":"{\n  \"verificationId\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}}},{"name":"Check a password reset code","request":{"method":"POST","description":"The `verificationId` from `password/forgot` and the code. A right code returns a one-time `resetToken` for `POST /auth/password/reset`, valid for 10 minutes.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/admin/auth/password/forgot/verify","host":["{{baseUrl}}"],"path":["v1","admin","auth","password","forgot","verify"]},"body":{"mode":"raw","raw":"{\n  \"verificationId\": \"vrf_Zk3n0x8Qy1u4b2V7s9Lr5mT6pWcA0dEfGhIjKlMnOpQ\",\n  \"code\": \"1234\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}}},{"name":"Set a new password","request":{"method":"POST","description":"Uses the `resetToken` from a verified code, once. Every session of the admin is signed out; the dashboard shows \"Password changed successfully.\" and returns to login.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/admin/auth/password/reset","host":["{{baseUrl}}"],"path":["v1","admin","auth","password","reset"]},"body":{"mode":"raw","raw":"{\n  \"resetToken\": \"string\",\n  \"password\": \"Mashhour2027\",\n  \"passwordConfirmation\": \"Mashhour2027\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}}},{"name":"Current admin","request":{"method":"GET","description":"The signed-in admin with their role and permissions, for building the dashboard menu.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/auth/me","host":["{{baseUrl}}"],"path":["v1","admin","auth","me"]}}},{"name":"Permission catalogue","request":{"method":"GET","description":"Every permission a role can grant, grouped by control panel area, with labels in the request language.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/auth/permissions","host":["{{baseUrl}}"],"path":["v1","admin","auth","permissions"]}}},{"name":"Log out","request":{"method":"POST","description":"Ends the session of the token sent; it stops working immediately. Other devices stay signed in.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/auth/logout","host":["{{baseUrl}}"],"path":["v1","admin","auth","logout"]}}}]},{"name":"Profile","item":[{"name":"Your profile","request":{"method":"GET","description":"The same as `GET auth/me`; the role is read-only.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/profile","host":["{{baseUrl}}"],"path":["v1","admin","profile"]}}},{"name":"Edit your profile or language","request":{"method":"PATCH","description":"Send only what changed. Changing `mobile` (your login) or `email` needs `currentPassword`; a new mobile signs out your other devices. After 5 wrong passwords in 15 minutes, `TOO_MANY_REQUESTS`. The answer is in the saved language.\n\n**Files with the form:** send it as `multipart/form-data` (fields as text; lists and objects in brackets, e.g. `specifications[0][name]`) with `avatar` as file parts.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/profile","host":["{{baseUrl}}"],"path":["v1","admin","profile"]},"body":{"mode":"formdata","formdata":[{"key":"name","type":"text","value":"سارة العتيبي","disabled":true},{"key":"email","type":"text","value":"sara@mashhour.sa","disabled":true,"description":"Needs `currentPassword`."},{"key":"mobile","type":"text","value":"0551234567","disabled":true,"description":"Your login number; needs `currentPassword`, and signs out your other"},{"key":"currentPassword","type":"text","value":"string","disabled":true,"description":"Required when the `mobile` or `email` sent differs from the saved one"},{"key":"avatarFileId","type":"text","value":"string","disabled":true,"description":"Leave out to keep the current file; send it empty to remove it. To replace it, send the `avatar` file instead."},{"key":"language","type":"text","value":"en","disabled":true,"description":"The control panel's language."},{"key":"avatar","type":"file","description":"PNG, JPG, WEBP, up to 10 MB."}]}}},{"name":"Change your password","request":{"method":"POST","description":"With the current password (BRD). Signs out your other devices; this one stays signed in.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/admin/profile/change-password","host":["{{baseUrl}}"],"path":["v1","admin","profile","change-password"]},"body":{"mode":"raw","raw":"{\n  \"currentPassword\": \"Mashhour2026\",\n  \"password\": \"Mashhour2027\",\n  \"passwordConfirmation\": \"Mashhour2027\"\n}","options":{"raw":{"language":"json"}}}}}]},{"name":"Audit","item":[{"name":"Audit trail","request":{"method":"GET","description":"Who changed what in the control panel, newest first. Filter by admin, record (`entityType` + `entityId` for one record’s history), action and days.\n\n**Permission:** `audit.view`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/audit","host":["{{baseUrl}}"],"path":["v1","admin","audit"],"query":[{"key":"page","value":"1","disabled":true,"description":"1-based page number."},{"key":"limit","value":"20","disabled":true,"description":"Items per page (1–100)."},{"key":"adminId","value":"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b","disabled":true,"description":"Only this admin's actions."},{"key":"entityType","value":"role","disabled":true,"description":"`role`, `admin`, `customer`, `advertiser`, …"},{"key":"entityId","value":"boost_enabled","disabled":true,"description":"One record's history (with `entityType`): its id, or a setting's key."},{"key":"action","value":"role.update","disabled":true,"description":"An exact action, e.g. `advertiser.deactivate`."},{"key":"from","value":"2026-10-01","disabled":true,"description":"From this day (`YYYY-MM-DD`, UTC), inclusive."},{"key":"to","value":"2026-10-31","disabled":true,"description":"To this day (`YYYY-MM-DD`, UTC), inclusive."}]}}}]},{"name":"Roles","item":[{"name":"Search roles","request":{"method":"GET","description":"Pages of roles, the Super Admin role first, then newest. `q` searches both names.\n\n**Permission:** `roles.view`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/roles","host":["{{baseUrl}}"],"path":["v1","admin","roles"],"query":[{"key":"page","value":"1","disabled":true,"description":"1-based page number."},{"key":"limit","value":"20","disabled":true,"description":"Items per page (1–100)."},{"key":"status","value":"active","disabled":true,"description":"Only roles with this status. One of: `active`, `disabled`."},{"key":"q","value":"string","disabled":true,"description":"Text to look for (case-insensitive)."}]}}},{"name":"Add a role","request":{"method":"POST","description":"Permissions come from `GET /v1/admin/auth/permissions`. Only super admins can grant permissions they don’t have themselves.\n\n**Permission:** `roles.create`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/admin/roles","host":["{{baseUrl}}"],"path":["v1","admin","roles"]},"body":{"mode":"raw","raw":"{\n  \"nameAr\": \"مشرف الباقات\",\n  \"nameEn\": \"Packages manager\",\n  \"permissions\": [\n    \"packages.view\",\n    \"packages.create\",\n    \"packages.update\"\n  ]\n}","options":{"raw":{"language":"json"}}}}},{"name":"View a role","request":{"method":"GET","description":"**Permission:** `roles.view`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/roles/:id","host":["{{baseUrl}}"],"path":["v1","admin","roles",":id"],"variable":[{"key":"id","value":"string"}]}}},{"name":"Edit a role","request":{"method":"PATCH","description":"Its admins get the new permissions on their next request, and their open dashboards receive `permissions.changed`. The Super Admin role and your own role can’t be edited.\n\n**Permission:** `roles.update`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/admin/roles/:id","host":["{{baseUrl}}"],"path":["v1","admin","roles",":id"],"variable":[{"key":"id","value":"string"}]},"body":{"mode":"raw","raw":"{\n  \"nameAr\": \"مشرف الباقات\",\n  \"nameEn\": \"Packages manager\",\n  \"permissions\": [\n    \"packages.view\",\n    \"packages.update\"\n  ]\n}","options":{"raw":{"language":"json"}}}}},{"name":"Delete a role","request":{"method":"DELETE","description":"Refused while admins have it (`ROLE_IN_USE`): move them to another role first.\n\n**Permission:** `roles.delete`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/roles/:id","host":["{{baseUrl}}"],"path":["v1","admin","roles",":id"],"variable":[{"key":"id","value":"string"}]}}},{"name":"Activate a role","request":{"method":"POST","description":"Its admins get its permissions again on their next request.\n\n**Permission:** `roles.status`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/roles/:id/activate","host":["{{baseUrl}}"],"path":["v1","admin","roles",":id","activate"],"variable":[{"key":"id","value":"string"}]}}},{"name":"Deactivate a role","request":{"method":"POST","description":"A deactivated role grants nothing to its admins (`adminCount` says how many), from their next request, and can’t be given to admins.\n\n**Permission:** `roles.status`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/roles/:id/deactivate","host":["{{baseUrl}}"],"path":["v1","admin","roles",":id","deactivate"],"variable":[{"key":"id","value":"string"}]}}}]},{"name":"Admins","item":[{"name":"Search admins","request":{"method":"GET","description":"Newest first. Filters by part of the name, email or mobile (as typed, `055…`), status and role.\n\n**Permission:** `admins.view`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/admins","host":["{{baseUrl}}"],"path":["v1","admin","admins"],"query":[{"key":"page","value":"1","disabled":true,"description":"1-based page number."},{"key":"limit","value":"20","disabled":true,"description":"Items per page (1–100)."},{"key":"name","value":"string","disabled":true,"description":"Part of the name."},{"key":"email","value":"string","disabled":true,"description":"Part of the email."},{"key":"mobile","value":"0551","disabled":true,"description":"Part of the mobile, as typed (`055…`)."},{"key":"status","value":"active","disabled":true,"description":"One of: `active`, `disabled`."},{"key":"roleId","value":"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b","disabled":true,"description":"Only admins with this role."}]}}},{"name":"Add an admin","request":{"method":"POST","description":"The admin logs in with the mobile and password. They are emailed that the account exists (without the password).\n\n**Files with the form:** send it as `multipart/form-data` (fields as text; lists and objects in brackets, e.g. `specifications[0][name]`) with `avatar` as file parts.\n\n**Permission:** `admins.create`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/admins","host":["{{baseUrl}}"],"path":["v1","admin","admins"]},"body":{"mode":"formdata","formdata":[{"key":"name","type":"text","value":"سارة العتيبي","description":"Letters and numbers, 2–100 characters."},{"key":"email","type":"text","value":"sara@maqsed.sa","description":"Unique; notices are sent here."},{"key":"mobile","type":"text","value":"0551234567","description":"Saudi mobile, unique; the admin logs in with it."},{"key":"password","type":"text","value":"Maqsed@2026","description":"8–25 characters with an uppercase letter, a lowercase letter, a number"},{"key":"passwordConfirmation","type":"text","value":"Maqsed@2026"},{"key":"roleId","type":"text","value":"string","description":"An active role from `GET /v1/admin/roles`, not the Super Admin."},{"key":"status","type":"text","value":"active","disabled":true,"description":"`disabled` creates the account without letting it log in yet."},{"key":"avatar","type":"file","description":"PNG, JPG, WEBP, up to 10 MB."}]}}},{"name":"View an admin","request":{"method":"GET","description":"With the login count and last login (BRD).\n\n**Permission:** `admins.view`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/admins/:id","host":["{{baseUrl}}"],"path":["v1","admin","admins",":id"],"variable":[{"key":"id","value":"string"}]}}},{"name":"Edit an admin","request":{"method":"PATCH","description":"Send only what changed. A new role applies on their next request (`permissions.changed` to their dashboard); a new password or mobile signs them out. Only the super admin edits the super admin, and nobody changes their own role.\n\n**Files with the form:** send it as `multipart/form-data` (fields as text; lists and objects in brackets, e.g. `specifications[0][name]`) with `avatar` as file parts.\n\n**Permission:** `admins.update`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/admins/:id","host":["{{baseUrl}}"],"path":["v1","admin","admins",":id"],"variable":[{"key":"id","value":"string"}]},"body":{"mode":"formdata","formdata":[{"key":"name","type":"text","value":"سارة العتيبي","disabled":true},{"key":"email","type":"text","value":"sara@maqsed.sa","disabled":true},{"key":"mobile","type":"text","value":"0551234567","disabled":true,"description":"Their login number; changing it signs them out."},{"key":"password","type":"text","value":"string","disabled":true,"description":"Changing it signs them out everywhere."},{"key":"passwordConfirmation","type":"text","value":"string","disabled":true},{"key":"roleId","type":"text","value":"string","disabled":true,"description":"Their permissions change on their next request."},{"key":"avatarFileId","type":"text","value":"string","disabled":true,"description":"Leave out to keep the current file; send it empty to remove it. To replace it, send the `avatar` file instead."},{"key":"avatar","type":"file","description":"PNG, JPG, WEBP, up to 10 MB."}]}}},{"name":"Delete an admin","request":{"method":"DELETE","description":"Signs them out, frees their mobile and email, and emails them. The super admin and your own account can’t be deleted.\n\n**Permission:** `admins.delete`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/admins/:id","host":["{{baseUrl}}"],"path":["v1","admin","admins",":id"],"variable":[{"key":"id","value":"string"}]}}},{"name":"Activate an admin","request":{"method":"POST","description":"They can log in again; they are emailed.\n\n**Permission:** `admins.status`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/admins/:id/activate","host":["{{baseUrl}}"],"path":["v1","admin","admins",":id","activate"],"variable":[{"key":"id","value":"string"}]}}},{"name":"Deactivate an admin","request":{"method":"POST","description":"Needs a `reason` (3–250, BRD); signs them out at once and emails them the reason.\n\n**Permission:** `admins.status`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/admin/admins/:id/deactivate","host":["{{baseUrl}}"],"path":["v1","admin","admins",":id","deactivate"],"variable":[{"key":"id","value":"string"}]},"body":{"mode":"raw","raw":"{\n  \"reason\": \"Repeated policy violations\"\n}","options":{"raw":{"language":"json"}}}}}]},{"name":"Customers","item":[{"name":"Search customers","request":{"method":"GET","description":"Newest first (join date). Filters by part of the name, mobile (as typed, `055…`) or email, and status. Deleted accounts are not listed.\n\n**Permission:** `customers.view`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/customers","host":["{{baseUrl}}"],"path":["v1","admin","customers"],"query":[{"key":"page","value":"1","disabled":true,"description":"1-based page number."},{"key":"limit","value":"20","disabled":true,"description":"Items per page (1–100)."},{"key":"name","value":"string","disabled":true,"description":"Part of the name."},{"key":"mobile","value":"0551","disabled":true,"description":"Part of the mobile, as typed (`055…`)."},{"key":"email","value":"string","disabled":true,"description":"Part of the email."},{"key":"status","value":"active","disabled":true,"description":"One of: `active`, `disabled`."}]}}},{"name":"View a customer","request":{"method":"GET","description":"With their favorites, reports and chats counted.\n\n**Permission:** `customers.view`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/customers/:id","host":["{{baseUrl}}"],"path":["v1","admin","customers",":id"],"variable":[{"key":"id","value":"string"}]}}},{"name":"Edit a customer","request":{"method":"PATCH","description":"Send only what changed; `null` clears an optional field. A new mobile is their login, so it signs them out.\n\n**Permission:** `customers.update`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/admin/customers/:id","host":["{{baseUrl}}"],"path":["v1","admin","customers",":id"],"variable":[{"key":"id","value":"string"}]},"body":{"mode":"raw","raw":"{\n  \"name\": \"سارة أحمد\",\n  \"email\": \"sara@example.com\",\n  \"mobile\": \"0551234567\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Activate a customer","request":{"method":"POST","description":"After the confirmation (BRD): they can log in to the app again, and are emailed when they have an email.\n\n**Permission:** `customers.status`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/customers/:id/activate","host":["{{baseUrl}}"],"path":["v1","admin","customers",":id","activate"],"variable":[{"key":"id","value":"string"}]}}},{"name":"Deactivate a customer","request":{"method":"POST","description":"Needs a `reason` (3–250, letters and numbers, BRD); signs them out of the app at once and emails them the reason when they have an email.\n\n**Permission:** `customers.status`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/admin/customers/:id/deactivate","host":["{{baseUrl}}"],"path":["v1","admin","customers",":id","deactivate"],"variable":[{"key":"id","value":"string"}]},"body":{"mode":"raw","raw":"{\n  \"reason\": \"Repeated policy violations\"\n}","options":{"raw":{"language":"json"}}}}}]},{"name":"Merchants","item":[{"name":"Search service providers","request":{"method":"GET","description":"Newest first unless `sort`/`order` say otherwise. `reviewStatus=under_review` lists the joining requests; `counts` has each review status (with the other filters) for the menu badge. Sign-ups whose mobile isn’t verified and deleted accounts are not listed.\n\n**Permission:** `merchants.view`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/merchants","host":["{{baseUrl}}"],"path":["v1","admin","merchants"],"query":[{"key":"page","value":"1","disabled":true,"description":"1-based page number."},{"key":"limit","value":"20","disabled":true,"description":"Items per page (1–100)."},{"key":"q","value":"string","disabled":true,"description":"Part of the name, store name, mobile (as typed, `055…`) or email."},{"key":"reviewStatus","value":"under_review","disabled":true,"description":"One of: `under_review`, `approved`, `rejected`."},{"key":"status","value":"active","disabled":true,"description":"One of: `active`, `disabled`."},{"key":"sort","value":"name","disabled":true,"description":"Default `createdAt` (the registration date). One of: `name`, `createdAt`, `reviewStatus`."},{"key":"order","value":"asc","disabled":true,"description":"Default `desc`. One of: `asc`, `desc`."}]}}},{"name":"View a service provider","request":{"method":"GET","description":"Everything from registration, the CR image and work document as signed links, the trial and subscription, and the ads count.\n\n**Permission:** `merchants.view`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/merchants/:id","host":["{{baseUrl}}"],"path":["v1","admin","merchants",":id"],"variable":[{"key":"id","value":"string"}]}}},{"name":"Edit a service provider","request":{"method":"PATCH","description":"Send only what changed: name, store name, email, CR number (the profile’s rules). A CR number or a work document must remain.\n\n**Permission:** `merchants.update`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/admin/merchants/:id","host":["{{baseUrl}}"],"path":["v1","admin","merchants",":id"],"variable":[{"key":"id","value":"string"}]},"body":{"mode":"raw","raw":"{\n  \"name\": \"محمد عبدالله\",\n  \"email\": \"store@example.com\",\n  \"storeName\": \"مطعم البيت الشامي للمشاويات\",\n  \"crNumber\": \"1010123456\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Approve a joining request","request":{"method":"POST","description":"Under review only. The merchant can log in, the free trial starts while seats remain, and they get an SMS and a notification.\n\n**Permission:** `merchants.update`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/merchants/:id/approve","host":["{{baseUrl}}"],"path":["v1","admin","merchants",":id","approve"],"variable":[{"key":"id","value":"string"}]}}},{"name":"Reject a joining request","request":{"method":"POST","description":"Under review only, with a `reason` (3–250) the merchant sees at login and in a notification. They may apply again.\n\n**Permission:** `merchants.update`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/admin/merchants/:id/reject","host":["{{baseUrl}}"],"path":["v1","admin","merchants",":id","reject"],"variable":[{"key":"id","value":"string"}]},"body":{"mode":"raw","raw":"{\n  \"reason\": \"string\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Activate a service provider","request":{"method":"POST","description":"They can log in again and their ads are shown to customers again; they are emailed.\n\n**Permission:** `merchants.status`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/merchants/:id/activate","host":["{{baseUrl}}"],"path":["v1","admin","merchants",":id","activate"],"variable":[{"key":"id","value":"string"}]}}},{"name":"Deactivate a service provider","request":{"method":"POST","description":"Needs a `reason` (3–250). Signs them out at once, hides their ads from customers, and emails them the reason.\n\n**Permission:** `merchants.status`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/admin/merchants/:id/deactivate","host":["{{baseUrl}}"],"path":["v1","admin","merchants",":id","deactivate"],"variable":[{"key":"id","value":"string"}]},"body":{"mode":"raw","raw":"{\n  \"reason\": \"Repeated policy violations\"\n}","options":{"raw":{"language":"json"}}}}}]},{"name":"Ads","item":[{"name":"Search ads","request":{"method":"GET","description":"Newest first. Filters by part of the title, main category, status (`pending_review` is the review queue; an active ad past its date is `expired`) and merchant. `totals` are the BRD’s cards: all ads and per category, with every filter but the category.\n\n**Permission:** `ads.view`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/ads","host":["{{baseUrl}}"],"path":["v1","admin","ads"],"query":[{"key":"page","value":"1","disabled":true,"description":"1-based page number."},{"key":"limit","value":"20","disabled":true,"description":"Items per page (1–100)."},{"key":"q","value":"string","disabled":true,"description":"Part of the title."},{"key":"categoryId","value":"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b","disabled":true,"description":"The main category (a totals chip)."},{"key":"status","value":"active","disabled":true,"description":"One of: `active`, `disabled`, `rejected`, `pending_review`, `expired`."},{"key":"merchantId","value":"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b","disabled":true}]}}},{"name":"View an ad","request":{"method":"GET","description":"All its data, images and video, the merchant’s contact, the open reports, what can be done now (`adminActions`) and its history.\n\n**Permission:** `ads.view`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/ads/:id","host":["{{baseUrl}}"],"path":["v1","admin","ads",":id"],"variable":[{"key":"id","value":"string"}]}}},{"name":"Delete an ad","request":{"method":"DELETE","description":"After the confirmation (BRD): gone for the merchant and customers, out of favorites, its media deleted; statistics follow at once. The body is optional (`reason`, `closeReports`).\n\n**Permission:** `ads.delete`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/admin/ads/:id","host":["{{baseUrl}}"],"path":["v1","admin","ads",":id"],"variable":[{"key":"id","value":"string"}]},"body":{"mode":"raw","raw":"{\n  \"reason\": \"string\",\n  \"closeReports\": true\n}","options":{"raw":{"language":"json"}}}}},{"name":"Approve an ad","request":{"method":"POST","description":"From the review queue: it goes live (or expired, if its date passed while waiting); the merchant is notified.\n\n**Permission:** `ads.update`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/ads/:id/approve","host":["{{baseUrl}}"],"path":["v1","admin","ads",":id","approve"],"variable":[{"key":"id","value":"string"}]}}},{"name":"Reject an ad","request":{"method":"POST","description":"From the review queue, with a `reason` (3–500) the merchant sees on the ad and in a notification; they can edit it and send it again.\n\n**Permission:** `ads.update`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/admin/ads/:id/reject","host":["{{baseUrl}}"],"path":["v1","admin","ads",":id","reject"],"variable":[{"key":"id","value":"string"}]},"body":{"mode":"raw","raw":"{\n  \"reason\": \"string\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Disable an ad","request":{"method":"POST","description":"From the queue, live or expired: hidden from customers at once, the merchant notified (with the `reason` when given). `closeReports: true` marks its open reports reviewed.\n\n**Permission:** `ads.update`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/admin/ads/:id/disable","host":["{{baseUrl}}"],"path":["v1","admin","ads",":id","disable"],"variable":[{"key":"id","value":"string"}]},"body":{"mode":"raw","raw":"{\n  \"reason\": \"string\",\n  \"closeReports\": true\n}","options":{"raw":{"language":"json"}}}}},{"name":"Enable an ad","request":{"method":"POST","description":"A disabled ad goes live again (or expired, if its date passed meanwhile).\n\n**Permission:** `ads.update`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/ads/:id/enable","host":["{{baseUrl}}"],"path":["v1","admin","ads",":id","enable"],"variable":[{"key":"id","value":"string"}]}}}]},{"name":"Categories","item":[{"name":"Search main categories","request":{"method":"GET","description":"Inactive ones included, in display order, by part of the Arabic or English name and status, with their ads and subcategories counted.\n\n**Permission:** `categories.view`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/categories","host":["{{baseUrl}}"],"path":["v1","admin","categories"],"query":[{"key":"page","value":"1","disabled":true,"description":"1-based page number."},{"key":"limit","value":"20","disabled":true,"description":"Items per page (1–100)."},{"key":"q","value":"string","disabled":true,"description":"Part of the name, Arabic or English."},{"key":"status","value":"active","disabled":true,"description":"One of: `active`, `inactive`."}]}}},{"name":"Add a category or subcategory","request":{"method":"POST","description":"Sent as `multipart/form-data`, the picture as the `image` part. Without `parentId` it is a main category; with it, a subcategory of that active main category. Added last among its siblings, active.\n\n**Files with the form:** send it as `multipart/form-data` (fields as text; lists and objects in brackets, e.g. `specifications[0][name]`) with `image` as file parts.\n\n**Permission:** `categories.create`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/categories","host":["{{baseUrl}}"],"path":["v1","admin","categories"]},"body":{"mode":"formdata","formdata":[{"key":"parentId","type":"text","value":"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b","disabled":true,"description":"The main category, for a subcategory."},{"key":"nameAr","type":"text","value":"مطاعم","description":"Unique among its siblings."},{"key":"nameEn","type":"text","value":"Restaurants","description":"Unique among its siblings."},{"key":"descriptionAr","type":"text","value":"string","disabled":true,"description":"The line under the name on the home cards."},{"key":"descriptionEn","type":"text","value":"string","disabled":true},{"key":"image","type":"file","description":"PNG, JPG, WEBP, up to 5 MB."}]}}},{"name":"Search subcategories","request":{"method":"GET","description":"By part of the name, status and main category (`parentId`); each with its main category.\n\n**Permission:** `categories.view`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/categories/subcategories","host":["{{baseUrl}}"],"path":["v1","admin","categories","subcategories"],"query":[{"key":"page","value":"1","disabled":true,"description":"1-based page number."},{"key":"limit","value":"20","disabled":true,"description":"Items per page (1–100)."},{"key":"q","value":"string","disabled":true,"description":"Part of the name, Arabic or English."},{"key":"status","value":"active","disabled":true,"description":"One of: `active`, `inactive`."},{"key":"parentId","value":"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b","disabled":true,"description":"Only this main category's subcategories."}]}}},{"name":"View a category","request":{"method":"GET","description":"With its subcategories, for a main category.\n\n**Permission:** `categories.view`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/categories/:id","host":["{{baseUrl}}"],"path":["v1","admin","categories",":id"],"variable":[{"key":"id","value":"string"}]}}},{"name":"Edit a category","request":{"method":"PATCH","description":"Multipart; send only what changed. `imageFileId` or a description sent empty removes it. A subcategory may move to another active main category.\n\n**Files with the form:** send it as `multipart/form-data` (fields as text; lists and objects in brackets, e.g. `specifications[0][name]`) with `image` as file parts.\n\n**Permission:** `categories.update`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/categories/:id","host":["{{baseUrl}}"],"path":["v1","admin","categories",":id"],"variable":[{"key":"id","value":"string"}]},"body":{"mode":"formdata","formdata":[{"key":"parentId","type":"text","value":"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b","disabled":true,"description":"A subcategory's new main category (active)."},{"key":"nameAr","type":"text","value":"string","disabled":true},{"key":"nameEn","type":"text","value":"string","disabled":true},{"key":"descriptionAr","type":"text","value":"string","disabled":true},{"key":"descriptionEn","type":"text","value":"string","disabled":true},{"key":"imageFileId","type":"text","value":"string","disabled":true,"description":"Leave out to keep the current file; send it empty to remove it. To replace it, send the `image` file instead."},{"key":"image","type":"file","description":"PNG, JPG, WEBP, up to 5 MB."}]}}},{"name":"Delete a category","request":{"method":"DELETE","description":"After the confirmation (BRD). Only when no ad uses it or its subcategories, deleted ads included; otherwise `CATEGORY_HAS_ADS`: deactivate it instead. A main category goes with its subcategories.\n\n**Permission:** `categories.delete`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/categories/:id","host":["{{baseUrl}}"],"path":["v1","admin","categories",":id"],"variable":[{"key":"id","value":"string"}]}}},{"name":"Reorder categories","request":{"method":"POST","description":"Every subcategory of `parentId` (`null`: the main categories), in the new order the apps show.\n\n**Permission:** `categories.update`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/admin/categories/reorder","host":["{{baseUrl}}"],"path":["v1","admin","categories","reorder"]},"body":{"mode":"raw","raw":"{\n  \"parentId\": \"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b\",\n  \"ids\": [\n    \"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b\"\n  ]\n}","options":{"raw":{"language":"json"}}}}},{"name":"Activate a category","request":{"method":"POST","description":"It can be chosen for new ads again, and a main category’s ads are shown again.\n\n**Permission:** `categories.status`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/categories/:id/activate","host":["{{baseUrl}}"],"path":["v1","admin","categories",":id","activate"],"variable":[{"key":"id","value":"string"}]}}},{"name":"Deactivate a category","request":{"method":"POST","description":"It can’t be chosen for new ads. A main category hides its subcategories and its ads; a subcategory’s ads stay shown through their main category.\n\n**Permission:** `categories.status`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/categories/:id/deactivate","host":["{{baseUrl}}"],"path":["v1","admin","categories",":id","deactivate"],"variable":[{"key":"id","value":"string"}]}}}]},{"name":"Packages","item":[{"name":"Search packages","request":{"method":"GET","description":"Inactive ones included, in display order, by part of the name and status, with their running subscriptions counted.\n\n**Permission:** `packages.view`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/packages","host":["{{baseUrl}}"],"path":["v1","admin","packages"],"query":[{"key":"page","value":"1","disabled":true,"description":"1-based page number."},{"key":"limit","value":"20","disabled":true,"description":"Items per page (1–100)."},{"key":"q","value":"string","disabled":true,"description":"Part of the name, Arabic or English."},{"key":"status","value":"active","disabled":true,"description":"One of: `active`, `inactive`."}]}}},{"name":"Add a package","request":{"method":"POST","description":"Active unless `active: false` (BRD). The name must not be another active package’s.\n\n**Permission:** `packages.create`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/admin/packages","host":["{{baseUrl}}"],"path":["v1","admin","packages"]},"body":{"mode":"raw","raw":"{\n  \"nameAr\": \"الباقة الشهرية\",\n  \"nameEn\": \"Monthly package\",\n  \"price\": 100,\n  \"previousPrice\": 120,\n  \"durationValue\": 1,\n  \"durationUnit\": \"month\",\n  \"benefitsAr\": [\n    \"string\"\n  ],\n  \"benefitsEn\": [\n    \"string\"\n  ],\n  \"recommended\": true,\n  \"active\": true\n}","options":{"raw":{"language":"json"}}}}},{"name":"View a package","request":{"method":"GET","description":"**Permission:** `packages.view`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/packages/:id","host":["{{baseUrl}}"],"path":["v1","admin","packages",":id"],"variable":[{"key":"id","value":"string"}]}}},{"name":"Edit a package","request":{"method":"PATCH","description":"Send only what changed. Running subscriptions keep what they bought; the change applies to new ones.\n\n**Permission:** `packages.update`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/admin/packages/:id","host":["{{baseUrl}}"],"path":["v1","admin","packages",":id"],"variable":[{"key":"id","value":"string"}]},"body":{"mode":"raw","raw":"{\n  \"nameAr\": \"string\",\n  \"nameEn\": \"string\",\n  \"price\": 0,\n  \"previousPrice\": 0,\n  \"durationValue\": 1,\n  \"durationUnit\": \"day\",\n  \"benefitsAr\": [\n    \"string\"\n  ],\n  \"benefitsEn\": [\n    \"string\"\n  ],\n  \"recommended\": true\n}","options":{"raw":{"language":"json"}}}}},{"name":"Delete a package","request":{"method":"DELETE","description":"After the confirmation (BRD). Only a package nobody bought; otherwise `PACKAGE_IN_USE`: deactivate it instead.\n\n**Permission:** `packages.delete`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/packages/:id","host":["{{baseUrl}}"],"path":["v1","admin","packages",":id"],"variable":[{"key":"id","value":"string"}]}}},{"name":"Reorder packages","request":{"method":"POST","description":"Every package, in the order merchants see them.\n\n**Permission:** `packages.update`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/admin/packages/reorder","host":["{{baseUrl}}"],"path":["v1","admin","packages","reorder"]},"body":{"mode":"raw","raw":"{\n  \"ids\": [\n    \"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b\"\n  ]\n}","options":{"raw":{"language":"json"}}}}},{"name":"Activate a package","request":{"method":"POST","description":"Merchants can subscribe to it again. No past subscription is restarted (BRD).\n\n**Permission:** `packages.status`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/packages/:id/activate","host":["{{baseUrl}}"],"path":["v1","admin","packages",":id","activate"],"variable":[{"key":"id","value":"string"}]}}},{"name":"Deactivate a package","request":{"method":"POST","description":"Hidden from merchants for new subscriptions; running ones continue to their end (BRD).\n\n**Permission:** `packages.status`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/packages/:id/deactivate","host":["{{baseUrl}}"],"path":["v1","admin","packages",":id","deactivate"],"variable":[{"key":"id","value":"string"}]}}}]},{"name":"Settings","item":[{"name":"The general settings","request":{"method":"GET","description":"The values that apply now (defaults until saved), with when and by whom each was last saved.\n\n**Permission:** `settings.view`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/settings","host":["{{baseUrl}}"],"path":["v1","admin","settings"]}}},{"name":"Save general settings","request":{"method":"PATCH","description":"Only the fields sent change; each applies from the next request (the next ad pays the new fee; a granted trial keeps its dates). Store links show in the apps at once.\n\n**Permission:** `settings.update`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/admin/settings","host":["{{baseUrl}}"],"path":["v1","admin","settings"]},"body":{"mode":"raw","raw":"{\n  \"adFee\": 15,\n  \"freeTrial\": {\n    \"months\": 1,\n    \"seats\": 100\n  },\n  \"adDefaultDays\": 30,\n  \"reminderDays\": 3,\n  \"appLinks\": {\n    \"customer\": {\n      \"ios\": \"string\",\n      \"android\": \"string\"\n    },\n    \"merchant\": {\n      \"ios\": \"string\",\n      \"android\": \"string\"\n    }\n  }\n}","options":{"raw":{"language":"json"}}}}}]},{"name":"Content","item":[{"name":"The pages","request":{"method":"GET","description":"About, Terms and Privacy, with when and by whom saved.\n\n**Permission:** `content.view`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/content/pages","host":["{{baseUrl}}"],"path":["v1","admin","content","pages"]}}},{"name":"A page, in both languages","request":{"method":"GET","description":"**Permission:** `content.view`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/content/pages/:key","host":["{{baseUrl}}"],"path":["v1","admin","content","pages",":key"],"variable":[{"key":"key","value":"about"}]}}},{"name":"Save a page","request":{"method":"PUT","description":"Rich text (HTML) in both languages, both required (BRD: \"Text must be entered\"). Scripts, images and other markup are removed. The apps show it on the next request.\n\n**Permission:** `content.update`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/admin/content/pages/:key","host":["{{baseUrl}}"],"path":["v1","admin","content","pages",":key"],"variable":[{"key":"key","value":"about"}]},"body":{"mode":"raw","raw":"{\n  \"contentAr\": \"string\",\n  \"contentEn\": \"string\",\n  \"titleAr\": \"من نحن\",\n  \"titleEn\": \"About us\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"The Contact Us channels","request":{"method":"GET","description":"**Permission:** `content.view`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/content/contact-channels","host":["{{baseUrl}}"],"path":["v1","admin","content","contact-channels"]}}},{"name":"Save the Contact Us channels","request":{"method":"PUT","description":"The whole set (BRD 4.3.10.1.1): contact mobiles and WhatsApp as `05…`, an email, and links on each platform’s own site. A channel sent empty or left out is hidden in the apps.\n\n**Permission:** `content.update`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/admin/content/contact-channels","host":["{{baseUrl}}"],"path":["v1","admin","content","contact-channels"]},"body":{"mode":"raw","raw":"{\n  \"phones\": [\n    \"0551234567\"\n  ],\n  \"whatsapp\": \"0551234567\",\n  \"email\": \"support@mashhour.sa\",\n  \"facebook\": \"https://facebook.com/mashhour\",\n  \"x\": \"https://x.com/mashhour\",\n  \"instagram\": \"string\",\n  \"snapchat\": \"string\",\n  \"tiktok\": \"string\",\n  \"youtube\": \"string\"\n}","options":{"raw":{"language":"json"}}}}}]},{"name":"Contact messages","item":[{"name":"Search contact messages","request":{"method":"GET","description":"Newest first (BRD). By part of the name, mobile (as typed) or email, kind, type, status and sender. `counts.notReplied` is the menu badge.\n\n**Permission:** `contact.view`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/contact/messages","host":["{{baseUrl}}"],"path":["v1","admin","contact","messages"],"query":[{"key":"page","value":"1","disabled":true,"description":"1-based page number."},{"key":"limit","value":"20","disabled":true,"description":"Items per page (1–100)."},{"key":"name","value":"string","disabled":true,"description":"Part of the sender's name."},{"key":"mobile","value":"0551","disabled":true,"description":"Part of the mobile, as typed (`055…`)."},{"key":"email","value":"string","disabled":true,"description":"Part of the email."},{"key":"kind","value":"message","disabled":true,"description":"One of: `message`, `complaint`."},{"key":"type","value":"merchant","disabled":true,"description":"A message type (BRD: request, suggestion, inquiry, complaint, other) or a complaint type. One of: `merchant`, `app`, `complaint`, `ad`, `payment`, `request`, `suggestion`, `inquiry`, `other`."},{"key":"status","value":"not_replied","disabled":true,"description":"One of: `not_replied`, `replied`."},{"key":"senderType","value":"customer","disabled":true,"description":"Who sent it; `guest` is a sender without an account. One of: `customer`, `merchant`, `guest`."}]}}},{"name":"View a message","request":{"method":"GET","description":"The whole text, and the reply once sent (read-only).\n\n**Permission:** `contact.view`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/contact/messages/:id","host":["{{baseUrl}}"],"path":["v1","admin","contact","messages",":id"],"variable":[{"key":"id","value":"string"}]}}},{"name":"Reply to a message","request":{"method":"POST","description":"Once only (BRD): the reply (3–250) is emailed to the sender in their language and the message becomes replied.\n\n**Permission:** `contact.update`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/admin/contact/messages/:id/reply","host":["{{baseUrl}}"],"path":["v1","admin","contact","messages",":id","reply"],"variable":[{"key":"id","value":"string"}]},"body":{"mode":"raw","raw":"{\n  \"reply\": \"string\"\n}","options":{"raw":{"language":"json"}}}}}]},{"name":"Reports","item":[{"name":"List ad reports","request":{"method":"GET","description":"Newest first; by status, ad, or part of the reason or text. `openCount` is the menu badge. To act on the ad, disable or delete it with `closeReports: true`.\n\n**Permission:** `reports.view`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/reports","host":["{{baseUrl}}"],"path":["v1","admin","reports"],"query":[{"key":"page","value":"1","disabled":true,"description":"1-based page number."},{"key":"limit","value":"20","disabled":true,"description":"Items per page (1–100)."},{"key":"status","value":"open","disabled":true,"description":"One of: `open`, `reviewed`."},{"key":"adId","value":"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b","disabled":true,"description":"The reports on one ad."},{"key":"q","value":"string","disabled":true,"description":"Part of the reason or the text."}]}}},{"name":"View an ad report","request":{"method":"GET","description":"**Permission:** `reports.view`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/admin/reports/:id","host":["{{baseUrl}}"],"path":["v1","admin","reports",":id"],"variable":[{"key":"id","value":"string"}]}}},{"name":"Mark a report reviewed","request":{"method":"POST","description":"An open report, with an optional `note` (3–500).\n\n**Permission:** `reports.update`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/admin/reports/:id/mark-reviewed","host":["{{baseUrl}}"],"path":["v1","admin","reports",":id","mark-reviewed"],"variable":[{"key":"id","value":"string"}]},"body":{"mode":"raw","raw":"{\n  \"note\": \"string\"\n}","options":{"raw":{"language":"json"}}}}}]}]},{"name":"System","auth":{"type":"noauth"},"item":[{"name":"Download a file\n\nPublic files (ad media, logos, avatars) need nothing else. Private\nfiles (CR images, work documents) need the `expires` and `signature` from their URL; an invalid or\nexpired link answers 404. Supports `Range` for video seeking.","request":{"method":"GET","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/files/:id","host":["{{baseUrl}}"],"path":["files",":id"],"query":[{"key":"signature","value":"","disabled":true,"description":"Private files only."},{"key":"expires","value":"","disabled":true,"description":"Private files only."}],"variable":[{"key":"id","value":"string"}]}}},{"name":"Readiness: MySQL and Redis\n\n200 while the API can serve requests (`ok`, or `degraded` when only the\ncache is down), 503 when the database is unreachable. The Docker image's\nHEALTHCHECK calls this.","request":{"method":"GET","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/health","host":["{{baseUrl}}"],"path":["health"]}}},{"name":"Liveness: the process is up","request":{"method":"GET","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/health/live","host":["{{baseUrl}}"],"path":["health","live"]}}}]}]}