Skip to main content

Scheduling

The Scheduling API lets telemedicine partners offer patient self-booking for video appointments. Public discovery endpoints can be called with a shop_uid. Booking and rebooking operations use either an organisation API key or a short-lived patient booking token.
Never put signed patient booking tokens in URLs. Exchange the token for a booking_launch_code and use that code for hosted booking links or embedded widgets.

Authentication

The Scheduling API accepts two credentials. Most endpoints accept either; some require a specific one. A short-lived JWT minted by the partner backend for a specific patient. Sent in the X-RxScale-Booking-Token header. The token is verified against an organisation booking-token secret provisioned by RxScale. Algorithm: HS256 JOSE header: must include kid — the key_id returned when you provisioned the booking-token secret. Required claims:
ClaimTypeDescription
audstringMust be rxscale-scheduling.
expintExpiration (Unix seconds). Keep this short — under 5 minutes is typical.
nbfintNot-before (Unix seconds).
iatintIssued-at (Unix seconds).
shop_identifierstringThe shop identifier you configured in RxScale.
shop_customer_idstringYour stable customer identifier. RxScale uses it to deduplicate patients across booking sessions.
patient_profile_fieldsarrayPatient profile fields to upsert. Each entry: { "field": "<field_key>", "value": <value>, "source": "shop" }.
import jwt, time
now = int(time.time())
payload = {
    "aud": "rxscale-scheduling",
    "exp": now + 300,
    "nbf": now - 10,
    "iat": now,
    "shop_identifier": "shop_abc",
    "shop_customer_id": "cust_123",
    "patient_profile_fields": [
        {"field": "first_name", "value": "Ada", "source": "shop"},
        {"field": "last_name",  "value": "Lovelace", "source": "shop"},
        {"field": "email",      "value": "ada@example.com", "source": "shop"},
    ],
}
token = jwt.encode(payload, SECRET, algorithm="HS256", headers={"kid": KEY_ID})
Booking-token secrets are stored encrypted at rest in the RxScale database. Only your key_id is stored in plaintext for routing; the secret value is decrypted in-memory both at verification time and whenever an admin reads the value back through the admin portal or the secrets API. Rotate by provisioning a new secret and revoking the old one once your minter has switched over.

Organisation API key

Send X-API-Key: <key> instead of the booking token for server-to-server flows. The key needs the scheduling:write permission. Use this for back-office tooling, scripted rebooking, or operational scripts.

List Appointment Types

GET /v1/scheduling/appointment-types?shop_uid={shop_uid}
shop_uid
string
required
Shop UID used to scope appointment types to the correct organisation.
No API key is required.
curl "https://api.rxscale.com/v1/scheduling/appointment-types?shop_uid=shop_123"
{
  "data": [
    {
      "uid": "apt_video_15",
      "name": "Video consultation",
      "duration_minutes": 15,
      "hold_ttl_seconds": 900,
      "booking_min_notice_minutes": 10,
      "doctor_assignment_mode": "all_available_doctors",
      "allow_patient_rebooking": true,
      "rebooking_mode": "same_doctor_only"
    }
  ]
}

Search Slots

POST /v1/scheduling/slots/search
No API key is required. Slot search only returns slots that satisfy the appointment type’s effective booking notice. If a doctor has a doctor-specific override for that appointment type, the doctor’s value takes precedence, including 0 minutes. Appointment types configured as selected_doctors_only only return doctors with an active doctor-specific setting.
shop_uid
string
required
appointment_type_uid
string
required
from
int
required
Window start (Unix seconds).
to
int
required
Window end (Unix seconds). Max 31 days from from.
doctor_uid
string
Restrict to a single doctor.
curl -X POST "https://api.rxscale.com/v1/scheduling/slots/search" \
  -H "Content-Type: application/json" \
  -d '{
    "shop_uid": "shop_123",
    "appointment_type_uid": "apt_video_15",
    "from": 1778457600,
    "to": 1778544000
  }'
{
  "slots": [
    {
      "doctor_uid": "doc_123",
      "doctor_name": "Dr. Max Meyer",
      "start_date": 1778490000,
      "end_date": 1778490900
    }
  ]
}

Create Booking Session

Use this endpoint when launching the hosted UI or script widget. The request can include the signed patient booking token either in X-RxScale-Booking-Token or in the JSON body as booking_token.
POST /v1/scheduling/booking-sessions
mode
string
required
booking for a new appointment, rebooking for an existing one.
appointment_type_uid
string
Required for booking mode.
from
int
Window start (Unix seconds). Required for booking mode.
to
int
Window end (Unix seconds). Required for booking mode.
return_url
string
URL to redirect the patient to after a successful booking.
booking_token
string
Patient booking JWT, if not sent via header.
curl -X POST "https://api.rxscale.com/v1/scheduling/booking-sessions" \
  -H "Content-Type: application/json" \
  -H "X-RxScale-Booking-Token: eyJ..." \
  -d '{
    "mode": "booking",
    "appointment_type_uid": "apt_video_15",
    "from": 1778457600,
    "to": 1778544000,
    "return_url": "https://partner.example/booking-complete"
  }'
{
  "booking_launch_code": "blc_Nc8...",
  "expires_at": 1778458500,
  "launch_url": "https://meetings.rxscale.com/booking?launch=blc_Nc8..."
}

