Przejdź do głównej zawartości

Rezerwacje i dostępność

Ten przewodnik opisuje przepływ rezerwacji i reguły, które nim rządzą. Pola i parametry każdego endpointu znajdziesz w referencji API.

  1. GET /v1/services — wybierz usługę.
  2. GET /v1/availability — pobierz wolne terminy usługi.
  3. POST /v1/bookings — zarezerwuj wybrany termin.
  4. GET /v1/bookings/{id} albo GET /v1/bookings — śledź stan rezerwacji.
  5. POST /v1/bookings/{id}/cancel — anuluj, jeśli trzeba.

API zwraca te same usługi, które klient widzi w widgecie organizacji: aktywne, publiczne i z co najmniej jednym aktywnym pracownikiem. Rezerwację utworzysz tylko dla usługi z tej listy — inna daje 404 not_found.

Przed rezerwacją sprawdź w usłudze:

  • modes — online, offline albo oba. Gdy usługa ma oba tryby, w rezerwacji musisz podać mode.
  • required_client_fields — które dane klienta są wymagane. name jest zawsze. Organizacja może wymagać też email, phone albo obu.
  • payment — none, optional, required albo deposit. Dwie ostatnie wartości oznaczają płatność z góry. Zobacz Płatności online.
  • staff_ids — pracownicy, którzy wykonują usługę.

GET /v1/staff zwraca pracowników z imieniem i listą usług, bez danych kontaktowych.

Listy usług i pracowników mają Cache-Control: private, max-age=60. Możesz je cache’ować przez minutę.

GET /v1/availability przyjmuje service_id, opcjonalnie staff_id oraz daty from i to (YYYY-MM-DD, obie włącznie).

  • to może być najwyżej 31 dni po from. Dłuższy zakres daje 422 range_too_large, a to przed from — 422 invalid_range.
  • Organizacja, która chwilowo nie przyjmuje rezerwacji, zwraca 409 booking_unavailable zamiast slotów. Pusta lista data znaczy więc tylko brak wolnych terminów.
  • Dni w przeszłości i dni za horyzontem rezerwacji organizacji nie mają slotów. Zakres jest przycinany, a nie odrzucany.
  • „Dziś” to data w strefie organizacji, nie w UTC.
  • Bez staff_id dostajesz sloty wszystkich pracowników usługi. Każdy slot ma staff_id pracownika, który go obsłuży.
  • Sloty uwzględniają godziny pracy, bufory, minimalne wyprzedzenie i istniejące rezerwacje — tak jak widget.

Odpowiedź to {data: [...]} bez paginacji i z Cache-Control: no-store. Wolne terminy zmieniają się z każdą rezerwacją, więc pobieraj je tuż przed pokazaniem klientowi.

W noc zmiany czasu end - start zawsze odpowiada rzeczywistemu czasowi usługi, a offset końca może różnić się od offsetu początku.

POST /v1/bookings wymaga scope’u bookings:write i nagłówka Idempotency-Key. Body:

  • service_id, start (z offsetem) i client są wymagane;
  • staff_id — pracownik slotu; bez niego Rezeri wybierze pracownika sam;
  • mode — gdy usługa ma oba tryby;
  • pay_online, return_url — dla płatności online, zobacz Płatności online;
  • terms_accepted, terms_hash, early_performance_requested — akceptacja warunków, zobacz Warunki rezerwacji;
  • metadata — Twoje dane (niżej).

Body z nieznanym polem daje 422 unknown_field.

Rezerwacja przechodzi te same reguły co rezerwacja z widgetu:

Sytuacja Błąd
Usługa albo pracownik spoza oferty organizacji 404 not_found
Brak mode przy usłudze z dwoma trybami 422 mode_required
mode, którego usługa nie oferuje 422 mode_not_supported
Brak wymaganego e-maila albo telefonu klienta 422 client_email_required, 422 client_phone_required
Termin zajęty, w przeszłości, za blisko albo za daleko w przyszłości 409 slot_unavailable
Organizacja chwilowo nie przyjmuje rezerwacji 409 booking_unavailable
Wyczerpany miesięczny limit rezerwacji planu organizacji 403 booking_limit_reached
Za dużo rezerwacji jednego klienta albo pracownika 429 client_booking_limit

