# Doctor API > فیلد `owner_status` (`claimed` | `unclaimed` | `pending_transfer`) به خروجی لیست و جزئیات پزشک > اضافه شده است — پروفایل `unclaimed` (ایمپورت نظام پزشکی) در سایت دکمهٔ «تصاحب پروفایل» میگیرد > (`docs/api/doctor-claim.md`) و نوبتدهی آنلاینش غیرفعال است. > > **امتیاز فقط برای پروفایل `claimed` منتشر میشود.** برای هر `owner_status` غیر از `claimed` > (یعنی `unclaimed` / `pending_transfer`) فیلدهای `point` و `satisfaction` مقدار `null` برمیگردند — > مقدار پیشفرض انتیتی (`3.5` / `60`) نشتی نمیکند تا امتیاز جعلی/`AggregateRating` جعلی ساخته نشود. > بعد از تصاحب و claimed شدن، امتیاز واقعی بهصورت خودکار برمیگردد. > **Prefix:** `/api/v1/doctor`, `/api/v1/doctors`, `/api/v1/clinic-pro/doctor-address*` > > Numeric path params on the address routes (`doctor-address/{id}`, `doctor-addresses/{doctorId}`) require `\d+`; a non-numeric value returns a clean `404` instead of a `500`. --- ## POST `/api/v1/doctor` Create a doctor profile for the authenticated user. **Permission:** `AUTH` — any authenticated user ### Request Body (`application/json`) ```json { "title": "دکتر علی احمدی", "gender": "male", "medical_system_code": "12345", "degree": "متخصص", "info": "توضیحات درباره پزشک", "specialties": [1, 2], "doctor_services": [3, 4] } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `title` | string | ✅ | Full name with title | | `gender` | string | ❌ | `"male"` or `"female"` | | `medical_system_code` | string | ❌ | Nظام پزشکی code | | `degree` | string | ❌ | Academic degree | | `info` | string | ❌ | Bio/description | | `specialties` | integer[] | ❌ | Array of specialty IDs | | `doctor_services` | integer[] | ❌ | Array of doctor service IDs | | `activity_time` | integer | ❌ | Unix timestamp (ثانیه) تاریخ شروع فعالیت؛ مبنای محاسبهٔ `experience` (سال تجربه) در پاسخ | ### Response `201` ```json { "success": true, "data": { "uuid": "550e8400-e29b-41d4-a716-446655440000", "title": "دکتر علی احمدی", "gender": "male", "medical_system_code": "12345", "degree": "متخصص", "info": "...", "doctor_rate": null, "active_doctor_appointment": false, "specialties": [], "doctor_services": [], "created_at": 1717000000 } } ``` ### Errors | Code | HTTP | Description | |------|------|-------------| | `ERR_AUTH_001` | 401 | Missing or invalid token | | `ERR_CONFLICT_001` | 409 | Doctor profile already exists for this user | | `ERR_VALIDATION_002` | 422 | Missing required field | | `ERR_VALIDATION_001` | 422 | نام پزشک شمارهتلفن یا مقدار آزمایشی است | --- ## GET `/api/v1/doctor/{uuid}` Get doctor detail with clinics. **Permission:** `PUBLIC` ### Path Parameters | Param | Type | Description | |-------|------|-------------| | `uuid` | string (UUID) | Doctor UUID | ### Response `200` ```json { "success": true, "data": { "data": { "uuid": "550e8400-...", "name": "دکتر علی احمدی", "gender": "man", "medical_system_code": "12345", "degree": "specialist", "detail": "...", "img": [], "social_media": { "instagram": "https://instagram.com/dr.example", "telegram": "https://t.me/dr_example", "aparat": null, "youtube": null, "linkedin": null }, "satisfaction": "60", "point": "3.5", "free_turn": "دوشنبه 09:00–13:00", "hours_of_work": "شنبه: 09:00–13:00 و 14:00–18:00 | یکشنبه: 09:00–13:00", "active": true, "owner_status": "claimed", "specialties": [{ "uuid": "...", "id": "1", "name": "قلب و عروق", "parent_id": null }], "expertise": [{ "uuid": "...", "id": "3", "name": "نوار قلب" }], "address": [], "state": [], "city": [], "clinics": [{ "uuid": "...", "name": "کلینیک الوند", "address": "...", "telephone": "..." }], "representation": { "id": 12, "uuid": "9c1...", "full_name": "علی محمدی" } } } } ``` > ⚠️ **Double-nested:** Frontend extracts with `data?.data?.data` > > ℹ️ `representation` نمایندهی مالکِ پزشک است؛ برای پزشکِ بدون نماینده `null`. ### Errors | Code | HTTP | Description | |------|------|-------------| | `ERR_NOT_FOUND_001` | 404 | Doctor not found | --- ## GET `/api/v1/clinic/my-doctor/{doctorUuid}` Get doctor detail for clinic owner — only doctors who are members of the authenticated clinic. **Permission:** `ROLE_CLINIC` ### Path Parameters | Param | Type | Description | |-------|------|-------------| | `doctorUuid` | string (UUID) | Doctor UUID | ### Response `200` ```json { "success": true, "data": { "data": { "uuid": "...", "title": "دکتر علی احمدی", "specialties": [...], "clinics": [{ "uuid": "...", "name": "کلینیک نور", "address": "...", "telephone": "..." }] } } } ``` > ⚠️ **Double-nested:** Frontend extracts with `data?.data?.data` > **Side note:** Returns only the authenticated clinic's data in the `clinics` array (not all clinics of the doctor). ### Errors | Code | HTTP | Description | |------|------|-------------| | `ERR_VALIDATION_002` | 404 | Clinic or doctor not found | | `ERR_AUTH_006` | 403 | Doctor is not a member of this clinic | --- ### Schedule Fields Notes **تجمیع همهٔ برنامهها (2026-07):** این فیلدها روی **همهٔ** برنامههای هفتگی پزشک محاسبه میشوند — برنامهٔ مطب شخصی (`clinic_id IS NULL`) بهعلاوهٔ یک برنامه به ازای هر کلینیک. پزشکی که برنامهٔ شخصی خالی/خاموش ولی برنامهٔ کلینیکیِ فعال دارد، `active=true` میگیرد؛ برنامهٔ یک محیط هرگز محیط دیگر را نمیپوشاند. | Field | When at least one schedule is bookable | When none | |-------|---------------------------------------|-----------| | `free_turn` | نزدیکترین روز/ساعت کاری از امروز، بین همهٔ برنامههای روشن (مثلاً «دوشنبه ۹:۰۰–۱۳:۰۰») | «نوبت آزادی موجود نیست» | | `hours_of_work` | خلاصه ساعتهای همان برنامهای که `free_turn` را داده (ساعتهای دو محل با هم ترکیب نمیشوند) | «برنامه کاری تنظیم نشده» | | `active` | `activeDoctorAppointment && (∃ schedule: online_booking_enabled && has_active_sessions)` | `false` — نوبتدهی غیرفعال | > **نوبتدهی آنلاین غیرفعال:** اگر `meta.online_booking_enabled` در **همهٔ** برنامههای پزشک > `false` باشد، `free_turn` برابر `"نوبتدهی آنلاین غیرفعال است"` و `active` برابر `false` > برمیگردد؛ `hours_of_work` در صورت وجود برنامه حفظ میشود. تا وقتی حتی یک برنامه روشن و > دارای روز فعال باشد، همان مبنا قرار میگیرد. چنین پزشکی (همه خاموش) در لیست عمومی > `GET /api/v1/doctors` (پیشفرض `active=true`) نمایش داده نمیشود، ولی صفحهی تکی > `GET /api/v1/doctor/{slug}` همچنان قابل دسترسی است. --- ## GET `/api/v1/doctors` List doctors with pagination and filters. **Permission:** `PUBLIC` ### Query Parameters | Param | Type | Required | Description | |-------|------|----------|-------------| | `page` | integer | ❌ | Default: 1 | | `limit` | integer | ❌ | Default: 10. **حداکثر ۵۰** — مقادیر بزرگتر بیصدا به ۵۰ کاهش مییابند. مقدار واقعاً اعمالشده در `meta.limit` برمیگردد؛ برای پیمایش کامل به `meta.totalPages` تکیه کن، نه به «تعداد آیتم کمتر از limit درخواستی» | | `search` | string | ❌ | Search in title | | `specialty_id` | integer | ❌ | Filter by specialty ID | | `city_id` | integer | ❌ | Filter by city ID — شامل دکترهایی که آدرس شخصیشان (`doctor_addresses.city_id`, با `doctor_id` مقداردار) در آن شهر است یا از طریق کلینیکی که آدرس آن در آن شهر است (`doctor_addresses.clinic_id`) | | `state_id` | integer | ❌ | Filter by province ID — بر اساس آدرس شخصی پزشک (`doctor_addresses.province_id`) یا آدرس کلینیک | | `domain` | string | ❌ | دامنهی سایتِ درخواستکننده. اگر دامنهی یک **نماینده سراسری** باشد، فقط پزشکانِ همان نماینده برمیگردند و `city_id`/`state_id` نادیده گرفته میشوند؛ دامنه شهری/ناشناخته اثری ندارد | ### Response `200` ```json { "success": true, "data": [ { "uuid": "...", "name": "دکتر علی احمدی", "gender": "man", "degree": "specialist", "img": [], "specialties": [{ "uuid": "...", "id": "1", "name": "قلب و عروق" }], "satisfaction": "60", "point": "3.5", "free_turn": "دوشنبه 09:00–13:00", "hours_of_work": "شنبه: 09:00–13:00 و 14:00–18:00 | یکشنبه: 09:00–13:00", "active": true, "owner_status": "claimed", "city": [ { "uuid": "7bfb989e-...", "id": "123", "name": "یاسوج", "parent": "23" } ], "state": [ { "uuid": "7bfb5705-...", "id": "23", "name": "کهگیلویه و بویراحمد" } ] } ], "meta": { "totalRecords": 50, "totalPages": 3, "currentPage": 1, "limit": 50 } } ``` > ℹ️ `point` و `satisfaction` فقط برای `owner_status="claimed"` مقدار دارند؛ برای `unclaimed`/`pending_transfer` هر دو `null` هستند. ### اعتبارسنجی نام پزشک `name` نمیتواند شمارهتلفن (`^0?9\d{9}$`) یا مقدار آزمایشی (`test`، `تست`، `-`، `null`) باشد. این مقادیر در **هر** مسیر نوشتن با `422` رد میشوند — API عمومی، پنل ادمین، import و دعوت کلینیک — چون گارد روی خودِ Entity نشسته است (`App\Shared\Util\DisplayName`). دلیل: نام پزشک در `