Get Booking Session

The hosted UI calls this with just the launch code to load the booking context. No authentication header is needed — the launch code itself is the credential.
GET /v1/scheduling/booking-sessions/{booking_launch_code}
curl "https://api.rxscale.com/v1/scheduling/booking-sessions/blc_Nc8..."
{
  "uid": "bsn_abc...",
  "shop_uid": "shop_123",
  "appointment_type_uid": "apt_video_15",
  "appointment_type": {
    "uid": "apt_video_15",
    "name": "Video consultation",
    "duration_minutes": 15
  },
  "start_from": 1778457600,
  "start_to": 1778544000,
  "mode": "booking",
  "return_url": "https://partner.example/booking-complete",
  "expires_at": 1778458500
}
The response intentionally omits patient_profile_uid and other patient identifiers, so the launch code can be safely passed through URLs in the patient’s browser.

Create and Confirm Holds

Authenticated integrations can create holds with an API key that has scheduling:write. Hosted UI integrations use the booking_launch_code routes.
POST /v1/scheduling/booking-sessions/{booking_launch_code}/holds
doctor_uid
string
required
appointment_type_uid
string
required
start_date
int
required
Unix seconds.
Idempotency-Key
string
Optional. Repeating the same key with the same body returns the existing hold instead of creating a duplicate.
curl -X POST "https://api.rxscale.com/v1/scheduling/booking-sessions/blc_Nc8.../holds" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: hold-attempt-1" \
  -d '{
    "doctor_uid": "doc_123",
    "appointment_type_uid": "apt_video_15",
    "start_date": 1778490000
  }'
{
  "hold_uid": "pdm_123",
  "expires_at": 1778490900,
  "ttl_seconds": 900,
  "status": "held"
}
POST /v1/scheduling/booking-sessions/{booking_launch_code}/holds/{hold_uid}/confirm
curl -X POST "https://api.rxscale.com/v1/scheduling/booking-sessions/blc_Nc8.../holds/pdm_123/confirm"
{
  "uid": "pdm_123",
  "status": "confirmed",
  "start_date": 1778490000,
  "end_date": 1778490900
}
If a hold has expired but no other appointment has taken the slot, confirmation is still allowed and the appointment moves to confirmed.

Cancel Appointment

Cancel a confirmed or held appointment. Accepts either a booking token (patient-driven cancel) or an organisation API key (operations).
POST /v1/scheduling/appointments/{appointment_uid}/cancel
reason
string
Optional free-text reason. Stored on the appointment for audit.
curl -X POST "https://api.rxscale.com/v1/scheduling/appointments/pdm_123/cancel" \
  -H "X-RxScale-Booking-Token: eyJ..." \
  -H "Content-Type: application/json" \
  -d '{"reason": "patient_request"}'
{
  "appointment_uid": "pdm_123",
  "status": "cancelled"
}
Patient cancellations honour the appointment type’s cancellation_min_notice_minutes. If you cancel inside the notice window the API returns a 400 with the offending field; rebook the patient or call with an API key for an operational override.

Rebook Appointment

Move an existing appointment to a new slot. The rebook flow allocates a new appointment row (with previous_meeting_uid set to the original) and cancels the old one atomically.
POST /v1/scheduling/appointments/{appointment_uid}/rebook
new_start_date
int
required
Unix seconds for the new slot.
new_doctor_uid
string
Required when the appointment type’s rebooking_mode is any_doctor. For same_doctor_only, the original doctor is reused.
appointment_type_uid
string
Optional. Defaults to the original appointment type.
Idempotency-Key
string
Strongly recommended — protects against double-rebooks during retries.
curl -X POST "https://api.rxscale.com/v1/scheduling/appointments/pdm_123/rebook" \
  -H "X-RxScale-Booking-Token: eyJ..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: rebook-attempt-1" \
  -d '{"new_start_date": 1778510000}'
{
  "appointment_uid": "pdm_456",
  "status": "confirmed"
}
Rebooks are blocked if the appointment type sets allow_patient_rebooking: false, if rebooking_min_notice_minutes has not been met, or if the new slot is taken.

Get Join Token

Returns a short-lived Jitsi waiting-room JWT and the room name. Used by the patient hosted UI to launch the video call.
POST /v1/scheduling/appointments/{appointment_uid}/join
curl -X POST "https://api.rxscale.com/v1/scheduling/appointments/pdm_123/join" \
  -H "X-RxScale-Booking-Token: eyJ..."
{
  "room_name": "doctor-123-room",
  "token": "eyJhbGciOi...",
  "expires_at": 1778497200
}

