Для чого API
API має два види токенів. Кожен видає хтось інший і кожен може інше.
- Токен клієнта видає клієнт у своєму обліковому записі, і він діє тільки від його імені: вільний час, створення, список, читання, перенесення та скасування його власних бронювань — за тими самими правилами, що й у застосунку, включно з терміном скасування, який встановлює компанія. Він безкоштовний, і модуль API компанії його не вимикає.
- Токен компанії видає компанія в адмініструванні, і він діє від імені компанії: створює клієнтів компанії, наприклад під час переходу з іншої системи. До бронювань і вільного часу доступу не має — вони завжди належать конкретній людині.
Чого API поки не вміє: показати список послуг, платежі, вебхуки, масові операції та видалення бронювань (скасоване бронювання залишається в системі).
Як отримати токен
Токен компанії
- Увійдіть в адміністрування як адміністратор закладу.
- У меню Klienti відкрийте Firemní přístup přes API (доступ компанії через API).
- У полі Popis (опис) напишіть, для чого буде токен (наприклад, «Імпорт клієнтів з Excel»), і натисніть Vydat (видати).
- Токен показується лише один раз, одразу після видачі. Скопіюйте його: ми зберігаємо тільки його хеш і показати його знову неможливо.
Список під формою потім показує опис, початок токена і коли його востаннє використали. Кнопка Zneplatnit (анулювати) негайно зупиняє токен; новий видаєте так само, як перший.
Сторінка доступна, поки компанія має увімкнений модуль API (за замовчуванням він увімкнений). Вимкнений модуль зупиняє всі токени компанії, токени клієнтів — ні.
Відкрити керування токенами в адмініструванні
Токен клієнта
Клієнт видає токен сам: Обліковий запис → Доступ через API → Видати токен. Діє те саме: показується один раз, і анулювати його можна будь-коли.
Автентифікація
Кожен запит несе токен у заголовку Authorization зі словом Bearer. Токен починається з rzv_live_.
Authorization: Bearer rzv_live_…
Завжди викликайте через https. Базова адреса інтерфейсу:
https://reservuj.cz/api/v1
Відсутній, недійсний або анульований токен повертає 401 unauthorized. Токен клієнта перестає діяти й тоді, коли компанія заблокує обліковий запис клієнта.
Перший виклик
Почніть з кінцевої точки GET /me. Вона нічого не змінює, лише каже, кому належить токен: тип, компанію, дозволи та ліміти. Працює з обома видами токенів.
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);
Відповідь для токена компанії:
{
"data": {
"type": "company",
"organization_id": 42,
"client_id": null,
"scopes": [
"clients"
],
"rate_limit": {
"limit_per_minute": 60,
"availability_cost": 5
}
}
}
Токен компанії: створення клієнта
POST /clients з тілом application/json. Обовʼязкові e-mail і прізвище, необовʼязкові імʼя, телефон і примітка (e-mail, імʼя та прізвище щонайбільше 50 символів, телефон 20, примітка 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);
Відповідь 201. Відомий e-mail не створює другого клієнта, а лише доповнює відсутні дані; поле created каже, що сталося.
{
"data": {
"email": "jana.novakova@example.com",
"created": true
}
}
Створений клієнт не отримує жодного листа. Заздалегідь повідомте своїх клієнтів, що переносите їхні дані в нову систему.
Токен клієнта: вільний час і бронювання
Вільний час шукаєте для однієї послуги, щонайбільше на 31 днів за раз. ID послуги (service_id) — це ID рядка прайс-листа: компанія бачить його в адмініструванні в прайс-листі (Ceník) у стовпці ID і передає клієнтові. Список послуг API поки не видає.
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
}
]
}
Бронювання створюєте на початок із відповіді. З provider_id і branch_id з вільного часу воно буде саме в цього працівника й у цій філії; без них компанія призначить будь-кого вільного.
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é"}'
Час у форматі ISO 8601 зі зміщенням; час з іншим зміщенням перераховується на час сервера (Europe/Prague). Між запитом вільного часу і створенням бронювання час може зникнути — тоді прийде 409.
Посторінковий вивід
GET /reservations повертає власні бронювання клієнта, відсортовані за початком, посторінково. Параметр limit від 1 до 200 (за замовчуванням 50). Якщо у відповіді є next_cursor, надішліть його в параметрі cursor наступного запиту з тим самим фільтром (from, to, status); null означає останню сторінку. Курсор з іншого фільтра повертає 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);
Читання бронювань повертає заголовок ETag. Надішліть його назад у If-None-Match, і незмінені дані прийдуть як 304 без тіла.
Помилки та коди
Помилка завжди має однакову форму. На код (error.code) можна покладатися, текст (error.message) для людини і може змінитися. Невідомий код оцінюйте за HTTP-статусом.
{
"error": {
"code": "conflict",
"message": "Služba se platí zálohou online; objednejte ji v rezervačním portálu."
}
}
| Код | HTTP | Коли приходить |
|---|---|---|
unauthorized |
401 | Токена немає, він недійсний або анульований, не має дозволу на цю операцію, або компанія вимкнула модуль API (токен компанії). |
bad_request |
400 | Неправильний або відсутній параметр, неправильний формат Idempotency-Key. |
invalid_json |
400 | Тіла запису немає, воно не є дійсним JSON або не є обʼєктом. |
invalid_cursor |
400 | Курсор недійсний або належить іншому фільтру. |
idempotency_key_required |
400 | Перенесення бронювання без заголовка Idempotency-Key. |
not_found |
404 | Запис не існує або токен не має до нього доступу; невідомий шлях. |
conflict |
409 | Час зайнятий, скасувати чи перенести вже пізно, бронювання не можна перенести, компанія призупинена або ще не приймає бронювання, послуга оплачується онлайн-завдатком. |
idempotency_in_progress |
409 | Запит із тим самим Idempotency-Key ще обробляється. |
unsupported_media_type |
415 | Тіло запису не є application/json. |
unprocessable |
422 | Дані правильні, але їх не можна прийняти: послуга не пропонується клієнтові, час поза робочими годинами, зарано або не пропонується для перенесення, працівник чи філія не належать до послуги. |
idempotency_key_reused |
422 | Той самий Idempotency-Key з іншим тілом запиту. |
rate_limited |
429 | Перевищено ліміт запитів. |
method_not_allowed |
405 | Шлях не знає цього методу. |
server_error |
500 | Помилка на нашому боці; у error.details.incident є позначка для підтримки. |
409 для послуги із завдатком. Послугу, за яку компанія бере онлайн-завдаток, API забронювати не може: відповість 409 conflict і нічого не запише. Направте клієнта на портал бронювання компанії, де він сплатить завдаток.
409 повертається також для зайнятого часу (тим часом його взяв хтось інший — завантажте вільний час знову), запізнілого скасування чи перенесення, призупиненої компанії та компанії, яка ще не приймає бронювання.
Чужий запис відповідає як неіснуючий (404), а не 403 — з відповіді не можна дізнатися, що існує.
При 500 server_error повідомте підтримці позначку з error.details.incident. Запис можна повторити з тим самим Idempotency-Key; якщо він уже пройшов, удруге не запишеться.
Ліміти запитів
Кожен токен має 60 балів за ковзну хвилину. Звичайний запит коштує один бал, пошук вільного часу 5 балів, бо він обчислювально складний.
Кожна відповідь містить RateLimit-Limit, RateLimit-Remaining і RateLimit-Reset. Після перевищення приходить 429 rate_limited із заголовком Retry-After: зачекайте вказану кількість секунд і продовжуйте.
HTTP/1.1 429 Too Many Requests
RateLimit-Limit: 60
RateLimit-Remaining: 0
RateLimit-Reset: 17
Retry-After: 17
Запити без дійсного токена рахуються за IP-адресою, щоб підбір токенів не був безкоштовним.
Безпечне повторення (Idempotency-Key)
Записи (POST і DELETE) можна надсилати із заголовком Idempotency-Key — унікальним значенням для однієї операції, наприклад UUID. Якщо зʼєднання обірвалося і ви не знаєте, чи запис пройшов, надішліть той самий запит знову з тим самим ключем: отримаєте збережену відповідь із заголовком Idempotency-Replayed: true, і нічого не запишеться двічі.
- Ключ має від 1 до 100 видимих символів ASCII, розрізняє великі й малі літери і діє 24 години, лише для того самого токена.
- Той самий ключ з іншим тілом повертає 422 idempotency_key_reused, повторення під час обробки 409 idempotency_in_progress.
- Для перенесення бронювання (POST /reservations/{id}/move) заголовок обовʼязковий, без нього прийде 400 idempotency_key_required.
- Зберігається успіх і відмови 409 або 422. Помилкові дані та відмови через поточний стан компанії не зберігаються, тож виправлена або пізніша спроба пройде.
Версії та зміни
Версія інтерфейсу є в адресі (/api/v1). Нові поля, значення переліку або кінцеві точки зʼявляються без нової версії в адресі, тому невідомі поля й значення ігноруйте, а невідомий статус бронювання вважайте завершеним бронюванням, яке не відбулося. Вилучення або зміна значення означали б /api/v2.
Безпека
- Токену не місце в браузері. Код вебсторінки чи застосунку прочитає кожен, хто його відкриє, і з токеном діятиме від вашого імені. Тому API не надсилає заголовків CORS, а браузер заблокує виклик із чужої вебсторінки. Викликайте його зі свого сервера.
- Зберігайте токен як секрет (змінна середовища, сховище паролів), а не у вихідному коді чи репозиторії.
- Для кожної інтеграції видайте окремий токен із влучним описом; у разі витоку анулюєте лише його.
- За підозри на витік негайно анулюйте токен. Анулювання діє одразу.
- Не давайте токен компанії клієнтам — вони видають собі власний токен у своєму обліковому записі.