{"openapi":"3.0.3","info":{"title":"Basmtak Clinics Partner API","version":"v1","description":"Read a clinic's patients, appointments and invoices, book and cancel appointments, and receive signed webhooks (appointment.*, invoice.*, claim.*, patient.erased). Money is always integer halalas (1 SAR = 100); ids of clinical rows are ULIDs; times are ISO-8601 in the branch's zone. Errors are JSON {error, message, details}; the message follows Accept-Language (ar or en). Lists answer {data, meta} and page with ?page and ?per_page (at most 100). No diagnoses, clinical notes or photos ever travel through this API.\n","contact":{"name":"Basmtak support","email":"support@basmtak.com"}},"servers":[{"url":"/partner/v1"}],"security":[{"bearer":[]}],"tags":[{"name":"meta","description":"Connectivity and what this server offers"},{"name":"patients","description":"The patient read subset (demographics and identifiers, never clinical content)"},{"name":"appointments","description":"Appointments, booking and cancellation"},{"name":"invoices","description":"Issued invoices and their payment state"},{"name":"webhooks","description":"REST hooks (Zapier and Make)"}],"paths":{"/ping":{"get":{"tags":["meta"],"operationId":"ping","summary":"Check the token and see the client's abilities","x-ability":"any","responses":{"200":{"description":"The client, its abilities and the clinic"},"401":{"$ref":"#/components/responses/Error"}}}},"/meta":{"get":{"tags":["meta"],"operationId":"meta","summary":"Abilities, the event catalogue, rate limits and the spec version","x-ability":"any","responses":{"200":{"description":"{data: {api_version, spec_version, abilities, client_abilities, events, rate_limits, per_page, webhooks, docs_url, openapi_url}}"}}}},"/patients":{"get":{"tags":["patients"],"operationId":"listPatients","summary":"List patients","description":"Demographics and identifiers only. Search by file_no, national id or phone through the filters below.","x-ability":"patients:read","parameters":[{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/per_page"},{"name":"file_no","in":"query","schema":{"type":"string"}},{"name":"phone","in":"query","description":"E.164","schema":{"type":"string"}},{"name":"updated_since","in":"query","description":"ISO-8601; only patients changed since then","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"A page of patients","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatientPage"}}}}}}},"/patients/{id}":{"parameters":[{"$ref":"#/components/parameters/ulid"}],"get":{"tags":["patients"],"operationId":"getPatient","summary":"One patient","x-ability":"patients:read","responses":{"200":{"description":"The patient","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Patient"}}}}}},"404":{"$ref":"#/components/responses/Error"}}}},"/appointments":{"get":{"tags":["appointments"],"operationId":"listAppointments","summary":"List appointments","x-ability":"appointments:read","parameters":[{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/per_page"},{"name":"branch_id","in":"query","schema":{"type":"integer"}},{"name":"patient_id","in":"query","schema":{"type":"string"}},{"name":"practitioner_id","in":"query","schema":{"type":"string"}},{"name":"from","in":"query","description":"ISO-8601 date or date-time (inclusive)","schema":{"type":"string"}},{"name":"to","in":"query","description":"ISO-8601 date or date-time (exclusive)","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string","enum":["booked","confirmed","arrived","in_progress","completed","cancelled","no_show"]}}],"responses":{"200":{"description":"A page of appointments","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppointmentPage"}}}}}},"post":{"tags":["appointments"],"operationId":"createAppointment","summary":"Book an appointment","description":"Books a free slot (GET the clinic's slots through the portal rules the SCHEDULING package publishes). external_ref makes the call idempotent per API client: the same external_ref answers 200 with the same appointment, never a second booking. The patient is named by id or by phone (a new patient file is opened when the phone is unknown).\n","x-ability":"appointments:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppointmentWrite"},"example":{"branch_id":1,"practitioner_id":"01J9ZJ0000000000000000000D","appointment_type_id":3,"starts_at":"2027-04-02T18:00:00+03:00","patient_id":"01J9ZJ0000000000000000000P","external_ref":"crm-1001","note":"يفضل الفترة المسائية"}}}},"responses":{"201":{"description":"The appointment","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Appointment"}}}}}},"200":{"description":"The same external_ref again — the existing appointment, with duplicate true"},"409":{"$ref":"#/components/responses/Error"},"422":{"$ref":"#/components/responses/Error"}}}},"/appointments/{id}":{"parameters":[{"$ref":"#/components/parameters/ulid"}],"get":{"tags":["appointments"],"operationId":"getAppointment","summary":"One appointment","x-ability":"appointments:read","responses":{"200":{"description":"The appointment","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Appointment"}}}}}},"404":{"$ref":"#/components/responses/Error"}}}},"/appointments/{id}/cancel":{"parameters":[{"$ref":"#/components/parameters/ulid"}],"post":{"tags":["appointments"],"operationId":"cancelAppointment","summary":"Cancel an appointment","description":"Cancelling twice is a no-op (200). A completed or in-progress appointment answers 409 appointment_locked.","x-ability":"appointments:write","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","enum":["patient_request","clinic","no_show","other"]}}},"example":{"reason":"patient_request"}}}},"responses":{"200":{"description":"The cancelled appointment"},"404":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"}}}},"/invoices":{"get":{"tags":["invoices"],"operationId":"listInvoices","summary":"List issued invoices","x-ability":"invoices:read","parameters":[{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/per_page"},{"name":"patient_id","in":"query","schema":{"type":"string"}},{"name":"branch_id","in":"query","schema":{"type":"integer"}},{"name":"status","in":"query","schema":{"type":"string","enum":["issued","partially_paid","paid","void"]}},{"name":"issued_from","in":"query","description":"ISO-8601 date (inclusive)","schema":{"type":"string"}},{"name":"issued_to","in":"query","description":"ISO-8601 date (exclusive)","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of invoices","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvoicePage"}}}}}}},"/invoices/{id}":{"parameters":[{"$ref":"#/components/parameters/ulid"}],"get":{"tags":["invoices"],"operationId":"getInvoice","summary":"One invoice with its lines and payments","x-ability":"invoices:read","responses":{"200":{"description":"The invoice","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}}}},"404":{"$ref":"#/components/responses/Error"}}}},"/webhooks":{"get":{"tags":["webhooks"],"operationId":"listWebhooks","summary":"Your subscriptions","x-ability":"webhooks","parameters":[{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/per_page"}],"responses":{"200":{"description":"A page of subscriptions"}}},"post":{"tags":["webhooks"],"operationId":"subscribeWebhook","summary":"Subscribe (REST hook)","description":"The answer carries the subscription id and its signing secret. The secret is shown only here. The client must hold the ability that reads each event's data (appointments:read, invoices:read, patients:read).","x-ability":"webhooks","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookWrite"},"example":{"target_url":"https://hooks.zapier.com/hooks/standard/123/abc","event":"appointment.created"}}}},"responses":{"201":{"description":"{id, secret, data}"},"403":{"$ref":"#/components/responses/Error"},"422":{"$ref":"#/components/responses/Error"}}}},"/webhooks/events":{"get":{"tags":["webhooks"],"operationId":"listWebhookEvents","summary":"The events, each with the ability it needs and sample data","x-ability":"webhooks","responses":{"200":{"description":"{data: [{event, ability, sample}]}"}}}},"/webhooks/events/{event}/recent":{"get":{"tags":["webhooks"],"operationId":"listRecentWebhookEvents","summary":"Recent real objects of one event (REST hook perform list)","description":"The newest real objects of one event, each wrapped in the envelope a delivery carries {id, event, tenant_id, occurred_at, api_version, data}, newest first. Zapier calls this as the REST Hook trigger's perform list when a user tests a Zap; Make uses it to fetch sample data. id is event:object-id (stable, for de-duplication), not the uuid of a live delivery. Besides webhooks the client needs the ability that receives the event. meta.sample is the static sample, for a clinic with no data yet; data is empty until the event's package publishes its recent objects.\n","x-ability":"webhooks","parameters":[{"name":"event","in":"path","required":true,"schema":{"type":"string","enum":["appointment.created","appointment.updated","appointment.cancelled","invoice.issued","invoice.paid","claim.updated","patient.erased"]}},{"name":"limit","in":"query","description":"1 to 25, default 3","schema":{"type":"integer"}}],"responses":{"200":{"description":"{data: [{id, event, tenant_id, occurred_at, api_version, data}], meta: {event, count, sample}}"},"403":{"$ref":"#/components/responses/Error"},"422":{"$ref":"#/components/responses/Error"}}}},"/webhooks/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"get":{"tags":["webhooks"],"operationId":"getWebhook","summary":"One subscription","x-ability":"webhooks","responses":{"200":{"description":"The subscription"},"404":{"$ref":"#/components/responses/Error"}}},"put":{"tags":["webhooks"],"operationId":"updateWebhook","summary":"Change the URL, the events, the description or switch it on or off","x-ability":"webhooks","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookWrite"},"example":{"events":["appointment.created","appointment.cancelled"],"status":"active"}}}},"responses":{"200":{"description":"The subscription"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"422":{"$ref":"#/components/responses/Error"}}},"delete":{"tags":["webhooks"],"operationId":"unsubscribeWebhook","summary":"Unsubscribe (REST hook)","x-ability":"webhooks","responses":{"204":{"description":"Removed"},"404":{"$ref":"#/components/responses/Error"}}}},"/webhooks/{id}/rotate":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"post":{"tags":["webhooks"],"operationId":"rotateWebhookSecret","summary":"Mint a new signing secret (the old one stops at once)","x-ability":"webhooks","responses":{"200":{"description":"{id, secret, data} — the new secret, shown only here"},"404":{"$ref":"#/components/responses/Error"}}}},"/webhooks/{id}/test":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"post":{"tags":["webhooks"],"operationId":"testWebhook","summary":"Send a signed ping to this subscription now","description":"Delivers a `ping` event (data {subscription_id, message}) and answers the delivery row with its status and the receiver's HTTP status.","x-ability":"webhooks","responses":{"200":{"description":"{data: Delivery}"},"404":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"}}}}},"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer","description":"Authorization: Bearer bmc_… (or X-Api-Key)"}},"parameters":{"page":{"name":"page","in":"query","schema":{"type":"integer","minimum":1}},"per_page":{"name":"per_page","in":"query","schema":{"type":"integer","minimum":1,"maximum":100}},"ulid":{"name":"id","in":"path","required":true,"description":"ULID (26 characters)","schema":{"type":"string"}}},"responses":{"Error":{"description":"{error, message, details}","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"},"details":{"type":"object"}}},"Meta":{"type":"object","properties":{"total":{"type":"integer"},"page":{"type":"integer"},"per_page":{"type":"integer"}}},"Patient":{"type":"object","description":"Demographics and identifiers only; contact fields follow the patient's consent and are absent after erasure.","properties":{"id":{"type":"string"},"file_no":{"type":"string"},"name_ar":{"type":"string"},"name_en":{"type":"string"},"sex":{"type":"string","enum":["male","female"]},"birth_date":{"type":"string","format":"date"},"nationality":{"type":"string"},"national_id_type":{"type":"string","enum":["national_id","iqama","passport","border","gcc"]},"national_id_last4":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"},"branch_id":{"type":"integer"},"payer_id":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"PatientPage":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Patient"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"Appointment":{"type":"object","properties":{"id":{"type":"string"},"patient_id":{"type":"string"},"branch_id":{"type":"integer"},"practitioner_id":{"type":"string"},"appointment_type_id":{"type":"integer"},"room_id":{"type":"integer"},"status":{"type":"string","enum":["booked","confirmed","arrived","in_progress","completed","cancelled","no_show"]},"starts_at":{"type":"string","format":"date-time"},"ends_at":{"type":"string","format":"date-time"},"source":{"type":"string"},"external_ref":{"type":"string"},"note":{"type":"string"},"cancel_reason":{"type":"string"},"cancelled_at":{"type":"string","format":"date-time"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"AppointmentPage":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Appointment"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"AppointmentWrite":{"type":"object","required":["branch_id","appointment_type_id","starts_at"],"properties":{"branch_id":{"type":"integer"},"practitioner_id":{"type":"string"},"appointment_type_id":{"type":"integer"},"starts_at":{"type":"string","format":"date-time"},"patient_id":{"type":"string"},"patient_phone":{"type":"string","description":"E.164; used when patient_id is absent"},"patient_name":{"type":"string","description":"for a new patient file"},"external_ref":{"type":"string","description":"your id; idempotent per API client"},"note":{"type":"string"}}},"InvoiceLine":{"type":"object","properties":{"line_no":{"type":"integer"},"nature":{"type":"string","enum":["medical","cosmetic","product","package","fee"],"description":"billing facts only — no procedure code, description or tooth leaves through the Partner API (PDPL)"},"qty":{"type":"integer"},"unit_halalas":{"type":"integer"},"net_halalas":{"type":"integer"},"tax_category":{"type":"string","enum":["standard","exempt","zero","state_borne"]},"vat_halalas":{"type":"integer"},"total_halalas":{"type":"integer"}}},"Invoice":{"type":"object","properties":{"id":{"type":"string"},"number":{"type":"string"},"kind":{"type":"string","enum":["simplified","standard"]},"status":{"type":"string","enum":["issued","partially_paid","paid","void"]},"patient_id":{"type":"string"},"branch_id":{"type":"integer"},"buyer_kind":{"type":"string","enum":["citizen","resident","company","payer"]},"currency":{"type":"string"},"issued_at":{"type":"string","format":"date-time"},"subtotal_halalas":{"type":"integer"},"discount_halalas":{"type":"integer"},"vat_halalas":{"type":"integer"},"total_halalas":{"type":"integer"},"paid_halalas":{"type":"integer"},"due_halalas":{"type":"integer"},"tax_breakdown":{"type":"array","items":{"type":"object","properties":{"tax_category":{"type":"string"},"net_halalas":{"type":"integer"},"vat_halalas":{"type":"integer"}}}},"lines":{"type":"array","items":{"$ref":"#/components/schemas/InvoiceLine"}},"payments":{"type":"array","items":{"type":"object","properties":{"method":{"type":"string"},"amount_halalas":{"type":"integer"},"scheme":{"type":"string"},"last4":{"type":"string"}}}}}},"InvoicePage":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Invoice"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"Claim":{"type":"object","description":"The data of a claim.updated delivery.","properties":{"id":{"type":"string"},"invoice_id":{"type":"string"},"patient_id":{"type":"string"},"payer_id":{"type":"integer"},"status":{"type":"string"},"previous_status":{"type":"string"},"outcome":{"type":"string","enum":["approved","partial","rejected","pending"]},"claimed_halalas":{"type":"integer"},"approved_halalas":{"type":"integer"},"patient_share_halalas":{"type":"integer"},"nphies_ref":{"type":"string"},"updated_at":{"type":"string","format":"date-time"}}},"WebhookWrite":{"type":"object","properties":{"target_url":{"type":"string","description":"HTTPS on a public address (url is accepted too)"},"url":{"type":"string"},"event":{"type":"string","enum":["appointment.created","appointment.updated","appointment.cancelled","invoice.issued","invoice.paid","claim.updated","patient.erased"]},"events":{"type":"array","description":"several events, or [\"*\"] for all","items":{"type":"string"}},"description":{"type":"string"},"status":{"type":"string","enum":["active","disabled"],"description":"PUT only"}}},"Delivery":{"type":"object","properties":{"id":{"type":"integer"},"subscription_id":{"type":"integer"},"event":{"type":"string"},"event_id":{"type":"string"},"status":{"type":"string","enum":["pending","sent","failed","dead"]},"attempts":{"type":"integer"},"last_http":{"type":"integer"},"last_error_code":{"type":"string"},"duration_ms":{"type":"integer"},"delivered_at":{"type":"string","format":"date-time"},"payload_redacted":{"type":"boolean"}}}}}}