> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rxscale.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Scheduling

> Patienten können Terminarten anzeigen, freie Slots buchen und Videosprechstunden starten

# Scheduling

Die Scheduling API ermöglicht patientenseitige Selbstbuchung für Videosprechstunden. Öffentliche Discovery-Endpunkte können mit einer `shop_uid` aufgerufen werden. Buchungs- und Umbuchungsvorgänge nutzen entweder einen Organisations-API-Key oder einen kurzlebigen Patient Booking Token.

<Warning>
  Übermitteln Sie signierte Patient Booking Tokens niemals in URLs. Tauschen Sie das Token gegen einen `booking_launch_code` ein und verwenden Sie diesen Code für gehostete Buchungslinks oder eingebettete Widgets.
</Warning>

## Authentifizierung

Die Scheduling API akzeptiert zwei Credentials. Die meisten Endpunkte akzeptieren beides; einige sind auf eines beschränkt.

### Patient Booking Tokens (empfohlen für Patientenflüsse)

Ein kurzlebiges JWT, das Ihr Partner-Backend für einen bestimmten Patienten ausstellt. Übergeben Sie es im Header `X-RxScale-Booking-Token`. Das Token wird gegen ein in RxScale hinterlegtes Organisations-Booking-Token-Secret verifiziert.

**Algorithmus:** `HS256`

**JOSE-Header:** muss `kid` enthalten — die `key_id`, die beim Provisionieren des Secrets zurückgegeben wurde.

**Erforderliche Claims:**

| Claim                    | Typ    | Beschreibung                                                                                                                |
| ------------------------ | ------ | --------------------------------------------------------------------------------------------------------------------------- |
| `aud`                    | string | Muss `rxscale-scheduling` sein.                                                                                             |
| `exp`                    | int    | Ablauf (Unix-Sekunden). Kurz halten — unter 5 Minuten ist üblich.                                                           |
| `nbf`                    | int    | Not-before (Unix-Sekunden).                                                                                                 |
| `iat`                    | int    | Issued-at (Unix-Sekunden).                                                                                                  |
| `shop_identifier`        | string | Der in RxScale konfigurierte Shop-Identifier.                                                                               |
| `shop_customer_id`       | string | Ihre stabile Kunden-ID. RxScale dedupliziert Patienten anhand dieser ID.                                                    |
| `patient_profile_fields` | array  | Profilfelder, die übernommen werden sollen. Jeder Eintrag: `{ "field": "<field_key>", "value": <wert>, "source": "shop" }`. |

```python theme={null}
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})
```

<Note>
  Booking-Token-Secrets werden in der RxScale-Datenbank **verschlüsselt gespeichert**. Nur Ihre `key_id` liegt im Klartext für das Routing vor; der Secret-Wert wird sowohl bei der Token-Verifikation als auch beim Abruf durch einen Admin (Admin-Portal oder Secrets-API) im Speicher entschlüsselt. Rotieren Sie, indem Sie ein neues Secret provisionieren und das alte widerrufen, sobald Ihr Minter umgestellt ist.
</Note>

### Organisations-API-Key

Senden Sie `X-API-Key: <key>` statt des Booking Tokens für Server-zu-Server-Flüsse. Der Key benötigt die Berechtigung `scheduling:write`. Nutzen Sie das für Back-Office-Tools, skriptgesteuertes Umbuchen oder Operations-Skripte.

## Terminarten auflisten

```bash theme={null}
GET /v1/scheduling/appointment-types?shop_uid={shop_uid}
```

<ParamField query="shop_uid" type="string" required>
  Shop-UID, mit der die Terminarten auf die richtige Organisation eingeschränkt werden.
</ParamField>

Kein API-Key erforderlich.

```bash theme={null}
curl "https://api.rxscale.com/v1/scheduling/appointment-types?shop_uid=shop_123"
```

