Do czego służy API
API ma dwa rodzaje tokenów. Każdy wystawia ktoś inny i każdy może co innego.
- Token klienta wystawia klient na swoim koncie i działa tylko w jego imieniu: wolne terminy, założenie, lista, odczyt, przeniesienie i anulowanie jego własnych rezerwacji — według tych samych zasad co w aplikacji, łącznie z terminem anulowania, który ustala firma. Jest bezpłatny, a moduł API firmy go nie wyłącza.
- Token firmowy wystawia firma w panelu administracyjnym i działa w imieniu firmy: zakłada klientów firmy, na przykład przy przenosinach z innego systemu. Nie ma dostępu do rezerwacji ani wolnych terminów — te zawsze należą do konkretnej osoby.
Czego API jeszcze nie potrafi: wypisać usług, płatności, webhooków, operacji zbiorczych i usuwania rezerwacji (anulowana rezerwacja zostaje w systemie).
Jak zdobyć token
Token firmowy
- Zaloguj się do panelu administracyjnego jako administrator firmy.
- W menu Klienti otwórz Firemní přístup přes API (firmowy dostęp przez API).
- W polu Popis (opis) wpisz, do czego będzie token (na przykład „Import klientów z Excela”), i kliknij Vydat (wystaw).
- Token wyświetla się tylko raz, zaraz po wystawieniu. Skopiuj go: przechowujemy tylko jego skrót i nie da się go ponownie wyświetlić.
Lista pod formularzem pokazuje potem opis, początek tokenu i kiedy był ostatnio użyty. Przycisk Zneplatnit (unieważnij) natychmiast zatrzymuje token; nowy wystawisz tak samo jak pierwszy.
Strona jest dostępna, dopóki firma ma włączony moduł API (domyślnie jest włączony). Wyłączony moduł zatrzymuje wszystkie tokeny firmowe, tokeny klientów nie.
Otwórz zarządzanie tokenami w panelu administracyjnym
Token klienta
Klient wystawia token sam: Konto → Dostęp przez API → Wystaw token. Obowiązuje to samo: wyświetla się raz i można go w każdej chwili unieważnić.
Uwierzytelnianie
Każde zapytanie niesie token w nagłówku Authorization ze słowem Bearer. Token zaczyna się od rzv_live_.
Authorization: Bearer rzv_live_…
Wywołuj zawsze przez https. Adres bazowy interfejsu:
https://reservuj.cz/api/v1
Brakujący, nieprawidłowy lub unieważniony token zwraca 401 unauthorized. Token klienta przestaje działać także wtedy, gdy firma zablokuje konto klienta.
Pierwsze wywołanie
Zacznij od punktu końcowego GET /me. Niczego nie zmienia, mówi tylko, do kogo należy token: typ, firmę, zakresy i limity. Działa z oboma rodzajami tokenów.
curl -s "https://reservuj.cz/api/v1/me" \
-H "Authorization: Bearer $RESERVUJ_TOKEN"
const response = await fetch('https://reservuj.cz/api/v1/me', {
headers: { Authorization: `Bearer ${process.env.RESERVUJ_TOKEN}` },
});
const body = await response.json();
if (!response.ok) {
throw new Error(`${response.status} ${body.error.code}: ${body.error.message}`);
}
console.log(body.data.type, body.data.scopes);
Odpowiedź dla tokenu firmowego:
{
"data": {
"type": "company",
"organization_id": 42,
"client_id": null,
"scopes": [
"clients"
],
"rate_limit": {
"limit_per_minute": 60,
"availability_cost": 5
}
}
}
Token firmowy: założenie klienta
POST /clients z treścią application/json. Wymagane są e-mail i nazwisko, opcjonalne imię, telefon i notatka (e-mail, imię i nazwisko najwyżej 50 znaków, telefon 20, notatka 255).
curl -s -X POST "https://reservuj.cz/api/v1/clients" \
-H "Authorization: Bearer $RESERVUJ_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: import-2026-jana-novakova" \
-d '{"email":"jana.novakova@example.com","surname":"Nováková","name":"Jana","phone":"+420777123456"}'
import { randomUUID } from 'node:crypto';
const idempotencyKey = randomUUID();
const response = await fetch('https://reservuj.cz/api/v1/clients', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.RESERVUJ_TOKEN}`,
'Content-Type': 'application/json',
'Idempotency-Key': idempotencyKey,
},
body: JSON.stringify({ email: 'jana.novakova@example.com', surname: 'Nováková', name: 'Jana' }),
});
const body = await response.json();
console.log(response.status, body);
Odpowiedź 201. Znany e-mail nie zakłada drugiego klienta, tylko uzupełnia brakujące dane; pole created mówi, co się stało.
{
"data": {
"email": "jana.novakova@example.com",
"created": true
}
}
Założony klient nie dostaje żadnego e-maila. Poinformuj swoich klientów z wyprzedzeniem, że przenosisz ich dane do nowego systemu.
Token klienta: wolne terminy i rezerwacje
Wolne terminy wyszukujesz dla jednej usługi, najwyżej 31 dni naraz. ID usługi (service_id) to ID wiersza cennika: firma widzi je w panelu administracyjnym w cenniku (Ceník) w kolumnie ID i przekazuje klientowi. Listy usług API jeszcze nie udostępnia.
curl -s "https://reservuj.cz/api/v1/availability?service_id=123&date_from=2026-11-16&date_to=2026-11-22" \
-H "Authorization: Bearer $RESERVUJ_TOKEN"
{
"data": [
{
"start": "2026-11-17T10:00:00+01:00",
"end": "2026-11-17T11:00:00+01:00",
"service_id": 123,
"provider_id": 7,
"branch_id": 3
}
]
}
Rezerwację zakładasz na początek z odpowiedzi. Z provider_id i branch_id z wolnego terminu powstanie dokładnie u tego pracownika i w tym oddziale; bez nich firma przydzieli kogokolwiek wolnego.
curl -s -X POST "https://reservuj.cz/api/v1/reservations" \
-H "Authorization: Bearer $RESERVUJ_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 8d0c6a52-5d0e-4f4e-9a49-2f6b0c3e7a11" \
-d '{"service_id":123,"start":"2026-11-17T10:00:00+01:00","provider_id":7,"branch_id":3,"note":"Poprvé"}'
Czasy są w formacie ISO 8601 z przesunięciem strefy; czas z innym przesunięciem jest przeliczany na czas serwera (Europe/Prague). Między zapytaniem o wolne terminy a założeniem termin może zniknąć — wtedy przyjdzie 409.
Stronicowanie
GET /reservations zwraca własne rezerwacje klienta posortowane według początku, stronami. Parametr limit wynosi od 1 do 200 (domyślnie 50). Jeśli w odpowiedzi jest next_cursor, wyślij go w parametrze cursor następnego zapytania z tym samym filtrem (from, to, status); null oznacza ostatnią stronę. Kursor z innego filtra zwraca 400 invalid_cursor.
let cursor = null;
do {
const url = new URL('https://reservuj.cz/api/v1/reservations');
url.searchParams.set('from', '2026-11-16T00:00:00Z');
url.searchParams.set('limit', '200');
if (cursor) {
url.searchParams.set('cursor', cursor);
}
const response = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.RESERVUJ_TOKEN}` },
});
const page = await response.json();
for (const reservation of page.data) {
console.log(reservation.id, reservation.status, reservation.start);
}
cursor = page.next_cursor;
} while (cursor);
Odczyt rezerwacji zwraca nagłówek ETag. Odeślij go w If-None-Match, a niezmienione dane przyjdą jako 304 bez treści.
Błędy i kody
Błąd ma zawsze ten sam kształt. Na kodzie (error.code) można polegać, tekst (error.message) jest dla człowieka i może się zmienić. Nieznany kod oceniaj według statusu HTTP.
{
"error": {
"code": "conflict",
"message": "Služba se platí zálohou online; objednejte ji v rezervačním portálu."
}
}
| Kod | HTTP | Kiedy przychodzi |
|---|---|---|
unauthorized |
401 | Brak tokenu, token jest nieprawidłowy lub unieważniony, nie ma zakresu do tej operacji albo firma wyłączyła moduł API (token firmowy). |
bad_request |
400 | Błędny lub brakujący parametr, zły format Idempotency-Key. |
invalid_json |
400 | Brak treści zapisu, treść nie jest prawidłowym JSON-em albo nie jest obiektem. |
invalid_cursor |
400 | Kursor jest nieprawidłowy albo należy do innego filtra. |
idempotency_key_required |
400 | Przeniesienie rezerwacji bez nagłówka Idempotency-Key. |
not_found |
404 | Rekord nie istnieje albo token nie ma do niego dostępu; nieznana ścieżka. |
conflict |
409 | Termin jest zajęty, na anulowanie lub przeniesienie jest za późno, rezerwacji nie da się przenieść, firma jest zawieszona albo jeszcze nie przyjmuje rezerwacji, usługa jest płatna zaliczką online. |
idempotency_in_progress |
409 | Zapytanie z tym samym Idempotency-Key jest jeszcze przetwarzane. |
unsupported_media_type |
415 | Treść zapisu nie jest application/json. |
unprocessable |
422 | Dane są poprawne, ale nie można ich przyjąć: usługa nie jest w ofercie dla klienta, termin jest poza godzinami otwarcia, zbyt wcześnie albo nie jest oferowany przy przeniesieniu, pracownik lub oddział nie należy do usługi. |
idempotency_key_reused |
422 | Ten sam Idempotency-Key z inną treścią zapytania. |
rate_limited |
429 | Przekroczony limit zapytań. |
method_not_allowed |
405 | Ścieżka nie zna tej metody. |
server_error |
500 | Błąd po naszej stronie; w error.details.incident jest oznaczenie dla wsparcia. |
409 przy usłudze z zaliczką. Usługi, za którą firma pobiera zaliczkę online, API nie zarezerwuje: odpowie 409 conflict i niczego nie zapisze. Skieruj klienta do portalu rezerwacji firmy, gdzie zapłaci zaliczkę.
409 zwraca też zajęty termin (w międzyczasie zajął go ktoś inny — wczytaj wolne terminy ponownie), spóźnione anulowanie lub przeniesienie, zawieszona firma i firma, która jeszcze nie przyjmuje rezerwacji.
Cudzy rekord odpowiada jak nieistniejący (404), nie 403 — z odpowiedzi nie da się poznać, co istnieje.
Przy 500 server_error podaj wsparciu oznaczenie z error.details.incident. Zapis możesz powtórzyć z tym samym Idempotency-Key; jeśli już przeszedł, nie zapisze się drugi raz.
Limity zapytań
Każdy token ma 60 punktów na kroczącą minutę. Zwykłe zapytanie kosztuje jeden punkt, wyszukiwanie wolnych terminów 5 punktów, ponieważ jest wymagające obliczeniowo.
Każda odpowiedź zawiera RateLimit-Limit, RateLimit-Remaining i RateLimit-Reset. Po przekroczeniu przychodzi 429 rate_limited z nagłówkiem Retry-After: odczekaj podaną liczbę sekund i kontynuuj.
HTTP/1.1 429 Too Many Requests
RateLimit-Limit: 60
RateLimit-Remaining: 0
RateLimit-Reset: 17
Retry-After: 17
Zapytania bez prawidłowego tokenu są liczone według adresu IP, aby zgadywanie tokenów nie było darmowe.
Bezpieczne ponawianie (Idempotency-Key)
Zapisy (POST i DELETE) możesz wysyłać z nagłówkiem Idempotency-Key — unikalną wartością dla jednej operacji, na przykład UUID. Gdy połączenie zostanie przerwane i nie wiesz, czy zapis przeszedł, wyślij to samo zapytanie ponownie z tym samym kluczem: dostaniesz zapisaną odpowiedź z nagłówkiem Idempotency-Replayed: true i nic nie zapisze się dwa razy.
- Klucz ma od 1 do 100 widocznych znaków ASCII, rozróżnia wielkie i małe litery i jest ważny 24 godziny, tylko dla tego samego tokenu.
- Ten sam klucz z inną treścią zwraca 422 idempotency_key_reused, ponowienie w trakcie przetwarzania 409 idempotency_in_progress.
- Przy przeniesieniu rezerwacji (POST /reservations/{id}/move) nagłówek jest wymagany, bez niego przyjdzie 400 idempotency_key_required.
- Zapisywany jest sukces oraz odmowy 409 lub 422. Błędne dane ani odmowy z powodu bieżącego stanu firmy nie są zapisywane, więc poprawiona lub późniejsza próba przejdzie.
Wersje i zmiany
Wersja interfejsu jest w adresie (/api/v1). Nowe pola, wartości wyliczeń lub punkty końcowe przybywają bez nowej wersji w adresie, dlatego nieznane pola i wartości ignoruj, a nieznany status rezerwacji traktuj jako zakończoną rezerwację, która się nie odbyła. Usunięcie lub zmiana znaczenia oznaczałyby /api/v2.
Bezpieczeństwo
- Token nie należy do przeglądarki. Kod strony internetowej lub aplikacji odczyta każdy, kto ją otworzy, i z tokenem będzie działać w Twoim imieniu. Dlatego API nie wysyła nagłówków CORS, a przeglądarka zablokuje wywołanie z obcej strony. Wywołuj je ze swojego serwera.
- Przechowuj token jako dane poufne (zmienna środowiskowa, sejf haseł), nie w kodzie źródłowym ani w repozytorium.
- Dla każdej integracji wystaw osobny token z trafnym opisem; przy wycieku unieważnisz tylko ten jeden.
- Przy podejrzeniu wycieku natychmiast unieważnij token. Unieważnienie działa od razu.
- Nie dawaj tokenu firmowego klientom — oni wystawiają własny token na swoim koncie.