Przejdź do głównej zawartości

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​

PoleTypWymaganeOpis
external_idstring (1-120)✅Twój ID z CRM (np. slot-2026-05-10-1000)
doctor_external_idstring (1-120)✅external_id lekarza (musi istnieć w mapping)
start_atdatetime (ISO8601)✅Z timezone (+02:00 Warszawa)
end_atdatetime (ISO8601)✅Z timezone, > start_at
services_allowedstring[]—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 emitujemy booking.cancelled webhook z reason slot_removed_by_clinic
  • Pacjent dostaje email + SMS o anulowaniu
  • Twój system powinien zrobić cancel w 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_id musi istnieć w mappingu (push lekarzy ZANIM slotów)
  • Rate limit: 10 000 requests/dobę per API key