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.
Downloads
All three are generated from the same OpenAPI file as this page, so they always match the live API. No sign-in is needed.
Import it into Postman, set the {{token}} variable to your client token and run any request. «Subscribe» stores {{webhook_id}} and {{webhook_secret}}; the «Webhook signature» folder signs a sample delivery and sends it to {{receiver_url}}, so you can test your receiver before a real event happens.
Authentication
The account owner creates an API client under Office → Developers and chooses its abilities. The token is shown once. Send it on every call:
The token can also be sent as X-Api-Key. GET /ping answers the client, its abilities and the clinic: use it as the «test connection» call.
Abilities
Each endpoint needs one ability; a call without it answers 403 insufficient_ability with details.required_ability. webhooks only manages subscriptions: to receive an event, or list its recent objects, the client also needs the ability that reads that data.
Ability
Endpoints
any
GET /ping · GET /meta
patients:read
GET /patients · GET /patients/{id}
appointments:read
GET /appointments · GET /appointments/{id}
appointments:write
POST /appointments · POST /appointments/{id}/cancel
invoices:read
GET /invoices · GET /invoices/{id}
webhooks
GET /webhooks · POST /webhooks · GET /webhooks/events · GET /webhooks/events/{event}/recent · GET /webhooks/{id} · PUT /webhooks/{id} · DELETE /webhooks/{id} · POST /webhooks/{id}/rotate · POST /webhooks/{id}/test
Rate limit
120 requests a minute per API client. A 429 answer carries Retry-After; every answer carries X-RateLimit-Limit and X-RateLimit-Remaining.
Calls whose token matches no active client share a per-address limit of 30 a minute. A plan without the Partner API feature answers 403 not_entitled.
Errors
Every error is JSON {error, message, details}; the message follows Accept-Language (ar or en). Money is always integer halalas (1 SAR = 100).
Missing API token. Send it as "Authorization: Bearer bmc_…".
token_invalid
401
This API token is not valid or was revoked.
tenant_inactive
403
This clinic's account is suspended or closed.
not_entitled
403
The account's plan does not include the Partner API.
insufficient_ability
403
This API client does not have the ability this call needs.
forbidden
403
Only the account owner can manage API clients and webhooks.
not_found
404
Not found.
client_revoked
409
This API client was revoked.
webhook_disabled
409
This webhook is switched off.
delivery_sent
409
This delivery already succeeded.
payload_redacted
409
This delivery's body was removed (retention or a patient's erasure); it cannot be sent again.
validation_failed
422
Some fields are not valid.
event_unknown
422
Unknown webhook event.
url_not_allowed
422
This URL cannot receive webhooks (HTTPS on a public address only).
webhook_limit
422
Too many webhook subscriptions.
rate_limited
429
Too many requests. Try again shortly.
not_implemented
501
This endpoint is documented but not available yet; it answers once its package ships.
planned
Endpoints marked «planned» are fully documented here and their abilities are enforced, but they answer 501 not_implemented until their package ships (patients, appointments, invoices). Build against this file; the shapes are fixed.
Pagination
Lists answer {data, meta: {total, page, per_page}}. Use ?page= and ?per_page= (default 50, at most 100).
POST /appointments is idempotent on external_ref per API client: sending the same booking again answers 200 with the same appointment, never a second one. Cancelling an appointment twice is a no-op. On a timeout, read before you retry. Webhook deliveries may arrive more than once: drop repeats by X-Basmtak-Event-Id (the body's id).
Webhooks (REST hooks)
Subscribe with POST /webhooks {target_url, event} and unsubscribe with DELETE /webhooks/{id} — the Zapier and Make REST-hook pattern. Each delivery is a POST of {id, event, tenant_id, occurred_at, api_version, data}, retried with exponential backoff; 20 failures in a row (or a 410 answer) switch the subscription off. Deliveries never carry diagnoses, clinical notes or photos.
What a delivery looks like
A POST with Content-Type application/json and the body {id, event, tenant_id, occurred_at, api_version, data}; data has the same shape the API returns for that object. Headers: X-Basmtak-Event, X-Basmtak-Event-Id, X-Basmtak-Delivery, X-Basmtak-Timestamp and X-Basmtak-Signature.
Verifying a delivery
X-Basmtak-Signature = "sha256=" + hex HMAC-SHA256(secret, X-Basmtak-Timestamp + "\n" + "POST" + "\n" + path + "\n" + hex SHA-256(raw body)). Reject timestamps older than five minutes. X-Basmtak-Event-Id is unique per event — use it to drop duplicates.
Worked example
With these inputs your code must produce exactly this signature (computed live by the code that signs real deliveries). Use the secret exactly as you received it, including whsec_, and the raw body bytes, before any JSON parsing.
Any 2xx answer within 10 seconds is a success. Anything else (another status, a timeout, a refused address) is retried after 60 seconds, doubling each time up to 6 hours; after 10 attempts the delivery is given up. 20 failed attempts in a row switch the subscription off, and a 410 Gone answer switches it off at once (the REST-hook way to unsubscribe). Only the status code is kept, never your answer's body.
Events
appointment.createdAbility to receive: appointments:read
The hook URL must be HTTPS on a public address (hooks.zapier.com and hook.*.make.com are). A Zap or scenario that only needs actions (book or cancel an appointment) or searches (a patient by file number or phone, an invoice) calls the endpoints below directly.
Endpoints
meta Connectivity and what this server offers
GET/pingCheck the token and see the client's abilitiesAbility: any
Responses
200
The client, its abilities and the clinic
401
{error, message, details}
Error
GET/metaAbilities, the event catalogue, rate limits and the spec versionAbility: any
POST/appointmentsBook an appointmentAbility: appointments:write
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).
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).
GET/webhooks/eventsThe events, each with the ability it needs and sample dataAbility: webhooks
Responses
200
{data: [{event, ability, sample}]}
GET/webhooks/events/{event}/recentRecent real objects of one event (REST hook perform list)Ability: webhooks
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.