Buy a pack
Sell a session pack on its own, with no appointment attached.
POST /v1/session/packsA standalone pack purchase: the patient buys the block of sessions now and books visits against it later. It mints a hosted checkout and returns the URL — the pack itself is created only when the payment is confirmed, so an abandoned checkout buys nothing.
If the patient wants to buy a pack and take their first session in one go, use
book-appointment-unified
with bookingType: "PACKAGE_PURCHASE" instead.
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.
Body
Patient identification works as on the booking routes: patientId, or a
patientPayload object to find-or-create the record.
| Field | Type | Required | Description |
|---|---|---|---|
packageConfigId | number | Yes | The pack, from the catalog. |
packageAmount | number | Yes | What the patient pays. Use the catalog's discountedPrice. |
currency | string | Yes | ISO 4217. Match the pricing you quoted. |
doctorId | number | Yes | The practitioner the pack is bought against. |
addressId | number | Yes | The clinic location. workspaceAddressId is accepted as an alias. |
returnUrl | string | Yes | Where the gateway sends the patient afterwards. |
patientId | number | Yes* | *Or a patientPayload. |
Nothing else in the body is forwarded — this route sends a fixed set of fields upstream, so extra keys are ignored rather than rejected.
Headers
| Header | Description |
|---|---|
Idempotency-Key | Recommended. De-duplicates a double submit so a retry returns the same checkout instead of a second one. Generated for you when absent, which does nothing for your own retries. |
Try it
/v1/session/packsThe pack is minted PAID on verify, not here — a purchase that is never confirmed creates nothing.
Body
POST /v1/session/packs{
"currency": "INR",
"doctorId": "4",
"addressId": "1"
}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/session/packs" \
-H "x-api-key: $MEDOS_API_KEY" \
-H "x-end-user-id: $PATIENT_ID" \
-H "Idempotency-Key: 4c0f9e2a-0b77-4a51-9d3f-2b6f0a91c774" \
-H "Content-Type: application/json" \
-d '{
"patientId": 774,
"packageConfigId": 12,
"packageAmount": 4500,
"currency": "INR",
"doctorId": 4,
"addressId": 1,
"returnUrl": "https://clinic.example.com/packs/return"
}'Response
{
"checkoutUrl": "https://checkout.example-gateway.com/s/pk_9931",
"provider": "RAZORPAY",
"sessionRef": "pks_01J9ZB4M7T"
}| Field | Meaning |
|---|---|
checkoutUrl | Send the patient here. |
sessionRef | The only handle that settles this purchase. |
provider | The gateway the clinic has configured. |
Persist sessionRef before redirecting
There is no reference to fall back on here — a pack purchase has no booking
and no appointment ref, so sessionRef is the whole story. Store it against
your own order record before the patient leaves the page.
Common failures
| Status | Cause |
|---|---|
400 | packageConfigId is not a pack in this workspace, or is no longer isActive. |
400 | packageAmount does not match the pack's configured price. |
403 | The OTP gate. |
4xx | GATEWAY_NOT_CONFIGURED — the clinic admin has not enabled online payment. Passed through unchanged. Nothing was created. |
Next
Confirm a pack payment
Settle the purchase when the patient returns.