```json theme={null}
{
  "data": [
    {
      "uid": "apt_video_15",
      "name": "Videosprechstunde",
      "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"
    }
  ]
}
```

## Slots suchen

```bash theme={null}
POST /v1/scheduling/slots/search
```

Kein API-Key erforderlich.

Die Slot-Suche liefert nur Slots zurück, die den wirksamen Buchungsvorlauf der
Terminart erfüllen. Wenn ein Arzt für diese Terminart eine eigene Einstellung
hat, geht dieser Wert vor, auch `0` Minuten. Terminarten mit
`selected_doctors_only` liefern nur Ärzte mit aktiver arztspezifischer
Einstellung zurück.

<ParamField body="shop_uid" type="string" required />

<ParamField body="appointment_type_uid" type="string" required />

<ParamField body="from" type="int" required>Fensterstart (Unix-Sekunden).</ParamField>
<ParamField body="to" type="int" required>Fensterende (Unix-Sekunden). Max. 31 Tage ab `from`.</ParamField>
<ParamField body="doctor_uid" type="string">Auf einen Arzt einschränken.</ParamField>
<ParamField body="timezone" type="string">IANA-Zeitzone (z. B. `Europe/Berlin`) zur Lokalisierung der Ausgabe. Standard ist `Europe/Berlin`. Jeder Slot enthält `start_local`/`end_local` in dieser Zone; `start_date`/`end_date` bleiben Unix-Sekunden.</ParamField>

```bash theme={null}
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
  }'
```

```json theme={null}
{
  "slots": [
    {
      "doctor_uid": "doc_123",
      "doctor_name": "Dr. Max Meyer",
      "start_date": 1778490000,
      "end_date": 1778490900,
      "timezone": "Europe/Berlin",
      "start_local": "2026-05-11T11:00:00+02:00",
      "end_local": "2026-05-11T11:15:00+02:00"
    }
  ]
}
```

## Booking Session erstellen

Verwenden Sie diesen Endpunkt beim Start der gehosteten UI oder des Widget-Skripts. Die Anfrage kann das signierte Patient Booking Token entweder in `X-RxScale-Booking-Token` oder im JSON-Body als `booking_token` enthalten.

```bash theme={null}
POST /v1/scheduling/booking-sessions
```

<ParamField body="mode" type="string" required>`booking` für einen neuen Termin, `rebooking` für eine bestehende Buchung.</ParamField>
<ParamField body="appointment_type_uid" type="string">Pflicht im `booking`-Modus.</ParamField>
<ParamField body="from" type="int">Fensterstart (Unix-Sekunden). Pflicht im `booking`-Modus.</ParamField>
<ParamField body="to" type="int">Fensterende (Unix-Sekunden). Pflicht im `booking`-Modus.</ParamField>
<ParamField body="return_url" type="string">URL, zu der der Patient nach erfolgreicher Buchung weitergeleitet wird.</ParamField>
<ParamField body="booking_token" type="string">Patient Booking JWT, falls nicht im Header übergeben.</ParamField>

```bash theme={null}
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"
  }'
```

```json theme={null}
{
  "booking_launch_code": "blc_Nc8...",
  "expires_at": 1778458500,
  "launch_url": "https://meetings.rxscale.com/booking?launch=blc_Nc8..."
}
```

## Booking Session abrufen

Die gehostete UI ruft diesen Endpunkt nur mit dem Launch-Code auf, um den Buchungskontext zu laden. Kein Auth-Header nötig — der Launch-Code selbst ist das Credential.

```bash theme={null}
GET /v1/scheduling/booking-sessions/{booking_launch_code}
```

```bash theme={null}
curl "https://api.rxscale.com/v1/scheduling/booking-sessions/blc_Nc8..."
```

