Server-side APIEndpointsQMS (queue)

Pre-book a token

Reserve a queue token on a shift that has not started yet.

POST /v1/qms-appointments/pre/book

Books a numbered token in a queue shift before the queue opens — the patient turns up later and is called in token order. Use it for any shift whose isStarted is still false.

OTP-gated

The patient's phone must have been verified in the last 30 minutes, and this request must carry the same x-end-user-id as 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.

Identifying the patient

As with the scheduled routes: send patientId, or a patientPayload object to find-or-create the record. The inline patient objects are stripped before the booking — the queue books by patientId only.

Body

FieldTypeRequiredDescription
workspaceIdnumberYesYour workspace.
addressIdnumberYesThe clinic location.
doctorIdnumberYesThe practitioner.
patientIdnumberYes**Or a patientPayload to create one.
qmsWorkShiftIdnumberYesThe shift, from doctor shifts.
appointmentDatestringYesYYYY-MM-DD. Must not be in the past.
modestringYesOFFLINE or ONLINE.
bookingChannelstringYesHow the patient reached you: WALK_IN, PHONE_CALL, WHATSAPP, EMAIL, TELEGRAM, INTERNAL_REFERRAL, EXTERNAL_REFERRAL, SELF_QR.
isInternationalbooleanNotrue charges the shift's internationalFee in USD instead of the domestic fee. Defaults to false.
discountAmountnumberNoFlat discount, must not exceed the base fee.
discountReasonstringNoUp to 100 characters.
couponCodestringNoValidated against the clinic's coupons; overrides discountAmount.
typestringNoNEW or FOLLOW_UP.
followUpOfAppointmentIdnumberNoRequired when type is FOLLOW_UP. Must be a COMPLETED visit with the same patient and doctor.
paymentModestringNoCASH, UPI, … or a configured method name.
paymentMethodIdnumberNoA configured payment method, preferred over the string.
paymentStatusstringNoDefaults to PENDING. PAID also persists paymentReference.
paymentReferencestringNoStored only when paymentStatus is PAID.
sourcestringNoDEVELOPER_API from a server integration.
patientPhoneNumberstringNoRecorded on the visit.
patientEmailstringNoRecorded on the visit.
reasonForVisitstringNoUp to 500 characters.
symptomsstringNoUp to 2000 characters.
appointmentNotesstringNoUp to 2000 characters.
referralobjectNoRequired when bookingChannel is a referral channel.
vitalobjectNoIntake vitals, saved in the same transaction.

Try it

POST/v1/qms-appointments/pre/book

Body

Request
POST /v1/qms-appointments/pre/book
{
  "addressId": "1",
  "doctorId": "4",
  "appointmentDate": "2026-10-12",
  "mode": "OFFLINE"
}
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/qms-appointments/pre/book" \
  -H "x-api-key: $MEDOS_API_KEY" \
  -H "x-end-user-id: $PATIENT_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "workspaceId": 1,
    "addressId": 1,
    "doctorId": 4,
    "patientId": 774,
    "qmsWorkShiftId": 331,
    "appointmentDate": "2026-09-20",
    "mode": "OFFLINE",
    "bookingChannel": "PHONE_CALL",
    "paymentMode": "CASH",
    "source": "DEVELOPER_API",
    "reasonForVisit": "Annual check-up"
  }'

Response

201, with the visit wrapped in an envelope:

{
  "success": true,
  "message": "Appointment booked",
  "data": {
    "id": 5541,
    "workspaceId": 1,
    "addressId": 1,
    "doctorId": 4,
    "patientId": 774,
    "qmsWorkShiftId": 331,
    "appointmentDate": "2026-09-20",
    "tokenNumber": 13,
    "tokenDisplay": "P-013",
    "position": 13,
    "status": "SCHEDULED",
    "baseFee": 400,
    "discountAmount": 0,
    "finalAmount": 400,
    "currency": "INR",
    "paymentStatus": "PENDING",
    "publicStatusToken": "qst_8f21c0d4",
    "appointmentTimezone": "Asia/Kolkata"
  }
}
FieldMeaning
tokenDisplayWhat the patient is told — P-013. Show this, not tokenNumber.
positionWhere they currently stand in the queue. It moves as the day goes on.
publicStatusTokenAn opaque handle for a patient-facing "where am I in the queue" view.
finalAmountBase fee minus any discount or coupon, in currency.
idThe visit's numeric id.

A queue booking has no times

There is no fromDateTimeIso to send or read. slotStartDateTime on the response is an estimate the queue maintains, not a commitment.

Common failures

StatusCause
400bookingChannel missing — it is required, and has no default.
400type: "FOLLOW_UP" without followUpOfAppointmentId, or one that is not a completed visit with the same patient and doctor.
400The shift is full — bookedCount has reached maxAppointments.
403appointmentDate is in the past.
403The OTP gate.

Not idempotent on this route

An Idempotency-Key header is not forwarded here, so a retry books a second token. If you need de-duplication, reserve through reserve-slot with a qmsWorkShiftId in the body — that path does honour the header.

On this page