start nie musi leżeć na siatce slotów, ale musi przejść wszystkie powyższe kontrole. Najpewniej rezerwować start i staff_id slotu z /v1/availability.

Po 409 slot_unavailable pobierz wolne terminy ponownie i zaproponuj klientowi inny.

Jeśli organizacja ma warunki rezerwacji albo własny regulamin, klient musi je zaakceptować w Twoim formularzu przed rezerwacją.

  1. Pobierz warunki: GET /v1/booking-terms?service_id=...&start=... (scope bookings:write; pay_online=true wybiera wariant dla płatności online). Odpowiedź ma Cache-Control: no-store i status 200 także wtedy, gdy organizacja nie ma warunków (terms: null):

    { "terms": { "...": "..." }, "privacy_notice": { "...": "..." } }

    terms jest null, gdy organizacja nie ma warunków do akceptacji albo gdy rezerwacja w tym terminie powstanie jako prośba, która nie blokuje terminu do decyzji usługodawcy — wtedy pomiń kolejne kroki.

  2. Pokaż klientowi terms.sections (tytuły i akapity), a gdy jest terms.provider_terms — link do regulaminu organizacji (url; dla regulaminu tekstowego jest to bezwzględny adres strony). Gdy terms.request nie jest null, pokaż też jego label i description jako osobną zgodę na rozpoczęcie usługi przed upływem 14 dni na odstąpienie od umowy.

  3. Pod formularzem pokaż klauzulę informacyjną z art. 13 RODO na podstawie privacy_notice (nazwa, adres, NIP i e-mail administratora, czyli usługodawcy). Rezeri niczego z niej nie zapisuje i nie wymaga potwierdzenia.

  4. Wyślij w POST /v1/bookings: terms_accepted: true, terms_hash z odpowiedzi oraz early_performance_requested: true, gdy klient wyraził zgodę z kroku 2 (terms.request nie jest null). terms_hash ma najwyżej 128 znaków.

Akceptacji nie wymaga tylko prośba o rezerwację, która nie blokuje terminu do czasu decyzji usługodawcy (status pending). Rezerwacja czekająca na akceptację usługodawcy, ale blokująca termin (też pending), wymaga akceptacji warunków jak każda inna.

Sytuacja Błąd
Brak terms_accepted: true albo terms_hash 422 terms_required
Brak early_performance_requested, choć terms.request nie jest null 422 early_performance_required
terms_hash nieaktualny — warunki się zmieniły 409 terms_changed

Po 409 terms_changed pobierz warunki ponownie i pokaż je klientowi. Odrzucenie przez warunki zwalnia Idempotency-Key, więc ponowienie z tym samym kluczem i poprawnymi polami utworzy rezerwację.

Odpowiedź 201 zawiera pełną rezerwację. Rezerwacja ma źródło „API” i trafia do kalendarza panelu jak każda inna. Klient końcowy dostaje standardowe powiadomienia organizacji.

API nie wymaga od klienta weryfikacji kontaktu kodem. Uwierzytelnienie klientów jest po stronie Twojego systemu.

status Znaczenie
pending czeka na akceptację pracownika
awaiting_payment czeka na płatność online
accepted przyjęta
cancelled anulowana
completed zakończona

Nieobecność klienta nie jest statusem. Zaznacza ją pole no_show_at z czasem oznaczenia albo null.

W przyszłości mogą dojść nowe statusy. Obsłuż nieznaną wartość, zamiast odrzucać odpowiedź.

