Dokumentacja API
Rezerwacje
Własny frontend umawia wizyty w tym samym kalendarzu co wbudowana strona placówki: katalog usług i specjalistów, wolne terminy i rezerwacja. Nie ma drugiego grafiku do synchronizowania.
Endpointy
| Endpoint | Odpowiedź 200 | Opis |
|---|---|---|
| GET /api/v1/booking/catalog | { services: BookingCatalogService[]; resources: BookingCatalogResource[] } | Katalog rezerwacji: aktywne usługi i specjaliści z powiązaniami do zabiegów i zespołu |
| GET /api/v1/booking/slots | { slots: Slot[] } | Wolne terminy dla usługi (opcjonalnie zawężone do specjalisty) |
| GET /api/v1/booking/appointments | { appointments: Appointment[] } | Lista wizyt (z nazwami usługi i specjalisty) |
| POST /api/v1/booking/appointments | AppointmentCreated | Rezerwacja wizyty (startAt musi pochodzić z /booking/slots) |
1. Katalog
Katalog zwraca aktywne usługi rezerwacji i aktywnych specjalistów placówki. Identyfikatory z katalogu to parametry service i resource dla wolnych terminów oraz pola serviceId i resourceId przy rezerwacji.
curl "https://app.pozyskajpacjenta.pl/api/v1/booking/catalog" \
-H "Authorization: Bearer pp_live_TWOJ_KLUCZ"{
"services": [
{
"id": "bs_4k9x2m",
"name": "Konsultacja implantologiczna",
"durationMin": 30,
"priceLabel": "300 zł",
"resourceIds": ["br_7q1w3e"],
"serviceSlug": "implanty",
"nfz": false
}
],
"resources": [
{
"id": "br_7q1w3e",
"name": "Anna Kowalska",
"title": "lek. dent.",
"doctorSlug": "anna-kowalska"
}
]
}priceLabelto cena samej wizyty, np. konsultacja za 300 zł, nawet gdy powiązany zabieg kosztuje „od 3 200 zł”. Gdy placówka nie podała ceny wizyty, widzisz cenę powiązanego zabiegu (priceFromLabelzGET /api/v1/services/{slug}), a gdy nie ma żadnej —null. Tak działa od wersji 1.8.1, tak samo jak widżet rezerwacji na stronie placówki.resourceIdsto specjaliści, u których da się umówić tę usługę. Pusta lista oznacza, że dziś nie ma u kogo jej umówić.serviceSlugidoctorSlugprowadzą do opublikowanego zabiegu i profilu w zespole. W drugą stronę działają polabookingServiceIdzabiegu ibookingResourceIdprofilu (Encje): przycisk „Umów” na stronie zabiegu ma od razu właściwe id usługi. Wartośćnulloznacza, że tej pozycji nie da się umówić online.nfz(od 1.8.0):trueoznacza świadczenie NFZ. Pokaż je z dopiskiem „rejestracja telefoniczna” i telefonem placówki — online się go nie umówi:/booking/slotsi/booking/appointmentsodpowiadają 400 zcode: "nfz_phone_only". Brak pola (starszy serwer) znaczyfalse.
2. Wolne terminy
Terminy liczymy na żywo z grafików, urlopów i już umówionych wizyt. Zakres to najwyżej 31 dni; bez parametru resource dostajesz terminy wszystkich specjalistów wykonujących usługę. Odpowiedź nie jest cache'owana.
curl "https://app.pozyskajpacjenta.pl/api/v1/booking/slots?service=bs_4k9x2m&from=2026-10-05&to=2026-10-09" \
-H "Authorization: Bearer pp_live_TWOJ_KLUCZ"3. Rezerwacja
startAt musi pochodzić z listy wolnych terminów. Pacjent trafia do CRM jako lead (znajdowany po telefonie albo tworzony), a placówka dostaje wizytę w kalendarzu i e-mail „Nowa wizyta” z plikiem .ics na adres powiadomień.
curl -X POST "https://app.pozyskajpacjenta.pl/api/v1/booking/appointments" \
-H "Authorization: Bearer pp_live_TWOJ_KLUCZ" \
-H "Content-Type: application/json" \
-d '{
"serviceId": "bs_4k9x2m",
"resourceId": "br_7q1w3e",
"startAt": 1791187200,
"patient": { "name": "Jan Nowak", "phone": "+48 600 100 200" }
}'Od 1.7.0: rezerwacja opiera się na art. 6 ust. 1 lit. b i art. 9 ust. 2 lit. h RODO, więc formularz nie ma wymaganej zgody na przetwarzanie. Pokaż pod nim klauzulę informacyjną z GET /api/v1/privacy i opcjonalne, niezaznaczone pole zgody marketingowej, a w treści rezerwacji wyślij privacyNoticeVersion i (gdy pacjent zaznaczył pole) marketingConsent: true. Wizyta zapisze wersję klauzuli i czas wysłania. Zgoda marketingowa bez privacyNoticeVersion nie jest zapisywana, a u powracającego pacjenta zgoda e-mail obejmuje tylko adres zapisany przy jego leadzie.
Jeśli ktoś zajął ten termin w międzyczasie, dostajesz 409 i nic się nie zapisuje. Odpowiedź zawiera świeżą listę wolnych terminów tego dnia, więc możesz od razu zaproponować pacjentowi inny:
{
"error": "slot_taken",
"slots": [
{
"resourceId": "br_7q1w3e",
"startAt": 1791189000,
"endAt": 1791190800,
"localDate": "2026-10-05",
"localStartMin": 630
}
]
}Pola
BookingCatalogService — usługa w katalogu
| Pole | Typ | Wymagane | Opis |
|---|---|---|---|
| id | string | tak | Parametr service dla /booking/slots. |
| name | string | tak | |
| durationMin | integer | tak | Czas wizyty w minutach. |
| priceLabel | string | null | tak | Cena wizyty do wyświetlenia: etykieta ceny samej usługi rezerwacji (np. konsultacja 300 zł), a gdy placówka jej nie ustawiła — priceFromLabel opublikowanego zabiegu powiązanego z usługą (ta sama, co w /services/{slug}); null, gdy nie ma żadnej. Od 1.8.1 cena wizyty ma pierwszeństwo — wcześniej wygrywała cena zabiegu, więc konsultacja powiązana z ortodoncją pokazywała „od 1900 zł” zamiast swoich 300 zł. |
| resourceIds | string[] | tak | Aktywni specjaliści wykonujący usługę (id z resources). Pusta lista = nie ma u kogo umówić. Wolne terminy zawsze z /booking/slots. |
| serviceSlug | string | null | tak | Slug opublikowanego zabiegu powiązanego z usługą (GET /api/v1/services/{slug}); null, gdy żaden opublikowany zabieg jej nie wskazuje. |
| nfz | boolean | — | Od 1.8.0. true = świadczenie NFZ: pokazujesz je informacyjnie z dopiskiem „rejestracja telefoniczna” i telefonem placówki, ale nie da się go zarezerwować online — /booking/slots i /booking/appointments odpowiadają 400 z code nfz_phone_only. Brak pola (starsze serwery) = false. |
BookingCatalogResource — specjalista w katalogu
| Pole | Typ | Wymagane | Opis |
|---|---|---|---|
| id | string | tak | Parametr resource dla /booking/slots. |
| name | string | tak | |
| title | string | null | tak | Tytuł, np. lek. dent. |
| doctorSlug | string | null | tak | Slug opublikowanego profilu w zespole (GET /api/v1/doctors/{slug}); null, gdy żaden opublikowany profil go nie wskazuje. |
Slot — wolny termin
| Pole | Typ | Wymagane | Opis |
|---|---|---|---|
| resourceId | string | tak | |
| startAt | integer | tak | Początek, sekundy epoch (UTC). |
| endAt | integer | tak | Koniec, sekundy epoch (UTC). |
| localDate | string | tak | Lokalna data YYYY-MM-DD. |
| localStartMin | integer | tak | Lokalna minuta dnia (np. 570 = 9:30). |
SlotTaken — odpowiedź 409 na zajęty termin
| Pole | Typ | Wymagane | Opis |
|---|---|---|---|
| error | "slot_taken" | tak | |
| slots | Slot[] | tak |
Czytaj dalej: Leady →