Reserve a slot
Hold a slot unpaid, and — when the patient is paying online — mint the checkout in the same call.
POST /v1/appointments/reserve-slotThe entry point for online payment, and the only booking route that is safe to
retry. It creates the booking unpaid — the unpaid booking is the
reservation, and Medos's sweeper reclaims it if nobody pays — then, if you sent
a returnUrl, opens a hosted checkout session and merges it into the response.
It handles both booking kinds. A payload carrying qmsWorkShiftId reserves a
queue token; anything else reserves a scheduled slot.
OTP-gated
Verify the patient's phone within the last 30 minutes, and send the same
x-end-user-id on this request as on that verification. See OTP-gated routes. Calling with an x-api-key instead of a session token? The gate does not
apply — see Booking without OTP.
Two shapes, one route
| You send | What happens |
|---|---|
No returnUrl | Cash / at-clinic. The booking is created UNPAID and returned. Nothing else. |
A returnUrl | Pay-online. The booking is created, a checkout session is minted, and the response carries both. |
Body
Every field from book-appointment-unified applies, including patient find-or-create. Two additions:
| Field | Type | Required | Description |
|---|---|---|---|
returnUrl | string | No | Where the gateway sends the patient after paying. Its presence is what selects the online path. |
qmsWorkShiftId | number | No | Present ⇒ this is a queue reservation, not a scheduled one. |
Headers
| Header | Description |
|---|---|
Idempotency-Key | Strongly recommended. De-duplicates a double submit, and is passed to the payment rail so a retry returns the same checkout rather than a second one. Generated for you if absent — which does nothing for your retries. |
Try it
/v1/appointments/reserve-slotBooks UNPAID. Send returnUrl to also mint a checkout session; omit it for cash or pay-at-clinic.
Body
POST /v1/appointments/reserve-slot{
"addressId": "1",
"doctorId": "4"
}https://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 -sX POST "https://api.medos.one/v1/appointments/reserve-slot" \
-H "x-api-key: $MEDOS_API_KEY" \
-H "x-end-user-id: $PATIENT_ID" \
-H "Idempotency-Key: 7f1c2b8e-1f3a-4a1e-9c22-6d1d3f9a5f01" \
-H "Content-Type: application/json" \
-d '{
"workspaceId": 1,
"addressId": 1,
"doctorId": 4,
"patientId": 774,
"bookingType": "ONE_TIME_APPOINTMENT",
"mode": "OFFLINE",
"fromDateTimeIso": "2026-09-20T09:00:00+05:30",
"toDateTimeIso": "2026-09-20T09:30:00+05:30",
"consultationCharge": 500,
"currency": "INR",
"paymentMode": "ONLINE",
"source": "DEVELOPER_API",
"returnUrl": "https://clinic.example.com/booking/return"
}'Response
The booking payload, with the checkout fields merged on top:
{
"bookingReference": "MED-9182-AK",
"appointmentDate": "2026-09-20",
"fromDateTimeTs": "2026-09-20T09:00:00+05:30",
"toDateTimeTs": "2026-09-20T09:30:00+05:30",
"paymentStatus": "PENDING",
"consultationCharge": 500,
"currency": "INR",
"checkoutUrl": "https://checkout.example-gateway.com/s/abc123",
"provider": "RAZORPAY",
"sessionRef": "ps_01J9Z6K2QF",
"appointmentRef": "MED-9182-AK",
"patientId": 774
}| Field | Meaning |
|---|---|
checkoutUrl | Send the patient here. |
sessionRef | The payment session. This is what confirm-payments resolves by, and it is globally unique. |
appointmentRef | The booking's reference. For a queue reservation this is the token display (P-014). |
patientId | The resolved patient. |
provider | Which gateway the clinic has configured. |
A cash reservation returns the booking alone — no checkoutUrl, no
sessionRef.
Store sessionRef, appointmentRef and patientId before you redirect
Once the patient leaves for the gateway, the only state you keep is what you wrote down. A client that lost its in-memory state to the return redirect has nothing to confirm with. Persist all three against your own order record.
If minting the checkout fails
The booking that was already created is kept, and only the upstream error is
re-thrown — commonly GATEWAY_NOT_CONFIGURED, when the clinic admin has not
enabled a payment gateway.
That is a real unpaid booking the patient can settle at the clinic. Tell them online payment is unavailable; do not re-book on retry, or they will hold two slots.
Common failures
| Status | Cause |
|---|---|
400 | bookingType: "USE_ACTIVE_PACKAGE" together with a returnUrl. A session drawn from a pack is already paid for — there is nothing to collect. Omit returnUrl. |
400 | The usual unified-booking validation: missing bookingType, missing pack fields, an offset-less timestamp. |
400 | FOLLOW_UP_NOT_ELIGIBLE — followUp: true for a patient with no qualifying visit. Nothing is reserved. See Booking a follow-up. |
403 | The OTP gate. |
4xx | GATEWAY_NOT_CONFIGURED and other gateway errors, passed through unchanged. The booking survives. |