```json theme={null}
{
  "uid": "bsn_abc...",
  "shop_uid": "shop_123",
  "appointment_type_uid": "apt_video_15",
  "appointment_type": {
    "uid": "apt_video_15",
    "name": "Videosprechstunde",
    "duration_minutes": 15
  },
  "start_from": 1778457600,
  "start_to": 1778544000,
  "mode": "booking",
  "return_url": "https://partner.example/booking-complete",
  "expires_at": 1778458500
}
```

Die Antwort enthält bewusst kein `patient_profile_uid` und keine weiteren Patienten-Identifier, damit der Launch-Code gefahrlos durch die URL im Browser des Patienten geführt werden kann.

## Holds erstellen und bestätigen

Authentifizierte Integrationen können Holds mit einem API-Key mit `scheduling:write` anlegen. Gehostete-UI-Integrationen nutzen die `booking_launch_code`-Routen.

```bash theme={null}
POST /v1/scheduling/booking-sessions/{booking_launch_code}/holds
```

<ParamField body="doctor_uid" type="string" required />

<ParamField body="appointment_type_uid" type="string" required />

<ParamField body="start_date" type="int" required>Unix-Sekunden.</ParamField>
<ParamField body="visit_reason" type="string">Optionaler Grund für den Termin, bis zu 2000 Zeichen. Leere Werte werden als `null` gespeichert.</ParamField>
<ParamField header="Idempotency-Key" type="string">Optional. Derselbe Key mit identischem Body liefert den bestehenden Hold zurück, statt einen neuen anzulegen.</ParamField>

```bash theme={null}
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,
    "visit_reason": "Medikationsreview vor Dosisanpassung"
  }'
```

```json theme={null}
{
  "hold_uid": "pdm_123",
  "expires_at": 1778490900,
  "ttl_seconds": 900,
  "status": "held",
  "visit_reason": "Medikationsreview vor Dosisanpassung"
}
```

```bash theme={null}
POST /v1/scheduling/booking-sessions/{booking_launch_code}/holds/{hold_uid}/confirm
```

```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/scheduling/booking-sessions/blc_Nc8.../holds/pdm_123/confirm"
```

```json theme={null}
{
  "uid": "pdm_123",
  "status": "confirmed",
  "start_date": 1778490000,
  "end_date": 1778490900,
  "visit_reason": "Medikationsreview vor Dosisanpassung"
}
```

<Note>
  Wenn ein Hold abgelaufen ist, der Slot aber noch nicht durch einen anderen Termin belegt wurde, ist die Bestätigung weiterhin möglich und der Termin wechselt in den Status `confirmed`.
</Note>

<Note>
  Wenn `visit_reason` gesetzt ist, ist der Grund für Ärzte/Admins sichtbar und kann in Termin-Erinnerungen per E-Mail/SMS sowie in synchronisierten Kalendereinladungen erscheinen.
</Note>

## Termin stornieren

Storniert einen bestätigten oder gehaltenen Termin. Akzeptiert entweder ein Booking Token (patientenseitige Stornierung) oder einen Organisations-API-Key (Operations).

```bash theme={null}
POST /v1/scheduling/appointments/{appointment_uid}/cancel
```

<ParamField body="reason" type="string">Optionaler Freitext-Grund. Wird am Termin für Audit-Zwecke gespeichert.</ParamField>

```bash theme={null}
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"}'
```

```json theme={null}
{
  "appointment_uid": "pdm_123",
  "status": "cancelled"
}
```

Patientenseitige Stornierungen respektieren `cancellation_min_notice_minutes` der Terminart. Eine Stornierung innerhalb der Frist gibt `400` mit dem betroffenen Feld zurück; buchen Sie den Patienten um oder rufen Sie den Endpunkt mit einem API-Key auf, wenn ein operativer Override nötig ist.

## Termin umbuchen

Verschiebt einen bestehenden Termin auf einen neuen Slot. Der Rebook-Flow legt atomar einen neuen Termin an (mit `previous_meeting_uid` auf den Originaltermin) und storniert den alten.

