Available slots
The bookable slots for one doctor, at one location, on one date.
GET /v1/appointments/available-slotsThe starting point of every scheduled booking. It returns the grid a picker renders: the slots that can be booked, the ones that cannot (so you can grey them out instead of leaving unexplained gaps), and the plans the visit can be paid for with.
No OTP is needed to read slots — only to book one.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
addressId | number | Yes | The clinic location. From addresses[].id on workspace detail. |
doctorId | number | Yes | The practitioner. From addresses[].doctors[].id. |
appointmentDate | string | Yes | YYYY-MM-DD, in the clinic's own timezone. |
mode | string | No | OFFLINE, ONLINE, HOME_VISIT, HOME_COLLECTION or WALK_IN. Omit to get every mode the doctor offers. |
durationTierId | number | No | Generate the grid for one consultation-length tier. See availableDurations in the response. |
durationMins | number | No | The same choice expressed as minutes, when you do not hold the tier id. |
patientId | number | No | Narrows plans to what this patient can actually use — their active packs and follow-up entitlement. |
patientCountryCode | string | No | Dial code, ISO2 or country name. Decides domestic vs international pricing on plans. |
patientPhoneNumber | string | No | Same purpose as patientId when you only hold a phone. |
followUp | boolean | No | Price the visit as a follow-up of the patient's last consultation. To book it as one, send followUp: true on the booking too. |
patientTimezone | string | No | IANA zone (Asia/Kolkata) used for the patient* fields below. |
workspaceId is taken from your API key. Sending one changes nothing.
Try it
/v1/appointments/available-slotsNo OTP is needed to read slots — only to book one.
Query
GET /v1/appointments/available-slots?addressId=1&doctorId=4&appointmentDate=2026-10-12&mode=OFFLINEhttps://api.medos.oneUse a dedicated test key, and put your browser's address on its allowlist
A Developer API key authenticates on its own, so it only works from the addresses registered against it — and this page calls from your browser, not your servers. Unless your own public address is on the list you get a 403 naming it, which is the allowlist doing its job. Your browser may also reach us over IPv6 even when your server does not, so the address in the error is often not the one you expected. The key here is kept in memory only and never written to storage, but create a test key for it and deactivate that key when you are done.
Request
curl -sG "https://api.medos.one/v1/appointments/available-slots" \
-H "x-api-key: $MEDOS_API_KEY" \
--data-urlencode "addressId=1" \
--data-urlencode "doctorId=4" \
--data-urlencode "appointmentDate=2026-09-20" \
--data-urlencode "mode=OFFLINE"Response
{
"slots": [
{
"appointmentDate": "2026-09-20",
"fromDateTimeTs": "09:00",
"toDateTimeTs": "09:30",
"fromDateTimeIso": "2026-09-20T09:00:00+05:30",
"toDateTimeIso": "2026-09-20T09:30:00+05:30",
"appointmentTimezone": "Asia/Kolkata",
"patientFromDateTimeIso": "2026-09-20T05:30:00+02:00",
"patientToDateTimeIso": "2026-09-20T06:00:00+02:00",
"patientTimezone": "Europe/Berlin"
}
],
"unavailableSlots": [
{
"appointmentDate": "2026-09-20",
"fromDateTimeIso": "2026-09-20T10:00:00+05:30",
"toDateTimeIso": "2026-09-20T10:30:00+05:30",
"status": "BOOKED"
}
],
"plans": [],
"activeSessionPackResponses": [],
"allSessionPackResponses": [],
"nextAvailableDate": "2026-09-21",
"selectedDurationTierId": null,
"availableDurations": [15, 30],
"patientTimezone": "Europe/Berlin",
"patientTimezoneSource": "IP"
}| Field | Meaning |
|---|---|
slots[] | Bookable. Send fromDateTimeIso / toDateTimeIso back verbatim when you book. |
unavailableSlots[] | Same grid, unbookable. status is BOOKED (a Medos appointment holds it) or BLOCKED (external busy time from the practitioner's calendar). Deliberately coarse — it never names whose. |
plans[] | How this visit can be paid for: GENERAL_CONSULTATION, SESSION_PACK (buyable) or ACTIVE_PACKAGE (already owned). Each carries pricePerSession, currency, and for packs sessionsRemaining and expiresOn. |
nextAvailableDate | The next date with any capacity — use it to skip an empty day rather than probing forward. |
availableDurations[] | Distinct consultation lengths this doctor offers. Drive a duration picker from this, not from plans. |
selectedDurationTierId | Which tier the returned grid was generated for. |
Two renderings of the same instant
Every slot comes back twice. fromDateTimeIso is the clinic's own time; the
patient* fields are the same moment rendered in the viewer's zone, resolved
in this order:
| Source | patientTimezoneSource |
|---|---|
?patientTimezone= | PARAM |
| A CloudFront viewer header | HEADER |
| GeoIP on your server's address | IP |
| The clinic's own zone | CLINIC |
CLINIC is not an error — it just makes both renderings identical, keeping the
response shape stable. An unparseable patientTimezone falls through instead of
failing the request, and a slot with no usable timestamp is passed through
without the patient* fields at all.
Book with the clinic time, never the patient time
patientFromDateTimeIso is for display. Sending it back on a booking
double-converts the slot and books the wrong hour. Booking takes
fromDateTimeIso.
Server integrations usually want PARAM
Left alone, the zone is guessed from your server's IP — which is where your
backend runs, not where the patient is. If you know the patient's zone, send
patientTimezone; if you do not, ignore the patient* fields entirely.
Common failures
| Status | Cause |
|---|---|
400 | appointmentDate is not YYYY-MM-DD, or mode is not one of the five values. |
403 | Your source address is not on the key's allowlist — see IP allowlist. |
404 | doctorId is not mapped to addressId. A doctor belongs to specific locations. |
An empty slots array is a successful response, not an error: the doctor has no
capacity that day. Use nextAvailableDate to move on.