Join window

The endpoint accepts join requests from 10 minutes before the appointment start_date until 60 minutes after the appointment end_date. Requests outside that window return:
{ "error": ["Appointment is outside the join window"] }
Surface this clearly in your hosted UI — the patient should see a countdown or “join opens in N minutes” hint rather than a blocked button.

Hosted UI

Redirect patients to the launch_url returned by the booking session endpoint, or embed the widget script:
<script
  src="https://meetings.rxscale.com/booking-widget.js"
  data-launch-code="blc_Nc8..."
  data-target="#rxscale-booking"
  data-base-url="https://meetings.rxscale.com">
</script>
The widget renders the hosted booking page in an iframe inside a Shadow DOM wrapper so styles do not leak into the host page.

Doctor Portal Endpoints

These endpoints are scoped to the authenticated doctor (Auth0 token), used by the RxScale Doctor Portal. Partners typically don’t need to call them directly.

List Scheduled Appointments

GET /v1/doctor/appointments
Query parameters mirror the admin list endpoint: status, from, to, patient_uid, page, limit. The response is the doctor’s own appointments only.

Availability Rules CRUD

GET    /v1/doctor/availability-rules
POST   /v1/doctor/availability-rules
PATCH  /v1/doctor/availability-rules/{rule_uid}
DELETE /v1/doctor/availability-rules/{rule_uid}
Each rule has weekday (0 = Monday … 6 = Sunday), start_time and end_time in minutes since midnight, an optional buffer_minutes between consecutive slots, and optional valid_from / valid_until Unix-second bounds. PATCH bodies are partial; pass clear_valid_from: true or clear_valid_until: true to drop a previously-set bound. DELETE is a soft delete.

Admin Endpoints

These endpoints are scoped to the authenticated organisation admin (Auth0 token).

List Appointments

GET /v1/admin/scheduling/appointments
Query parameters: doctor_uid, patient_uid, status, from, to, page, limit. By default returns active (held + confirmed) appointments scheduled from now onwards; pass status=all and from=0 to see history.

Cancel Appointment

POST /v1/admin/scheduling/appointments/{appointment_uid}/cancel
Requires {"reason": "..."} in the body.

Appointment Type Reminders

GET    /v1/admin/scheduling/appointment-types/{appointment_type_uid}/reminders
POST   /v1/admin/scheduling/appointment-types/{appointment_type_uid}/reminders
PATCH  /v1/admin/scheduling/appointment-types/{appointment_type_uid}/reminders/{reminder_uid}
DELETE /v1/admin/scheduling/appointment-types/{appointment_type_uid}/reminders/{reminder_uid}
Each reminder has recipient_role (patient / doctor / admin), minutes_before (positive integer up to 60 days), send_email, send_sms, and active. Multiple reminders per (appointment_type, recipient_role) are allowed as long as the minutes_before offset differs. A maintenance job runs every minute, finds confirmed appointments whose firing window includes “now”, and publishes one scheduling.appointment_reminder_due event per resolved recipient. The published event always fires (partner webhook subscribers receive it); RxScale’s own notification handler only dispatches email/SMS when the corresponding send_email / send_sms flag is set on the reminder row. Partners subscribe to the event via the existing organisation notification subscription endpoint with notification_type=APPOINTMENT_REMINDER_DUE.

View Doctor Availability

GET /v1/admin/scheduling/doctors/{doctor_uid}/availability-rules
Read-only listing of a doctor’s weekly bookable windows. Returns 404 if the doctor is not in the admin’s organisation.

Booking-Token Secrets

GET    /v1/admin/scheduling/booking-token-secrets
GET    /v1/admin/scheduling/booking-token-secrets/{key_id}
POST   /v1/admin/scheduling/booking-token-secrets
DELETE /v1/admin/scheduling/booking-token-secrets/{key_id}
POST provisions a new secret and returns {key_id, secret}. GET returns the secret value decrypted — RxScale stores secrets encrypted at rest, but admins can re-read them any time from this endpoint (and from the admin portal Settings → Booking secrets page) so a new minter can be configured without re-provisioning. DELETE revokes the secret. The legacy organisation-scoped paths (/v1/admin/scheduling/organisations/{organisation_uid}/booking-token-secrets[/<key_id>]) are still accepted and validate against the authenticated organisation.

Errors

The Scheduling API uses standard HTTP status codes:
StatusMeaning
400Schema validation failed, or a business rule blocked the action (see the error body for the field/message).
401Booking token or API key missing or invalid.
404Resource not found, or the caller is not authorised to see it. The API returns 404 for unauthorised access to prevent resource enumeration.
409Slot taken by another patient between hold and confirm. Re-search and try again.
429Rate limit (10 requests per second per service). Back off and retry.