{"info":{"name":"Otlob Amel API","description":"One API for the three Otlob Amel clients: the customer app (`/v1/customer`), the provider app (`/v1/provider`), 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://www.otlobamel-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":"providerToken","value":"","description":"Session token for /v1/provider; 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":"Auth","item":[{"name":"Register","request":{"method":"POST","description":"Checks the registration form, saves it as an unverified sign-up and sends two 4-digit codes: by SMS to the mobile and by email to the address, each with its own `verificationId` (`mobile`, `email`). They work for 5 minutes. The account works only once `register/verify` confirms both; 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 a registration code","request":{"method":"POST","description":"Once that code’s `resendAfterSeconds` have passed: a new code for one `verificationId` from `register` (`mobile` resends the SMS, `email` the email); the id 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 a registration code","request":{"method":"POST","description":"One code at a time, in either order: `{ type: \"mobile\" | \"email\", verificationId, code }`, with the `verificationId` `register` answered for that type. While the other code is still due, answers `completed: false` with `verified` and `pending`; the code that completes the pair activates the account and answers `completed: true` with the session (the app then shows Home). A wrong code counts an attempt for that code only.","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  \"type\": \"mobile\",\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":"The mobile tab sends `mobile`, the email tab `email` (BRD 4.1.4): sends a 4-digit code by SMS or email to a registered, active account and answers its `verificationId`. `MOBILE_NOT_REGISTERED` / `EMAIL_NOT_REGISTERED` mean 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  \"email\": \"sara@example.com\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}}},{"name":"Resend the login code","request":{"method":"POST","description":"Once `resendAfterSeconds` have passed: a new code, by the same channel, 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 (`ar`, `en`, `ur`, `bn`; Urdu and Bengali get English text for now). 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/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 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/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":"Send a code to a new email","request":{"method":"POST","description":"Step 1 of changing the email (BRD 4.1.12.3): the code is emailed to the new address, and the answer carries the `verificationId` for step 2. Resend with `POST account/change-email/resend-otp`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/customer/account/change-email","host":["{{baseUrl}}"],"path":["v1","customer","account","change-email"]},"body":{"mode":"raw","raw":"{\n  \"email\": \"sara.new@example.com\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Resend the code to the new email","request":{"method":"POST","description":"Once `resendAfterSeconds` have passed: a new code for the `verificationId` from `account/change-email`, which stays the same.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/customer/account/change-email/resend-otp","host":["{{baseUrl}}"],"path":["v1","customer","account","change-email","resend-otp"]},"body":{"mode":"raw","raw":"{\n  \"verificationId\": \"string\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Change my email","request":{"method":"POST","description":"Step 2: the `verificationId` and the code emailed to the new address; swaps it in, and the old address no longer signs in. Every other device is signed out; this one stays signed in.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/customer/account/change-email/verify","host":["{{baseUrl}}"],"path":["v1","customer","account","change-email","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. Name and photo: a new photo goes in the `avatar` part (the old one is deleted), and `avatarFileId` sent empty removes it. The mobile and email change through `account/change-mobile` and `account/change-email`.\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":"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":"List a city’s neighbourhoods","request":{"method":"GET","description":"The active neighbourhoods of an active city, for the location screen, the search filter and the provider’s profile and areas, each with its centre (for maps and \"nearest\"). Public. Sorted by name in the request language.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/cities/:id/neighbourhoods","host":["{{baseUrl}}"],"path":["v1","customer","cities",":id","neighbourhoods"],"variable":[{"key":"id","value":"string"}]},"auth":{"type":"noauth"}}}]},{"name":"Services","item":[{"name":"List services","request":{"method":"GET","description":"The active home services, in display order: the home screen cards, the search filter, and the list providers pick theirs from. Public. `iconUrl` is null until an admin uploads one; the app may show its bundled icon for `code` meanwhile. An inactive or unknown service is refused with `SERVICE_NOT_AVAILABLE` where one is sent.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/services","host":["{{baseUrl}}"],"path":["v1","customer","services"]},"auth":{"type":"noauth"}}}]},{"name":"Settings","item":[{"name":"Read app settings","request":{"method":"GET","description":"The admin’s contact channels (Contact Us; BRD 4.3.20: only the configured ones are included, `phones` is always a list) and the apps’ store links. Public and cached. The grace period, VAT and reminders are server-side only.","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 (the account menu, and the Terms link on sign-up), as rich text (HTML). About is shared; `terms` and `privacy` are this app’s own (the customer’s or the provider’s, BRD 4.3.20). 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 (as in `GET settings`) and the message types, 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","request":{"method":"POST","description":"To the administration (BRD 4.1.12.4): name, mobile, email, an optional type and the text. 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  \"type\": \"inquiry\",\n  \"name\": \"سارة العتيبي\",\n  \"email\": \"sara@example.com\",\n  \"mobile\": \"0551234567\",\n  \"message\": \"كيف أغيّر الخدمات التي أقدمها؟\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}}}]},{"name":"Reports","item":[{"name":"Report reasons","request":{"method":"GET","description":"The active reasons for \"Report the service provider\" (BRD 4.1.9.1), in display order and the request language. When `notesRequired` is true, the report must give its details. Public (reporting itself needs login).","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/report-reasons","host":["{{baseUrl}}"],"path":["v1","customer","report-reasons"]},"auth":{"type":"noauth"}}}]},{"name":"Home","item":[{"name":"Banners","request":{"method":"GET","description":"The banners running today (BRD 4.1.6: active and inside their dates), in display order; an image or a video each. `link.type` says what a tap opens: nothing, a web page (`url`), a provider’s profile (`providerId`) or a service’s providers (`serviceId`). Empty when none is running: the app hides the space. Also part of the customer home (API-062). Guests allowed.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/customer/banners","host":["{{baseUrl}}"],"path":["v1","customer","banners"]},"auth":{"type":"noauth"}}}]}]},{"name":"Provider","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{providerToken}}","type":"string"}]},"item":[{"name":"Auth","item":[{"name":"Register","request":{"method":"POST","description":"One multipart form (BRD 4.2.5): `accountType` `individual` with `iqamaNumber`, or `company` with `crNumber` and the `crFile` part (D13). Checks the form, saves it as an unverified sign-up and sends two 4-digit codes: by SMS to the mobile and by email to the address, each with its own `verificationId` (`mobile`, `email`). They work for 5 minutes. The account works only once `register/verify` confirms both; 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.\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 `crFile` as file parts.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/provider/auth/register","host":["{{baseUrl}}"],"path":["v1","provider","auth","register"]},"body":{"mode":"formdata","formdata":[{"key":"accountType","type":"text","value":"company","description":"`individual` (an individual advertiser) or `company` (a company or"},{"key":"name","type":"text","value":"شركة الريان للمقاولات","description":"The provider's or company's name: letters and numbers, 2–100."},{"key":"mobile","type":"text","value":"0551234567","description":"Saudi mobile number, `05XXXXXXXX`; verified by SMS."},{"key":"email","type":"text","value":"info@alrayan.sa","description":"Required; unique, at most 100 characters; verified by email (D21)."},{"key":"iqamaNumber","type":"text","value":"2123456789","disabled":true,"description":"Individuals: the residence (Iqama) number, 10 digits starting with 2."},{"key":"crNumber","type":"text","value":"1010123456","disabled":true,"description":"Companies: the commercial registration number, 10 digits."},{"key":"acceptTerms","type":"text","value":"true","description":"The Terms and Conditions checkbox; must be `true`."},{"key":"crFile","type":"file","description":"PDF, PNG, JPG, up to 10 MB."}]},"auth":{"type":"noauth"}}},{"name":"Resend a registration code","request":{"method":"POST","description":"Once that code’s `resendAfterSeconds` have passed: a new code for one `verificationId` from `register` (`mobile` resends the SMS, `email` the email); the id stays the same.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/provider/auth/register/resend-otp","host":["{{baseUrl}}"],"path":["v1","provider","auth","register","resend-otp"]},"body":{"mode":"raw","raw":"{\n  \"verificationId\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}}},{"name":"Verify a registration code","request":{"method":"POST","description":"One code at a time, in either order: `{ type: \"mobile\" | \"email\", verificationId, code }`, with the `verificationId` `register` answered for that type. While the other code is still due, answers `completed: false` with `verified` and `pending`; the code that completes the pair activates the account and answers `completed: true` with the session (the app then shows Home). A wrong code counts an attempt for that code only.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/provider/auth/register/verify","host":["{{baseUrl}}"],"path":["v1","provider","auth","register","verify"]},"body":{"mode":"raw","raw":"{\n  \"type\": \"mobile\",\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(\"providerToken\", token);","}"]}}]},{"name":"Log in","request":{"method":"POST","description":"The mobile tab sends `mobile`, the email tab `email` (BRD 4.1.4): sends a 4-digit code by SMS or email to a registered, active account and answers its `verificationId`. `MOBILE_NOT_REGISTERED` / `EMAIL_NOT_REGISTERED` mean the app should offer registration.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/provider/auth/login","host":["{{baseUrl}}"],"path":["v1","provider","auth","login"]},"body":{"mode":"raw","raw":"{\n  \"mobile\": \"0551234567\",\n  \"email\": \"sara@example.com\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}}},{"name":"Resend the login code","request":{"method":"POST","description":"Once `resendAfterSeconds` have passed: a new code, by the same channel, 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/provider/auth/login/resend-otp","host":["{{baseUrl}}"],"path":["v1","provider","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/provider/auth/login/verify","host":["{{baseUrl}}"],"path":["v1","provider","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(\"providerToken\", 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/provider/auth/logout","host":["{{baseUrl}}"],"path":["v1","provider","auth","logout"]}}}]},{"name":"Account","item":[{"name":"My account","request":{"method":"GET","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/provider/account","host":["{{baseUrl}}"],"path":["v1","provider","account"]}}},{"name":"Change my language","request":{"method":"PATCH","description":"The \"Change language\" screen (`ar`, `en`, `ur`, `bn`; Urdu and Bengali get English text for now). 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/provider/account","host":["{{baseUrl}}"],"path":["v1","provider","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/provider/account","host":["{{baseUrl}}"],"path":["v1","provider","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/provider/account/change-mobile","host":["{{baseUrl}}"],"path":["v1","provider","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/provider/account/change-mobile/resend-otp","host":["{{baseUrl}}"],"path":["v1","provider","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/provider/account/change-mobile/verify","host":["{{baseUrl}}"],"path":["v1","provider","account","change-mobile","verify"]},"body":{"mode":"raw","raw":"{\n  \"verificationId\": \"vrf_Zk3n0x8Qy1u4b2V7s9Lr5mT6pWcA0dEfGhIjKlMnOpQ\",\n  \"code\": \"1234\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Send a code to a new email","request":{"method":"POST","description":"Step 1 of changing the email (BRD 4.1.12.3): the code is emailed to the new address, and the answer carries the `verificationId` for step 2. Resend with `POST account/change-email/resend-otp`.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/provider/account/change-email","host":["{{baseUrl}}"],"path":["v1","provider","account","change-email"]},"body":{"mode":"raw","raw":"{\n  \"email\": \"sara.new@example.com\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Resend the code to the new email","request":{"method":"POST","description":"Once `resendAfterSeconds` have passed: a new code for the `verificationId` from `account/change-email`, which stays the same.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/provider/account/change-email/resend-otp","host":["{{baseUrl}}"],"path":["v1","provider","account","change-email","resend-otp"]},"body":{"mode":"raw","raw":"{\n  \"verificationId\": \"string\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Change my email","request":{"method":"POST","description":"Step 2: the `verificationId` and the code emailed to the new address; swaps it in, and the old address no longer signs in. Every other device is signed out; this one stays signed in.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/provider/account/change-email/verify","host":["{{baseUrl}}"],"path":["v1","provider","account","change-email","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/provider/profile","host":["{{baseUrl}}"],"path":["v1","provider","profile"]}}},{"name":"Save my profile","request":{"method":"PATCH","description":"Send only the fields that changed: `name`, `bio`, `cityId` and `neighbourhoodId` (from `GET /cities` and `GET /cities/{id}/neighbourhoods`; the neighbourhood must be in the city), the photo as the `avatar` part and the cover as the `cover` part (replaced ones are deleted); `avatarFileId` / `coverFileId` / `bio` sent empty remove them. The account type, Iqama and CR are read-only; the mobile and email change through `account/change-mobile` and `account/change-email`.\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`, `cover` as file parts.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/provider/profile","host":["{{baseUrl}}"],"path":["v1","provider","profile"]},"body":{"mode":"formdata","formdata":[{"key":"name","type":"text","value":"شركة الريان للمقاولات","disabled":true,"description":"The provider's or company's name: letters and digits, 2–100."},{"key":"bio","type":"text","value":"خبرة أكثر من 10 سنوات في أعمال الكهرباء والسباكة.","disabled":true,"description":"The description customers read (BRD 4.1.9.1), at most 500"},{"key":"cityId","type":"text","value":"string","disabled":true,"description":"From `GET /cities`; sent empty, removes the city and neighbourhood. A"},{"key":"neighbourhoodId","type":"text","value":"string","disabled":true,"description":"From `GET /cities/{cityId}/neighbourhoods`; must be in the city."},{"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":"coverFileId","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 `cover` file instead."},{"key":"avatar","type":"file","description":"PNG, JPG, WEBP, up to 10 MB."},{"key":"cover","type":"file","description":"PNG, JPG, WEBP, up to 10 MB."}]}}}]},{"name":"My services","item":[{"name":"My services","request":{"method":"GET","description":"Every active service the administration offers, in display order, with `selected` on the ones you provide (BRD 4.2.11). Only these can be chosen; there is no adding a service of your own.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/provider/me/services","host":["{{baseUrl}}"],"path":["v1","provider","me","services"]}}},{"name":"Save my services","request":{"method":"PUT","description":"The whole selection: one or more service ids, replacing the previous ones (BRD: \"at least one service must be selected\"). An inactive or unknown one is `SERVICE_NOT_AVAILABLE`, saving nothing. Customers find you by these services, and you are listed only with at least one (API-044). Answers the screen as saved.","header":[{"key":"Accept-Language","value":"{{lang}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/v1/provider/me/services","host":["{{baseUrl}}"],"path":["v1","provider","me","services"]},"body":{"mode":"raw","raw":"{\n  \"serviceIds\": [\n    \"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b\"\n  ]\n}","options":{"raw":{"language":"json"}}}}}]},{"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/provider/cities","host":["{{baseUrl}}"],"path":["v1","provider","cities"]},"auth":{"type":"noauth"}}},{"name":"List a city’s neighbourhoods","request":{"method":"GET","description":"The active neighbourhoods of an active city, for the location screen, the search filter and the provider’s profile and areas, each with its centre (for maps and \"nearest\"). Public. Sorted by name in the request language.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/provider/cities/:id/neighbourhoods","host":["{{baseUrl}}"],"path":["v1","provider","cities",":id","neighbourhoods"],"variable":[{"key":"id","value":"string"}]},"auth":{"type":"noauth"}}}]},{"name":"Services","item":[{"name":"List services","request":{"method":"GET","description":"The active home services, in display order: the home screen cards, the search filter, and the list providers pick theirs from. Public. `iconUrl` is null until an admin uploads one; the app may show its bundled icon for `code` meanwhile. An inactive or unknown service is refused with `SERVICE_NOT_AVAILABLE` where one is sent.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/provider/services","host":["{{baseUrl}}"],"path":["v1","provider","services"]},"auth":{"type":"noauth"}}}]},{"name":"Settings","item":[{"name":"Read app settings","request":{"method":"GET","description":"The admin’s contact channels (Contact Us; BRD 4.3.20: only the configured ones are included, `phones` is always a list) and the apps’ store links. Public and cached. The grace period, VAT and reminders are server-side only.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/provider/settings","host":["{{baseUrl}}"],"path":["v1","provider","settings"]},"auth":{"type":"noauth"}}}]},{"name":"Content","item":[{"name":"Read a page","request":{"method":"GET","description":"About, Terms or Privacy (the account menu, and the Terms link on sign-up), as rich text (HTML). About is shared; `terms` and `privacy` are this app’s own (the customer’s or the provider’s, BRD 4.3.20). 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/provider/content/pages/:key","host":["{{baseUrl}}"],"path":["v1","provider","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 (as in `GET settings`) and the message types, in the request language. Public.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/provider/contact","host":["{{baseUrl}}"],"path":["v1","provider","contact"]},"auth":{"type":"noauth"}}},{"name":"Send a message","request":{"method":"POST","description":"To the administration (BRD 4.1.12.4): name, mobile, email, an optional type and the text. 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/provider/contact/messages","host":["{{baseUrl}}"],"path":["v1","provider","contact","messages"]},"body":{"mode":"raw","raw":"{\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 subscription or a boost). 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/provider/payments/:id","host":["{{baseUrl}}"],"path":["v1","provider","payments",":id"],"variable":[{"key":"id","value":"string"}]}}}]},{"name":"Packages","item":[{"name":"Packages","request":{"method":"GET","description":"The active packages for your account type (BRD 4.2.6; D4), each with its name, description, picture, price (VAT excluded) and duration; empty when none is available (Figma \"لا توجد باقات\"). `isCurrent` marks the package of your active subscription.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/provider/packages","host":["{{baseUrl}}"],"path":["v1","provider","packages"]}}},{"name":"Package details","request":{"method":"GET","description":"One package before subscribing: its benefits and what subscribing charges (price, VAT at today’s rate and total; D8). An inactive package, or one for the other account type, is `PACKAGE_NOT_AVAILABLE` (Figma \"الباقة لم تعد متاحة\").","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/provider/packages/:id","host":["{{baseUrl}}"],"path":["v1","provider","packages",":id"],"variable":[{"key":"id","value":"string"}]}}}]},{"name":"Subscriptions","item":[{"name":"My current subscription","request":{"method":"GET","description":"The active subscription: package, picture, fee, VAT and total, start and end dates, remaining days and benefits (Figma \"اشتراكي الحالي\"; the provider home card). `subscription: null` means none: the app shows the packages. Reading never creates or charges anything.","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/provider/subscriptions/current","host":["{{baseUrl}}"],"path":["v1","provider","subscriptions","current"]}}},{"name":"My subscriptions log","request":{"method":"GET","description":"Your current and past subscriptions, newest first (BRD 4.2.7: package name and picture, fee, VAT, total, start, end, period, status active / expired). An expired one has `canRenew`. Empty: \"No subscriptions have been registered yet\".","header":[{"key":"Accept-Language","value":"{{lang}}"}],"url":{"raw":"{{baseUrl}}/v1/provider/subscriptions","host":["{{baseUrl}}"],"path":["v1","provider","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 screen (Figma \"تأكيد الاشتراك\": package, fee, VAT, total from `GET packages/{id}`). Only one active subscription at a time (`SUBSCRIPTION_ALREADY_ACTIVE`, Figma \"يوجد اشتراك نشط\"); renewing is this call once the last one expired. The total (VAT at today’s rate) is charged through the payment gateway: open `payment.checkoutUrl` when set; the subscription starts when the payment is confirmed (at once with the fake gateway, Figma \"تم الاشتراك بنجاح\"). 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/provider/subscriptions","host":["{{baseUrl}}"],"path":["v1","provider","subscriptions"]},"body":{"mode":"raw","raw":"{\n  \"packageId\": \"0199a3c4-5b6e-7f80-9a1b-2c3d4e5f6a7b\"\n}","options":{"raw":{"language":"json"}}}}}]}]},{"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"]}}}]}]}