Slots
Push slotów dostępnych dla rezerwacji. Sloty są race-safe — atomic UPDATE
chroni przed double-booking. Re-push slotu status=booked nie odbookowuje
rezerwacji (defense G-ERR-003).
POST /api/v1/clinic/slots
Bulk upsert slotów. INSERT ... ON CONFLICT (clinic_id, external_id) —
status='booked' pomija update.
Auth: Bearer pk_live_* z scope slots:write.
Request
POST /api/v1/clinic/slots HTTP/1.1
Authorization: Bearer pk_live_XXXXXXXX_<secret>
Content-Type: application/json
{
"slots": [ /* SlotIngestItem, max 2000 */ ]
}
SlotIngestItem
| Pole | Typ | Wymagane | Opis |
|---|---|---|---|
external_id | string (1-120) | ✅ | Twój ID z CRM (np. slot-2026-05-10-1000) |
doctor_external_id | string (1-120) | ✅ | external_id lekarza (musi istnieć w mapping) |
start_at | datetime (ISO8601) | ✅ | Z timezone (+02:00 Warszawa) |
end_at | datetime (ISO8601) | ✅ | Z timezone, > start_at |
services_allowed | string[] | — | Lista external_id services. Pomiń = wszystkie active services |
Response 200
{
"created": 1500,
"updated": 488,
"skipped_booked": 12,
"errors": []
}
skipped_booked — sloty które ON CONFLICT natrafiły na status=booked i
zostały pominięte (defense). Twój system powinien pull'ować bookings i
synchronizować z MyDr — booking.created webhook robi to automatycznie.
Pełen przykład
curl -X POST https://api.pandoktorek.pl/api/v1/clinic/slots \
-H "Authorization: Bearer pk_live_XXXXXXXX_<secret>" \
-H "Content-Type: application/json" \
-d '{
"slots": [
{
"external_id": "slot-2026-05-10-1000-anna",
"doctor_external_id": "mydr-12345",
"start_at": "2026-05-10T10:00:00+02:00",
"end_at": "2026-05-10T10:30:00+02:00",
"services_allowed": ["svc-dermatologia-30"]
},
{
"external_id": "slot-2026-05-10-1030-anna",
"doctor_external_id": "mydr-12345",
"start_at": "2026-05-10T10:30:00+02:00",
"end_at": "2026-05-10T11:00:00+02:00"
}
]
}'
Bulk strategy
- Pełen sync co 5-15 min: cały grafik na 30 dni → bulk push (idempotent)
- Delta sync co 1 min: tylko zmienione sloty od ostatniego pullu z MyDr
Limit 2000 slotów per request. 30-dniowy grafik 6 lekarzy × 8h × 2 sloty/h = 2880 slotów → dziel na 2 paczki.
DELETE /api/v1/clinic/slots/{external_id}
Klinika cofnęła grafik — slot przechodzi do status=blocked (booking
lifecycle nietknięty: jeśli był booked to nadal jest, ale lekarza
poinformuje webhook booking.cancelled).
Auth: Bearer pk_live_* z scope slots:write.
Request
DELETE /api/v1/clinic/slots/slot-2026-05-10-1000-anna HTTP/1.1
Authorization: Bearer pk_live_XXXXXXXX_<secret>
Response 204
HTTP/1.1 204 No Content
Response 404
Slot z tym external_id nie istnieje w mappingu tej klinki.
{"detail": "Slot nie znaleziony"}
Effect
- Slot status:
available→blocked(znika z public discovery) - Jeśli był
booked: status pozostaje, ale emitujemybooking.cancelledwebhook z reasonslot_removed_by_clinic - Pacjent dostaje email + SMS o anulowaniu
- Twój system powinien zrobić
cancelw MyDr (jeśli to bidirectional flow)
Race-safety
Booking flow:
Pacjent A: POST /bookings { slot_id: X } ┐
Pacjent B: POST /bookings { slot_id: X } ├── równoczesnie
│
Pandoktorek: UPDATE slot SET status=booked │
WHERE id=X AND status=available │
RETURNING * │
│
Wins: pierwszy (rowcount=1) ──► 201 created
Loses: drugi (rowcount=0) ──► 409 conflict
Twój re-push slot status=booked jest pomijany (skipped_booked) — booking
nie zostaje odbook'owany.
Limity
- Max 2000 slots per request
- start_at/end_at muszą być >= teraz - 1h (chroni przed historycznym push'em)
- Doctor
doctor_external_idmusi istnieć w mappingu (push lekarzy ZANIM slotów) - Rate limit: 10 000 requests/dobę per API key