{"info":{"name":"Bayatak API","description":"One API for the two Bayatak clients: the mobile app for guests and signed-in users, who both browse and sell (`/v1/user`), 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://bayatak-api.dev-moltaqa.cloud","description":"API origin, without /v1."},{"key":"userToken","value":"","description":"Session token for /v1/user; 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":"User","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{userToken}}","type":"string"}]},"item":[{"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/user/auth/register","host":["{{baseUrl}}"],"path":["v1","user","auth","register"]},"body":{"mode":"raw","raw":"{\n  \"name\": \"فهد القحطاني\",\n  \"mobile\": \"0501234567\",\n  \"whatsapp\": \"0501234567\",\n  \"email\": \"fahad@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/user/auth/register/resend-otp","host":["{{baseUrl}}"],"path":["v1","user","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/user/auth/register/verify","host":["{{baseUrl}}"],"path":["v1","user","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(\"userToken\", token);","}"]}}]},{"name":"Log in","request":{"method":"POST","description":"Sends a 4-digit code by SMS to a registered, active account (BRD 4.1.3) 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/user/auth/login","host":["{{baseUrl}}"],"path":["v1","user","auth","login"]},"body":{"mode":"raw","raw":"{\n  \"mobile\": \"0501234567\"\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/user/auth/login/resend-otp","host":["{{baseUrl}}"],"path":["v1","user","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/user/auth/login/verify","host":["{{baseUrl}}"],"path":["v1","user","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(\"userToken\", 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/user/auth/logout","host":["{{baseUrl}}"],"path":["v1","user","auth","logout"]}}}]},{"name":"Account","item":[{"name":"My account","request":{"method":"GET","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/account","host":["{{baseUrl}}"],"path":["v1","user","account"]}}},{"name":"Change my language","request":{"method":"PATCH","description":"The \"Change language\" screen (`ar`, `en`). Requests without `Accept-Language` or `?lang=` are answered in it from the next request, and SMS and email messages use it.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/user/account","host":["{{baseUrl}}"],"path":["v1","user","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 code. Your details and files are removed, every device is signed out (this one included), and your number, email (and Iqama or CR number) can register a new, empty account.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/account","host":["{{baseUrl}}"],"path":["v1","user","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/user/account/change-mobile","host":["{{baseUrl}}"],"path":["v1","user","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/user/account/change-mobile/resend-otp","host":["{{baseUrl}}"],"path":["v1","user","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/user/account/change-mobile/verify","host":["{{baseUrl}}"],"path":["v1","user","account","change-mobile","verify"]},"body":{"mode":"raw","raw":"{\n  \"verificationId\": \"vrf_Zk3n0x8Qy1u4b2V7s9Lr5mT6pWcA0dEfGhIjKlMnOpQ\",\n  \"code\": \"1234\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Save my profile","request":{"method":"PATCH","description":"JSON, only the fields that changed (the lab’s `ProfileDetails`): name, WhatsApp, email, photo, gender, date of birth. Optional fields sent empty or `null` are cleared. A new photo is a `user_avatar` file id from `POST /uploads`; the old photo is deleted. All or nothing: a refused field changes nothing. The answer is the account, as `GET /account`. The mobile changes through `account/change-mobile`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/user/account/profile","host":["{{baseUrl}}"],"path":["v1","user","account","profile"]},"body":{"mode":"raw","raw":"{\n  \"name\": \"فهد القحطاني\",\n  \"whatsapp\": \"0501234567\",\n  \"email\": \"fahad@example.com\",\n  \"avatarFileId\": \"string\",\n  \"gender\": \"male\",\n  \"birthDate\": \"1995-04-12\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Save my place","request":{"method":"PUT","description":"The Location screen (BRD 4.1.7 v1.1: a region, a city in it, and optionally a neighbourhood, picked from `GET /regions`; the lab’s `updatePlace`). Home and the \"near me\" filter use it. Each refusal names its field: `REGION_NOT_AVAILABLE`, `CITY_NOT_AVAILABLE` (not active, or not in the region) or `NEIGHBORHOOD_NOT_AVAILABLE` (not active, or not in the city). Answers the account with its new `place`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/user/account/place","host":["{{baseUrl}}"],"path":["v1","user","account","place"]},"body":{"mode":"raw","raw":"{\n  \"regionId\": \"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b\",\n  \"cityId\": \"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7c\",\n  \"neighborhoodId\": \"string\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Verify my identity with Nafath","request":{"method":"POST","description":"Optional (BRD 4.1.5, v1.1; the lab’s `startNafath`). Sends a request to the user’s Nafath app and answers the two-digit `number` to show, with `expiresInSeconds` (120). An open request comes back as it is. The account’s `identity` becomes `pending`. Then poll `GET …/{id}` every 3 seconds. `TOO_MANY_ATTEMPTS` after three requests in 15 minutes, with `Retry-After` in seconds (the app shows the time). On test servers the fake Nafath answers after a few seconds: an ID ending in 1 is declined, 2 doesn’t match, 3 expires, any other is approved.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/user/account/identity/nafath","host":["{{baseUrl}}"],"path":["v1","user","account","identity","nafath"]},"body":{"mode":"raw","raw":"{\n  \"nationalId\": \"1012345678\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Check a Nafath request","request":{"method":"GET","description":"The waiting screen’s poll (the lab’s `nafathStatus`). Once `accepted`, the account is `verified` and shows the badge; `rejected`, `failed` and `expired` leave it unverified (its `identity` says which).","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/account/identity/nafath/:id","host":["{{baseUrl}}"],"path":["v1","user","account","identity","nafath",":id"],"variable":[{"key":"id","value":"string"}]}}}]},{"name":"Places","item":[{"name":"Regions, cities and neighbourhoods","request":{"method":"GET","description":"The active tree in one answer (the lab’s `regions()`): regions, their cities, and the cities’ neighbourhoods, in the admin’s order, each name in Arabic and English. Public. The location screen, the ad form and the search filters pick from it (BRD v1.1: lists, no map). A neighbourhood is optional; many cities have none listed.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/regions","host":["{{baseUrl}}"],"path":["v1","user","regions"]},"auth":{"type":"noauth"}}}]},{"name":"Uploads","item":[{"name":"Upload a photo or video","request":{"method":"POST","description":"One file per request, as multipart/form-data in the `file` field (the lab’s `uploadMedia`). Its type is read from its bytes, not its name:\n\n- `ad_image`: PNG or JPG, up to 5 MB (10 per ad)\n- `ad_video`: MP4 or MOV, up to 20 MB (one per ad, instead of photos); answers a poster in `thumbnailUrl`\n- `user_avatar`: PNG or JPG, up to 5 MB\n\nThen send the `id` with the ad or the profile; only the account that uploaded a file can use it, once. Uploads unused after 24 hours are deleted. `TOO_MANY_REQUESTS` (with `Retry-After`) past the hourly limit.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/uploads?purpose=ad_image","host":["{{baseUrl}}"],"path":["v1","user","uploads"],"query":[{"key":"purpose","value":"ad_image","disabled":false,"description":"What the file is for; decides its types and size limit. One of: `ad_image`, `ad_video`, `user_avatar`."}]},"body":{"mode":"formdata","formdata":[{"key":"file","type":"file"}]}}}]},{"name":"Sections","item":[{"name":"Sections, subcategories and their fields","request":{"method":"GET","description":"The active sections in the admin’s order (the lab’s `categories()`; BRD 4.1.8), each with its active subcategories and its own fields for the ad form and the ad’s specifications (D13): `number` (with an optional unit), `choice` (with options) or `text`. Names are in Arabic and English. Public. `iconUrl` is null until an admin uploads one; the app shows its bundled icon for `code` meanwhile.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/categories","host":["{{baseUrl}}"],"path":["v1","user","categories"]},"auth":{"type":"noauth"}}}]},{"name":"Settings","item":[{"name":"Read app settings","request":{"method":"GET","description":"What the admin controls and the app shows (the lab’s `settings()`): free ads per month, the auto-stop report count, VAT, the contribution switch, rates and text, the boost switch, and the store links. Rates are basis points (50 = 0.5%). Public and cached. The app never hard-codes these.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/settings","host":["{{baseUrl}}"],"path":["v1","user","settings"]},"auth":{"type":"noauth"}}}]},{"name":"Content","item":[{"name":"Read a static page","request":{"method":"GET","description":"`about`, `terms`, `privacy` or `posting_guidelines` (the add-ad form’s link), as the admin last saved them (the lab’s `staticPage`; BRD 4.1.22, 4.3.20). Both languages at once, in the lab’s light markup: blank lines between paragraphs, `## ` headings, `- ` bullets. Public.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/content/pages/:kind","host":["{{baseUrl}}"],"path":["v1","user","content","pages",":kind"],"variable":[{"key":"kind","value":"terms"}]},"auth":{"type":"noauth"}}}]},{"name":"Contact","item":[{"name":"Contact us screen","request":{"method":"GET","description":"The administration’s email, phone numbers and social accounts (the lab’s `contactInfo()`; only those the admin entered), and the message types with their names in Arabic and English. Public.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/contact","host":["{{baseUrl}}"],"path":["v1","user","contact"]},"auth":{"type":"noauth"}}},{"name":"Send a message","request":{"method":"POST","description":"To the administration (BRD 4.1.22; the lab’s `sendContactMessage`): type, name (2–50), email, Saudi mobile and the text (up to 500), all required. Guests can send; with a valid token 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`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/user/contact/messages","host":["{{baseUrl}}"],"path":["v1","user","contact","messages"]},"body":{"mode":"raw","raw":"{\n  \"type\": \"inquiry\",\n  \"name\": \"سارة العتيبي\",\n  \"email\": \"sara@example.com\",\n  \"mobile\": \"0551234567\",\n  \"text\": \"كيف أوثّق حسابي عبر نفاذ؟\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}}}]},{"name":"Home","item":[{"name":"Banners","request":{"method":"GET","description":"The banners running today (BRD 4.1.6, 4.3.13: active and inside their dates), in display order (the lab’s `PromoBanner`); an image or a video each, with the video’s poster. `link.type` says what a tap opens: an ad (`adId`), a section’s ads (`categoryId`), a web page (`url`) or nothing. Empty when none is running: the app hides the space. Also part of the home feed (API-051). Public.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/banners","host":["{{baseUrl}}"],"path":["v1","user","banners"]},"auth":{"type":"noauth"}}},{"name":"Home feed","request":{"method":"GET","description":"Everything the home screen shows in one request (BRD 4.1.6; the lab’s `homeFeed`): the running banners, the active sections, and up to 10 ads in each of Featured (boosted, newest first), Latest and Most viewed. `cityId` keeps the ads to one city: the app sends the city the user chose, else their saved place; without it, every city. Ads are the lab’s `Ad`, as a guest sees them. Public.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/home","host":["{{baseUrl}}"],"path":["v1","user","home"],"query":[{"key":"cityId","value":"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b","disabled":true,"description":"Only ads in this city; all cities when left out."}]},"auth":{"type":"noauth"}}}]},{"name":"Ads","item":[{"name":"My ad balance","request":{"method":"GET","description":"The free ads this month (the admin’s `freeAdsPerMonth`; unused ones don’t carry over) and the package’s ads (the lab’s `balance()`; BRD 4.1.13, 4.1.22).","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/account/balance","host":["{{baseUrl}}"],"path":["v1","user","account","balance"]}}},{"name":"Can I post an ad?","request":{"method":"GET","description":"Checked when the user taps \"Add an ad\" and after sending one (the lab’s `postingStatus()`): how many they can post, or why they can’t (`no_credit`, `package_expired`).","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/my-ads/posting-status","host":["{{baseUrl}}"],"path":["v1","user","my-ads","posting-status"]}}},{"name":"Search suggestions","request":{"method":"GET","description":"While the user types (BRD 4.1.9; the lab’s `searchSuggestions`): up to 8 section and subcategory names, then titles of public ads, most viewed first, that contain the text, forgiving how Arabic is typed. Empty for blank text. Guests and signed-in users.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/ads/suggestions","host":["{{baseUrl}}"],"path":["v1","user","ads","suggestions"],"query":[{"key":"q","value":"مجا","disabled":true,"description":"What the user has typed so far."}]},"auth":{"type":"noauth"}}},{"name":"Search and browse ads","request":{"method":"GET","description":"The lab’s `searchAds(AdQuery)` (BRD 4.1.9, 4.1.10, 4.1.12). `q` is found anywhere in the title, English title or description, forgiving how Arabic is typed (أ/إ/آ = ا, ة = ه, ى/ئ = ي, ؤ = و, diacritics and tatweel ignored) and case. Filters: section, subcategory, region, city, price range (whole riyals, inclusive), advertiser. Order: featured ads first (`featuredFirst=false` for an advertiser’s page), then `sort`, then id. Paged by cursor: send `nextCursor` back with the same query for the next page; `total` comes with the first page. Only public ads. Guests and signed-in users.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/ads","host":["{{baseUrl}}"],"path":["v1","user","ads"],"query":[{"key":"q","value":"ناقه","disabled":true,"description":"Found in the title, English title or description; forgiving of how Arabic is typed."},{"key":"categoryId","value":"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b","disabled":true},{"key":"subcategoryId","value":"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b","disabled":true},{"key":"regionId","value":"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b","disabled":true},{"key":"cityId","value":"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b","disabled":true},{"key":"minPrice","value":"5000","disabled":true,"description":"Whole riyals, inclusive."},{"key":"maxPrice","value":"20000","disabled":true,"description":"Whole riyals, inclusive."},{"key":"sellerId","value":"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b","disabled":true,"description":"One advertiser's ads (their page, BRD 4.1.12)."},{"key":"sort","value":"newest","disabled":true,"description":"One of: `newest`, `price_asc`, `price_desc`, `most_viewed`."},{"key":"featuredFirst","value":"true","disabled":true,"description":"Featured ads on top (BRD 4.1.9); `false` for an advertiser's page. Default true."},{"key":"cursor","value":"string","disabled":true,"description":"The `nextCursor` of the page before; send the same query with it."},{"key":"pageSize","value":"1","disabled":true,"description":"1–50, default 20."}]},"auth":{"type":"noauth"}}},{"name":"An ad","request":{"method":"GET","description":"The ad’s page (BRD 4.1.11; the lab’s `ad(id)`): the lab’s `Ad`. The advertiser (`GET /advertisers/:id`) and the heart (favourites) come from their own routes, and the deals disclaimer is the app’s own text. An ad that isn’t public (waiting, stopped, ended, deleted, or its section or seller hidden) is `AD_UNAVAILABLE` (410), unless it’s the caller’s own (any status but deleted); an unknown id is `NOT_FOUND`. Counts a view: once per viewer per 24 hours (D6), never the seller’s own. Guests: send `X-Device-Id` (a stable id the app makes once) so a guest is one viewer. Guests and signed-in users.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/ads/:id","host":["{{baseUrl}}"],"path":["v1","user","ads",":id"],"variable":[{"key":"id","value":"string"}]},"auth":{"type":"noauth"}}},{"name":"Count a tap on Call or WhatsApp","request":{"method":"POST","description":"Send when the user taps Call or WhatsApp on the ad’s page; the seller and the control panel see the counts. Once per viewer per 24 hours (D6); the seller’s own taps aren’t counted. Public ads only (`AD_UNAVAILABLE`). Guests and signed-in users.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/user/ads/:id/events","host":["{{baseUrl}}"],"path":["v1","user","ads",":id","events"],"variable":[{"key":"id","value":"string"}]},"body":{"mode":"raw","raw":"{\n  \"type\": \"call\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}}},{"name":"Share an ad","request":{"method":"POST","description":"The ad’s link for the share sheet (BRD 4.1.11; the lab’s `shareLink`), counting a share: once per viewer per 24 hours (D6), the seller’s own included. Public ads only (`AD_UNAVAILABLE`). The link opens the ad’s page or the app (API-074). Guests and signed-in users.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/ads/:id/share","host":["{{baseUrl}}"],"path":["v1","user","ads",":id","share"],"variable":[{"key":"id","value":"string"}]},"auth":{"type":"noauth"}}},{"name":"My recent searches","request":{"method":"GET","description":"Newest first, at most 10 (the lab’s `recentSearches`; BRD 4.1.9). Kept on the server for signed-in users so they follow the account (D10); guests keep theirs on the device.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/recent-searches","host":["{{baseUrl}}"],"path":["v1","user","recent-searches"]}}},{"name":"Remember a search","request":{"method":"POST","description":"The lab’s `addRecentSearch`: call it when a search is run. Repeating one (any wording that searches the same: \"ناقه\" = \"ناقة\") moves it to the top with the new wording instead of adding a copy; the oldest beyond 10 go. A blank one is ignored.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/user/recent-searches","host":["{{baseUrl}}"],"path":["v1","user","recent-searches"]},"body":{"mode":"raw","raw":"{\n  \"text\": \"ناقة مجاهيم\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Forget recent searches","request":{"method":"DELETE","description":"With `text`, that one search (the lab’s `removeRecentSearch`); without, all of them (`clearRecentSearches`).","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/recent-searches","host":["{{baseUrl}}"],"path":["v1","user","recent-searches"],"query":[{"key":"text","value":"string","disabled":true}]}}},{"name":"An advertiser","request":{"method":"GET","description":"The advertiser’s page (BRD 4.1.12; the lab’s `advertiser(id)`): name, photo, place, when they joined, whether Nafath verified them, how many public ads they have and in which sections. Their ads: `GET /ads?sellerId={id}&featuredFirst=false` (newest first). A disabled account is `ADVERTISER_UNAVAILABLE` (410); an unknown or deleted one is `NOT_FOUND`. Guests and signed-in users.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/advertisers/:id","host":["{{baseUrl}}"],"path":["v1","user","advertisers",":id"],"variable":[{"key":"id","value":"string"}]},"auth":{"type":"noauth"}}},{"name":"Report an ad","request":{"method":"POST","description":"The report sheet (BRD 4.1.11; the lab’s `reportAd`): a reason (`inappropriate`, `misleading`, `duplicate`, `prohibited`, `other`) and details (up to 500 characters; required for `other`). One report per user per ad (`ALREADY_REPORTED`); never on your own ad (`AD_ACTION_NOT_ALLOWED`). When an active ad’s open reports reach the admin’s threshold (`auto_block.reports`, 5 by default), it is stopped at once. Signed-in users only (guests sign in first, as in the lab).","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/user/ads/:id/reports","host":["{{baseUrl}}"],"path":["v1","user","ads",":id","reports"],"variable":[{"key":"id","value":"string"}]},"body":{"mode":"raw","raw":"{\n  \"reason\": \"misleading\",\n  \"details\": \"string\"\n}","options":{"raw":{"language":"json"}}}}}]},{"name":"My ads","item":[{"name":"My ads","request":{"method":"GET","description":"The seller’s ads in every status, newest change first (BRD 4.1.14; the lab’s `myAds`), each with its allowed `actions`. `status` keeps one status (an active ad past its end date counts as `ended`). `counts` covers all the seller’s ads whatever the filter, for the tabs. Deleted ads are gone.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/my-ads","host":["{{baseUrl}}"],"path":["v1","user","my-ads"],"query":[{"key":"status","value":"active","disabled":true,"description":"Only ads with this status (an active ad past its end date is `ended`). One of: `active`, `rejected`, `pending_review`, `paused`, `ended`."}]}}},{"name":"Send an ad for review","request":{"method":"POST","description":"The add-ad form (BRD 4.1.13; the lab’s `submitAd`). Upload each photo or the video first (`POST /uploads`), then send their ids in `media`, in order. Takes one credit (free ads first, then the package) and creates the ad `pending_review`; an admin approves it (on test servers it may be approved automatically after a few seconds). Every field problem comes back at once in `details`. `NO_CREDIT` / `PACKAGE_EXPIRED` (402) when nothing is left: nothing is saved. Contact numbers come from the seller’s profile.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/user/my-ads","host":["{{baseUrl}}"],"path":["v1","user","my-ads"]},"body":{"mode":"raw","raw":"{\n  \"title\": \"ناقة مجاهيم عمر 4 سنوات\",\n  \"titleEn\": \"Majaheem she-camel, 4 years\",\n  \"description\": \"string\",\n  \"price\": 12500,\n  \"categoryId\": \"string\",\n  \"subcategoryId\": \"string\",\n  \"media\": [\n    \"string\"\n  ],\n  \"regionId\": \"string\",\n  \"cityId\": \"string\",\n  \"neighborhoodId\": \"string\",\n  \"expiresOn\": \"2026-12-31\",\n  \"specs\": {\n    \"0199a3c4-…\": \"female\"\n  }\n}","options":{"raw":{"language":"json"}}}}},{"name":"One of my ads","request":{"method":"GET","description":"The seller’s ad in any status, with why it was rejected or stopped, featured-until, counters and allowed `actions` (the lab’s `allowedActions`). Anyone else’s ad, or a deleted one, is `NOT_FOUND`.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/my-ads/:id","host":["{{baseUrl}}"],"path":["v1","user","my-ads",":id"],"variable":[{"key":"id","value":"string"}]}}},{"name":"Edit my ad","request":{"method":"PATCH","description":"The whole form again, with the add-ad rules (BRD 4.1.17; the lab’s `updateAd`), free of charge. `media` may keep the ad’s current photos and add new uploads, in the new order; photos left out are deleted. An active ad stays published with the changes (D5: the admin sees it was edited); a rejected one goes back to review; a pending one keeps waiting. Paused and ended ads can’t be edited: `AD_ACTION_NOT_ALLOWED`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/user/my-ads/:id","host":["{{baseUrl}}"],"path":["v1","user","my-ads",":id"],"variable":[{"key":"id","value":"string"}]},"body":{"mode":"raw","raw":"{\n  \"title\": \"ناقة مجاهيم عمر 4 سنوات\",\n  \"titleEn\": \"Majaheem she-camel, 4 years\",\n  \"description\": \"string\",\n  \"price\": 12500,\n  \"categoryId\": \"string\",\n  \"subcategoryId\": \"string\",\n  \"media\": [\n    \"string\"\n  ],\n  \"regionId\": \"string\",\n  \"cityId\": \"string\",\n  \"neighborhoodId\": \"string\",\n  \"expiresOn\": \"2026-12-31\",\n  \"specs\": {\n    \"0199a3c4-…\": \"female\"\n  }\n}","options":{"raw":{"language":"json"}}}}},{"name":"Delete my ad","request":{"method":"DELETE","description":"Deletes the ad whatever its status (BRD 4.1.18; the lab’s `deleteAd`): it leaves every list and favourite, and its photos are deleted. Answers what the contribution sheet needs, as ending does.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/my-ads/:id","host":["{{baseUrl}}"],"path":["v1","user","my-ads",":id"],"variable":[{"key":"id","value":"string"}]}}},{"name":"End my ad (sold)","request":{"method":"POST","description":"Marks an active ad as ended (BRD 4.1.18; the lab’s `endAd`); it leaves every list at once. Only an active ad: `AD_ACTION_NOT_ALLOWED`. Answers the ad and what the contribution sheet needs (BRD 4.1.19): skip it when `contribution.enabled` is false.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/my-ads/:id/end","host":["{{baseUrl}}"],"path":["v1","user","my-ads",":id","end"],"variable":[{"key":"id","value":"string"}]}}}]},{"name":"Favourites","item":[{"name":"My favourites","request":{"method":"GET","description":"The saved ads, most recently saved first (BRD 4.1.20; the lab’s `favorites`), as the lab’s `Ad`. Ended and deleted ads drop out; one the admin stopped or sent back to review stays with its `status`, for the app to show as unavailable.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/favorites","host":["{{baseUrl}}"],"path":["v1","user","favorites"]}}},{"name":"Is it a favourite?","request":{"method":"GET","description":"The heart on an ad’s page (the lab’s `isFavorite`).","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/favorites/:adId","host":["{{baseUrl}}"],"path":["v1","user","favorites",":adId"],"variable":[{"key":"adId","value":"string"}]}}},{"name":"Save an ad","request":{"method":"PUT","description":"The heart, on (the lab’s `toggleFavorite` when it isn’t saved). Saving it again changes nothing. Only a public ad: `AD_UNAVAILABLE` otherwise.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/favorites/:adId","host":["{{baseUrl}}"],"path":["v1","user","favorites",":adId"],"variable":[{"key":"adId","value":"string"}]}}},{"name":"Unsave an ad","request":{"method":"DELETE","description":"The heart, off (the lab’s `toggleFavorite` when it is saved), whatever the ad’s state now. Removing one that isn’t saved changes nothing.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/user/favorites/:adId","host":["{{baseUrl}}"],"path":["v1","user","favorites",":adId"],"variable":[{"key":"adId","value":"string"}]}}}]}]},{"name":"System","auth":{"type":"noauth"},"item":[{"name":"Download a file\n\nPublic files (gallery media, icons, 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"]}}}]}]}