Bookings
Endpoint do tworzenia rezerwacji. Race-safe — atomic UPDATE chroni przed double-booking. Loser dostaje 409 Conflict.
POST /api/v1/bookings
Tworzy rezerwację. Guest (email + phone, bez konta) lub Auth user (JWT Bearer).
Auth: opcjonalny Bearer <jwt>. Bez auth = guest mode.
Request — guest
POST /api/v1/bookings HTTP/1.1
Content-Type: application/json
{
"slot_id": "uuid-...",
"service_id": "uuid-...",
"patient": {
"first_name": "Jan",
"last_name": "Kowalski",
"email": "jan@example.pl",
"phone": "+48501234567",
"pesel": "85010112345"
},
"notes": "Pierwsza wizyta — proszę o wczesny termin"
}
Request — auth user
POST /api/v1/bookings HTTP/1.1
Authorization: Bearer <jwt-token>
Content-Type: application/json
{
"slot_id": "uuid-...",
"service_id": "uuid-...",
"notes": "..."
}
(patient brany z User profile.)
Pola
| Pole | Typ | Wymagane | Opis |
|---|---|---|---|
slot_id | uuid | ✅ | ID slotu Pandoktorka (z /doctors/{slug}/slots) |
service_id | uuid | ✅ | ID services oferowanego dla tego slotu |
patient.first_name | string | guest only | |
patient.last_name | string | guest only | |
patient.email | guest only | Confirmation email | |
patient.phone | string | guest only | SMS reminders |
patient.pesel | string (regex ^\d{11}$ + mod11) | — | Wymagane dla NFZ services |
notes | string (max 500) | — | Notatka dla lekarza |
Response 201
{
"id": "uuid-...",
"status": "pending",
"slot": {
"id": "uuid-...",
"start_at": "2026-05-10T10:00:00+02:00",
"end_at": "2026-05-10T10:30:00+02:00"
},
"doctor": {
"first_name": "Anna",
"last_name": "Kowalczyk",
"slug": "dr-anna-kowalczyk-warszawa"
},
"clinic": {
"name": "MedTrust Warszawa",
"address": "ul. Marszałkowska 1"
},
"service": {
"name": "Konsultacja dermatologiczna",
"price_cents": 25000
},
"cancel_token": "...",
"magic_link_token": "..."
}
magic_link_token (guest only) — link w confirmation email do claim'u
booking'a do założonego konta (DEC-005).
Response 409 — race lost
{"detail": "Slot już zarezerwowany przez innego pacjenta"}
UI pokazuje toast, redirect do listy slotów lekarza.
Response 422 — walidacja
PESEL nie jest poprawny mod11, email zły format, slot/service mismatch
(service_id nie jest w services_allowed slotu), itp.
POST /api/v1/bookings/{id}/cancel
Cancel booking. Wymaga JWT (patient/clinic_admin) lub cancel_token z SMS link.
Request
POST /api/v1/bookings/{id}/cancel HTTP/1.1
Authorization: Bearer <jwt>
Content-Type: application/json
{
"reason": "Pacjent rezygnuje"
}
Response 200
{
"id": "uuid-...",
"status": "cancelled",
"cancelled_at": "2026-05-08T14:30:00+02:00"
}
Slot wraca do status=available. Webhook booking.cancelled emitowany.
POST /api/v1/bookings/cancel-by-token/{token}
Cancel via SMS link (token z cancel_token). Brak auth.
curl -X POST https://api.pandoktorek.pl/api/v1/bookings/cancel-by-token/abc123def
→ frontend strona /anulacja/[token] używa tego endpointu.
GET /api/v1/bookings/me
Lista rezerwacji zalogowanego pacjenta. JWT auth.
Query params
| Param | Default | Opis |
|---|---|---|
status | — | Filter (pending, confirmed, cancelled, completed, no_show) |
from / to | — | Date range |
page / page_size | 1 / 20 | Pagination |
State machine
pending ──confirm──► confirmed ──complete──► completed
│ │
│ └──cancel──► cancelled
│
└──cancel──► cancelled
no-show: confirmed ──missed──► no_show
pending— rezerwacja utworzona, czeka na confirmacje (manualnie albo auto)confirmed— potwierdzona, reminder SMS/email idzie 24h przedcompleted— wizyta odbyta, pacjent może dodać reviewcancelled— anulowana (slot wraca dostępny)no_show— pacjent się nie pojawił (manual flag clinic_admin)
Zmiany statusu emitują webhook eventy: booking.created,
booking.cancelled, booking.completed, booking.no_show.