Kilka pól wymaga komentarza:

  • price — uzgodniona cena jako string ("90.00") albo null.
  • payment — stan płatności online: required, status, amount, url. Zobacz Płatności online.
  • client.email i client.phone mogą być null — rezerwacja z panelu może mieć tylko jeden kontakt.
  • service_id i staff_id to wartości z chwili rezerwacji. Starsza rezerwacja może wskazywać usługę albo pracownika, których nie zwraca już /v1/services ani /v1/staff.
  • manage_url — strona rezerwacji dla klienta końcowego.
  • metadata — obiekt z Twoimi danymi albo {} dla rezerwacji spoza API.

Odpowiedzi z rezerwacją mają Cache-Control: no-store — zawierają dane osobowe klienta.

metadata to do 20 par klucz–wartość, które zapisujesz przy tworzeniu rezerwacji. Klucz ma do 40 znaków, wartość jest stringiem do 500 znaków. Przekroczenie limitu daje 422 validation_error.

Metadane wracają w każdej odpowiedzi z rezerwacją: przy tworzeniu, odczycie, na liście i przy anulowaniu. Użyj ich do powiązania rezerwacji z rekordem w swoim systemie, np. {"order_id": "1001"}.

Nie zapisuj w metadanych danych wrażliwych.

GET /v1/bookings zwraca rezerwacje organizacji posortowane po start rosnąco.

Filtry:

  • from i to — czas z offsetem; lista obejmuje rezerwacje z start w przedziale [from, to). Czas bez offsetu daje 422 validation_error.
  • status — jedna z publicznych wartości statusu.
  • staff_id — rezerwacje jednego pracownika.

Listy z paginacją — usługi, pracownicy, rezerwacje i endpointy webhooków — mają ten sam kształt (GET /v1/availability nie jest paginowane, zwraca same data):

{"data": [ ... ], "has_more": true, "next_cursor": "eyJrIjoi..."}

Gdy has_more jest true, pobierz kolejną stronę z cursor=<next_cursor> i tymi samymi filtrami:

Okno terminala
curl "https://api.rezeri.com/v1/bookings?from=2026-10-01T00:00:00%2B02:00&limit=50&cursor=eyJrIjoi..." \
-H "Authorization: Bearer $REZERI_API_KEY"

limit przyjmuje 1–100 (domyślnie 20). Kursor jest nieprzezroczysty — nie składaj go sam. Zły kursor albo kursor z innej listy daje 422 invalid_cursor. Na ostatniej stronie has_more ma wartość false, a next_cursor — null.

Pamiętaj o %2B zamiast + w offsecie w adresie URL.

POST /v1/bookings/{id}/cancel anuluje rezerwację w imieniu klienta. Body jest opcjonalne: {"reason": "…"} (do 500 znaków).

Okno terminala
curl -X POST https://api.rezeri.com/v1/bookings/311905c9-7a15-45c0-8b66-1dd2e8711875/cancel \
-H "Authorization: Bearer $REZERI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"reason": "Klient zmienił plany"}'
  • Odpowiedź 200 zawiera anulowaną rezerwację.
  • Ponowne anulowanie już anulowanej rezerwacji też daje 200.
  • Wizyta, która już się rozpoczęła, daje 409 cancellation_window_closed.
  • Rezerwacja w stanie, którego nie da się anulować (np. completed), daje 409 not_cancellable.
  • Zwrot płatności liczy się według zasad anulowania organizacji, tak jak przy anulowaniu przez klienta na stronie rezerwacji.

Nagłówek Idempotency-Key jest tu opcjonalny.

v1 nie ma endpointu przełożenia. Kieruj klienta na manage_url z odpowiedzi rezerwacji — tam przełoży albo anuluje wizytę sam. Zmianę zobaczysz w GET /v1/bookings/{id}.

Link do spotkania online nie jest częścią odpowiedzi. Klient znajdzie go pod manage_url.

Każde ID rezerwacji, usługi albo pracownika spoza organizacji klucza daje 404 not_found — w ścieżce i w body. API nie zdradza, czy taki zasób istnieje.