```bash theme={null}
POST /v1/scheduling/appointments/{appointment_uid}/rebook
```

<ParamField body="new_start_date" type="int" required>Unix-Sekunden für den neuen Slot.</ParamField>
<ParamField body="new_doctor_uid" type="string">Pflicht, wenn die Terminart `rebooking_mode: any_doctor` setzt. Bei `same_doctor_only` wird der ursprüngliche Arzt übernommen.</ParamField>
<ParamField body="appointment_type_uid" type="string">Optional. Standard ist die ursprüngliche Terminart.</ParamField>
<ParamField header="Idempotency-Key" type="string">Dringend empfohlen — schützt vor doppelten Umbuchungen bei Retries.</ParamField>

```bash theme={null}
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}'
```

```json theme={null}
{
  "appointment_uid": "pdm_456",
  "status": "confirmed"
}
```

Umbuchungen werden blockiert, wenn die Terminart `allow_patient_rebooking: false` setzt, `rebooking_min_notice_minutes` nicht eingehalten ist oder der neue Slot bereits belegt ist.

## Join-Token abrufen

Liefert ein kurzlebiges Jitsi-Waiting-Room-JWT und den Raumnamen. Wird von der Patient-UI verwendet, um den Videocall zu starten.

```bash theme={null}
POST /v1/scheduling/appointments/{appointment_uid}/join
```

```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/scheduling/appointments/pdm_123/join" \
  -H "X-RxScale-Booking-Token: eyJ..."
```

```json theme={null}
{
  "room_name": "doctor-123-room",
  "token": "eyJhbGciOi...",
  "expires_at": 1778497200
}
```

### Join-Fenster

Der Endpunkt akzeptiert Join-Anfragen ab **10 Minuten vor** `start_date` des Termins bis **60 Minuten nach** `end_date`. Anfragen außerhalb dieses Fensters liefern:

```json theme={null}
{ "error": ["Appointment is outside the join window"] }
```

Stellen Sie das in Ihrer UI klar dar — der Patient sollte einen Countdown oder einen "Join öffnet in N Minuten"-Hinweis sehen statt eines blockierten Buttons.

## Gehostete UI

Leiten Sie den Patienten auf die `launch_url` aus der Booking-Session-Antwort weiter oder binden Sie das Widget-Skript ein:

```html theme={null}
<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>
```

Das Widget rendert die gehostete Buchungsseite in einem iframe innerhalb eines Shadow-DOM-Wrappers, damit Styles nicht in die Host-Seite überschwappen.

## Arztportal-Endpunkte

Diese Endpunkte sind auf den authentifizierten Arzt (Auth0-Token) eingeschränkt und werden vom
RxScale-Arztportal genutzt. Partner müssen sie in der Regel nicht direkt aufrufen.

### Geplante Termine auflisten

```bash theme={null}
GET /v1/doctor/appointments
```

Query-Parameter wie beim Admin-Listing: `status`, `from`, `to`, `patient_uid`, `page`, `limit`.
Die Antwort enthält ausschließlich Termine des authentifizierten Arztes.

### Verfügbarkeits-CRUD

```bash theme={null}
GET    /v1/doctor/availability-rules
POST   /v1/doctor/availability-rules
PATCH  /v1/doctor/availability-rules/{rule_uid}
DELETE /v1/doctor/availability-rules/{rule_uid}
```

Jede Regel hat `weekday` (0 = Montag … 6 = Sonntag), `start_time` und `end_time` in Minuten seit
Mitternacht, optional `buffer_minutes` zwischen Slots sowie optionale Gültigkeitsgrenzen
`valid_from` / `valid_until` (Unix-Sekunden). PATCH-Bodies sind partiell; mit
`clear_valid_from: true` oder `clear_valid_until: true` lassen sich gesetzte Grenzen entfernen.
DELETE ist ein Soft-Delete.

## Admin-Endpunkte

