Server-side APIEndpointsAppointments

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-slot

The 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 sendWhat happens
No returnUrlCash / at-clinic. The booking is created UNPAID and returned. Nothing else.
A returnUrlPay-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:

FieldTypeRequiredDescription
returnUrlstringNoWhere the gateway sends the patient after paying. Its presence is what selects the online path.
qmsWorkShiftIdnumberNoPresent ⇒ this is a queue reservation, not a scheduled one.

Headers

HeaderDescription
Idempotency-KeyStrongly 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

POST/v1/appointments/reserve-slot

Books UNPAID. Send returnUrl to also mint a checkout session; omit it for cash or pay-at-clinic.

Body

Request
POST /v1/appointments/reserve-slot
{
  "addressId": "1",
  "doctorId": "4"
}
to https://api.medos.one
Use 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
}
FieldMeaning
checkoutUrlSend the patient here.
sessionRefThe payment session. This is what confirm-payments resolves by, and it is globally unique.
appointmentRefThe booking's reference. For a queue reservation this is the token display (P-014).
patientIdThe resolved patient.
providerWhich 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

StatusCause
400bookingType: "USE_ACTIVE_PACKAGE" together with a returnUrl. A session drawn from a pack is already paid for — there is nothing to collect. Omit returnUrl.
400The usual unified-booking validation: missing bookingType, missing pack fields, an offset-less timestamp.
400FOLLOW_UP_NOT_ELIGIBLE — followUp: true for a patient with no qualifying visit. Nothing is reserved. See Booking a follow-up.
403The OTP gate.
4xxGATEWAY_NOT_CONFIGURED and other gateway errors, passed through unchanged. The booking survives.

Next

On this page