Auf die authentifizierte Admin-Organisation (Auth0-Token) eingeschränkt.

### Termine auflisten

```bash theme={null}
GET /v1/admin/scheduling/appointments
```

Query: `doctor_uid`, `patient_uid`, `status`, `from`, `to`, `page`, `limit`. Standard:
aktive (gehalten + bestätigt) Termine ab jetzt; mit `status=all` und `from=0` lässt sich die
Historie einsehen.

### Termin stornieren

```bash theme={null}
POST /v1/admin/scheduling/appointments/{appointment_uid}/cancel
```

Body `{"reason": "..."}` erforderlich.

### Termin­art-Erinnerungen

```bash theme={null}
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}
```

Jede Erinnerung hat `recipient_role` (`patient` / `doctor` / `admin`), `minutes_before` (positive
ganze Zahl bis 60 Tage), `send_email`, `send_sms` und `active`. Mehrere Erinnerungen pro
`(appointment_type, recipient_role)` sind erlaubt, solange sich der `minutes_before`-Offset
unterscheidet.

Ein Maintenance-Job läuft jede Minute, findet bestätigte Termine, deren Auslösefenster "jetzt"
umfasst, und publiziert pro auflösbarem Empfänger ein `scheduling.appointment_reminder_due`-Event.
Das Event feuert immer (Partner-Webhook-Subscriber erhalten es); der RxScale-eigene
Notification-Handler verschickt E-Mail/SMS nur, wenn die jeweiligen Flags `send_email` /
`send_sms` auf der Erinnerungszeile gesetzt sind.

Wenn der Termin einen `visit_reason` hat, enthalten das Reminder-Event und die RxScale-E-Mail/SMS-Inhalte diesen Grund.

Partner abonnieren das Event über den bestehenden Endpunkt für Organisations-
Benachrichtigungs-Abonnements mit `notification_type=APPOINTMENT_REMINDER_DUE`.

### Verfügbarkeiten eines Arztes ansehen

```bash theme={null}
GET /v1/admin/scheduling/doctors/{doctor_uid}/availability-rules
```

Lesende Auflistung der wöchentlichen Buchungsfenster eines Arztes. Gibt `404` zurück, wenn der
Arzt nicht in der Organisation des Admins ist.

### Booking-Token-Secrets

```bash theme={null}
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` legt ein neues Secret an und gibt `{key_id, secret}` zurück. `GET` liefert den Wert
entschlüsselt zurück — Secrets liegen verschlüsselt in der Datenbank, sind aber von Admins
jederzeit über die API und das Admin-Portal (Einstellungen → Buchungs-Secrets) wieder lesbar,
damit ein neuer Minter ohne Neu-Provisionierung konfiguriert werden kann. `DELETE` widerruft
das Secret.

Die legacy organisations-gebundenen Pfade
(`/v1/admin/scheduling/organisations/{organisation_uid}/booking-token-secrets[/<key_id>]`)
werden weiterhin akzeptiert und gegen die authentifizierte Organisation validiert.

## Fehler

Die Scheduling API nutzt Standard-HTTP-Statuscodes:

| Status | Bedeutung                                                                                                                                                                   |
| ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Schema-Validierung fehlgeschlagen oder eine Geschäftsregel hat die Aktion blockiert (Details im `error`-Body).                                                              |
| `401`  | Booking Token oder API-Key fehlt oder ist ungültig.                                                                                                                         |
| `404`  | Ressource nicht gefunden **oder** der Aufrufer ist nicht berechtigt, sie zu sehen. Bei unberechtigtem Zugriff liefert die API `404`, um Resource-Enumeration zu verhindern. |
| `409`  | Slot wurde zwischen Hold und Confirm anderweitig belegt. Neu suchen und erneut versuchen.                                                                                   |
| `429`  | Rate Limit (10 Anfragen pro Sekunde pro Service). Backoff und retry.                                                                                                        |
