API v1

REST API pro napojení vašeho systému na Zuboklik

Kompletní dokumentace partnerského API: autentizace, endpointy pro pacienty a objednání, registrace webhooků a ověření HMAC podpisu.

verifiedAPI klíč + Bearer
lockHMAC-SHA256 podpis
boltJSON přes HTTPS
languageVerze v1
curl
# Vytvoření nového pacienta
curl -X POST https://app.zuboklik.cz/api/v1/patients \
  -H 'Authorization: Bearer VÁŠ_API_KLÍČ' \
  -H 'Content-Type: application/json' \
  -d '{
    "patient": {
      "first_name": "Jan",
      "last_name": "Novák",
      "phone_number": "+420777111222",
      "pin": "9001011234"
    }
  }'
Krok 1

Autentizace ordinace

Každá ordinace, která chce zpřístupnit API, dostane od Zubokliku jeden hlavní API klíč. Ten slouží k registraci vašeho napojeného systému a k ověření identity ordinace.

Jak získat API klíč

  1. 1

    Kontaktujte Zuboklik

    Napište na technicka@zuboklik.cz — ověříme, že vaše řešení splňuje naše partnerské podmínky, a nasdílíme vám API klíč ordinace.

  2. 2

    API klíč uložte bezpečně

    Klíč obdržíte jednorázově při registraci. Uchovávejte jej v secrets manageru (Vault, AWS Secrets Manager, Keychain). Nikdy jej neukládejte do git repozitáře.

  3. 3

    Klíč v produkci pravidelně rotujte

    Při podezření na kompromitaci kontaktujte Zuboklik — klíč zneplatníme a vygenerujeme nový. Doporučujeme rotaci alespoň jednou za 12 měsíců.

Authorization: ApiKey
# API klíč ordinace se posílá v hlavičce
# Authorization: ApiKey <office_api_key>
# alternativně: X-Api-Key: <office_api_key>

# Používá se pouze ve dvou scénářích:
# 1) registrace nového ConnectedSystem
# 2) odpojení (DELETE) ConnectedSystem
#
# Všude jinde (čtení/zápis pacientů, objednání,
# správa webhooků) použijte Bearer token vašeho
# ConnectedSystemu, viz další sekce.
warning

Dva typy klíčů, nepleťte si je

API klíč ordinace (ApiKey) prokazuje vlastnictví ordinace. Bearer token vašeho ConnectedSystemu prokazuje, že jste již napojeni. Jsou to dvě různé věci — v produkční komunikaci budete typicky používat jen Bearer token, ApiKey slouží jen k bootstrapu a odpojení.

Krok 2

Připojení vašeho systému

Po získání API klíče ordinace zaregistrujte svůj systém jedním HTTP požadavkem. V odpovědi dostanete Bearer token, který budete používat pro veškerou další komunikaci.

POST/api/v1/connected_systems

Registrace nového systému

Jméno vašeho systému musí být unikátní v rámci ordinace. Slouží k identifikaci v audit logu a k vizualizaci v administraci Zubokliku.

Hlavičky požadavku

Authorization: ApiKey <office_api_key>
Content-Type: application/json

Tělo požadavku

{ "name": "nazev-vaseho-systemu" }

Odpověď 201 Created

{
"connected_system": {
"id": 42,
"name": "nazev-vaseho-systemu",
"status": "active",
"office_code": "ABC123",
"connected_at": "2026-07-16T14:00:00.000+02:00"
},
"api_key": "f7c3a1b9e2...",
"message": "Napojený systém byl zaregistrován."
}

api_key si uložte: API klíč v odpovědi dostanete pouze jednou. Ihned jej uložte do secrets manageru — v databázi je uložen pouze jeho SHA-256 otisk. Při ztrátě klíče je nutné vytvořit nový ConnectedSystem.

DELETE/api/v1/connected_systems

Odpojení systému

Systém se odpojí jedním požadavkem. Po odpojení už jeho Bearer token nefunguje a nelze jej obnovit — pro opětovné napojení je nutné vytvořit nový ConnectedSystem.

Hlavičky požadavku

Authorization: Bearer <system_api_key>
X-Api-Key: <office_api_key>

Volitelně můžete do query přidat důvod: ?reason=migrace-na-v2. Důvod se uloží do audit logu ordinace.

Bezpečnostní kontrola: DELETE vyžaduje jak Bearer token vašeho systému, tak API klíč ordinace. Tím je zajištěno, že útočník s ukradeným Bearer tokenem nemůže odpojit systém jiné ordinace.

Endpointy

Správa pacientů

CRUD endpointy pro pacienty. Všechny operace jsou automaticky scopovány na ordinaci, která vlastní Bearer token. Pacienti jiných ordinací nejsou přístupní.

GET/api/v1/patients
Seznam pacientů

Vrátí aktivní pacienty ordinace (deaktivovaní se nezahrnují), seřazené podle příjmení a jména. Stránkování je výchozí 25 pacientů na stránku, maximálně 100.

Příklad požadavku
GET /api/v1/patients?page=1&per_page=50 HTTP/1.1 Host: app.zuboklik.cz Authorization: Bearer <system_api_key>
Příklad odpovědi 200 OK
{ "patients": [ { "id": 4711, "office_code": "ABC123", "zubo_cislo": null, "pin": "9001011234", "first_name": "Jan", "last_name": "Novák", "degree": null, "phone_number": "+420777111222", "email": "jan@example.cz", "gender": "male", "number": null, "booking_disabled": false, "deactivated": false, "deactivated_at": null, "created_at": "2026-07-01T10:00:00.000+02:00", "updated_at": "2026-07-15T14:00:00.000+02:00" } ], "meta": { "current_page": 1, "per_page": 50, "total_entries": 1, "total_pages": 1 } }
GET/api/v1/patients/:id
Detail pacienta

Vrátí jednoho aktivního pacienta podle relativního ID v ordinaci.

Příklad požadavku
GET /api/v1/patients/4711 HTTP/1.1 Host: app.zuboklik.cz Authorization: Bearer <system_api_key>
Příklad odpovědi 200 OK
{ "patient": { "id": 4711, "first_name": "Jan", "last_name": "Novák", "pin": "9001011234", "phone_number": "+420777111222", "deactivated": false } }
POST/api/v1/patients
Vytvoření pacienta

Vytvoří nového pacienta. PIN slouží k přihlášení v Zubokliku. Pokud pacient se stejným PINem v ordinaci již existuje, vrátí API chybu 422.

Příklad požadavku
POST /api/v1/patients HTTP/1.1 Host: app.zuboklik.cz Authorization: Bearer <system_api_key> Content-Type: application/json { "patient": { "first_name": "Jan", "last_name": "Novák", "pin": "9001011234", "phone_number": "+420777111222", "email": "jan@example.cz", "gender": "male" } }
Příklad odpovědi 201 Created
{ "patient": { "id": 4712, "first_name": "Jan", "last_name": "Novák", "pin": "9001011234", "phone_number": "+420777111222", "deactivated": false, "created_at": "2026-07-16T14:00:00.000+02:00" } }
PATCH/api/v1/patients/:id
Aktualizace pacienta

Aktualizuje vybrané atributy pacienta. Neposlané atributy zůstanou nezměněné.

Příklad požadavku
PATCH /api/v1/patients/4711 HTTP/1.1 Host: app.zuboklik.cz Authorization: Bearer <system_api_key> Content-Type: application/json { "patient": { "phone_number": "+420777333444", "email": "jan.novak@example.cz" } }
Příklad odpovědi 200 OK
{ "patient": { "id": 4711, "first_name": "Jan", "last_name": "Novák", "phone_number": "+420777333444", "email": "jan.novak@example.cz" } }
DELETE/api/v1/patients/:id
Deaktivace pacienta

Pacient se fyzicky neodstraní (smazání by porušilo zákonem danou retenční povinnost), pouze se nastaví deactivated_at a deactivate_reason. Pacient se přestane zobrazovat v seznamech a nelze jej znovu použít pro nové objednání.

Příklad požadavku
DELETE /api/v1/patients/4711?reason=duplicitni-zaznam HTTP/1.1 Host: app.zuboklik.cz Authorization: Bearer <system_api_key>
Odpověď
HTTP/1.1 204 No Content
Endpointy

Správa objednání (Entry)

Objednání v Zubokliku se nazývají Entry. Každé objednání má typ (EntryType), stav (EntryState) a volitelně vazbu na pacienta přes patient_symptoms.

GET/api/v1/entries
Seznam objednání

Vrátí objednání ordinace, nejnovější první. Dovolené (vacation) záznamy jsou vynechány — ty slouží jen internímu blokování kalendáře.

Příklad požadavku
GET /api/v1/entries?page=1 HTTP/1.1 Host: app.zuboklik.cz Authorization: Bearer <system_api_key>
Stránkovaná odpověď 200 OK
{ "entries": [ /* … */ ], "meta": { "current_page": 1, "per_page": 25, "total_entries": 1, "total_pages": 1 } }
GET/api/v1/entries/:id
Detail objednání

Vrátí jedno objednání včetně patient_symptoms.

Příklad požadavku
GET /api/v1/entries/8123 HTTP/1.1 Host: app.zuboklik.cz Authorization: Bearer <system_api_key>
Odpověď 200 OK
{ "entry": { /* viz ukázka níže */ } }
POST/api/v1/entries
Vytvoření objednání

Vytvoří nové objednání pro ordinaci. Pokud entry vyžaduje vazbu na pacienta, je nutné dodat pole patient_symptoms_attributes s patient_id a symptom_id. Pacient musí existovat v téže ordinaci.

Příklad požadavku
POST /api/v1/entries HTTP/1.1 Host: app.zuboklik.cz Authorization: Bearer <system_api_key> Content-Type: application/json { "entry": { "entry_type_id": 1, "entry_state_id": 1, "booked_for": "2026-07-20", "start_time": "2026-07-20T09:00:00.000+02:00", "end_time": "2026-07-20T09:30:00.000+02:00", "note": "Kontrola po ošetření", "patient_symptoms_attributes": [ { "patient_id": 4711, "symptom_id": 12 } ] } }
Odpověď 201 Created
{ "entry": { /* … entry.create! */ } }
PATCH/api/v1/entries/:id
Aktualizace objednání

Aktualizuje vybrané atributy. Pokud je v těle patient_symptoms_attributes, stávající vazby se nahradí novými.

Příklad požadavku
PATCH /api/v1/entries/8123 HTTP/1.1 Host: app.zuboklik.cz Authorization: Bearer <system_api_key> Content-Type: application/json { "entry": { "start_time": "2026-07-20T09:30:00.000+02:00", "end_time": "2026-07-20T10:00:00.000+02:00" } }
Odpověď 200 OK
{ "entry": { /* aktualizovaný entry */ } }
DELETE/api/v1/entries/:id
Smazání objednání

Fyzicky odstraní objednání z databáze. Tato operace je nevratná — pokud chcete objednání pouze zrušit, změňte entry_state_id na odpovídající stav (např. canceled).

Příklad požadavku
DELETE /api/v1/entries/8123 HTTP/1.1 Host: app.zuboklik.cz Authorization: Bearer <system_api_key>
Odpověď
HTTP/1.1 204 No Content

Ukázka kompletního Entry objektu

Toto je plná struktura odpovědi, kterou vrací GET /api/v1/entries/:id a POST /api/v1/entries.

{ "entry": { "id": 8123, "office_id": 42, "entry_type_id": 1, "entry_state_id": 1, "booked_for": "2026-07-20", "start_time": "2026-07-20T09:00:00.000+02:00", "end_time": "2026-07-20T09:30:00.000+02:00", "to_be_paid": false, "note": "Kontrola po ošetření", "patient_symptoms": [ { "id": 998, "patient_id": 4711, "symptom_id": 12 } ], "created_at": "2026-07-16T14:00:00.000+02:00", "updated_at": "2026-07-16T14:00:00.000+02:00" } }
Webhooky

Real-time notifikace o změnách

Webhooky vám umožní reagovat na změny v Zubokliku v reálném čase — žádné periodické polling. Každý CRUD nad objednáním nebo pacientem vaší ordinace zašle POST na váš endpoint s HMAC podpisem.

add_link

1. Zaregistrujte URL

POST na /api/v1/webhooks s URL vašeho endpointu. Dostanete zpět jednorázově HMAC secret.

verified_user

2. Ověřte podpis

Vždy ověřte hlavičku X-Zuboklik-Signature proti tělu požadavku a X-Zuboklik-Timestamp.

sync

3. Odpovězte rychle

Váš endpoint musí odpovědět 2xx do 10 sekund. Při pomalé odpovědi se doručení opakuje.

POST/api/v1/webhooks

Registrace nového webhooku

URL musí být https a nesmí směřovat na privátní IP adresu ani na metadata služby cloudů (SSRF ochrana). Maximální délka URL je 2048 znaků.

Požadavek
POST /api/v1/webhooks HTTP/1.1 Host: app.zuboklik.cz Authorization: Bearer <system_api_key> Content-Type: application/json { "webhook": { "url": "https://..." } }
Odpověď 201 Created
{
"webhook": {
"id": 7,
"url": "https://...",
"status": "active",
"failure_count": 0
},
"secret": "a1b2c3d4e5...",
"message": "Webhook byl zaregistrován."
}

secret si uložte: HMAC secret dostanete v odpovědi pouze jednou. Ihned jej uložte do secrets manageru. Zuboklik ukládá jen SHA-256 otisk. Při ztrátě je nutné vytvořit nový webhook (nový secret).

Formát payloadu

Payload je záměrně minimální: pouze ID a typ události. Pro získání detailů zavolejte zpětně GET na příslušný endpoint.

{ "event": "patient_created", "action": "create", "resource_type": "Patient", "resource_id": 4711, "office_code": "ABC123", "occurred_at": "2026-07-16T14:00:00.000+02:00" }

Proč je payload tenký? V případě kompromitace webhook URL uniknou jen ID a typy. Plná data získáte jen tehdy, pokud útočník zároveň zná váš Bearer token.

HTTP hlavičky doručení

HlavičkaPříkladVýznam
X-Zuboklik-Signaturea1b2c3d4…(64 hex znaků)HMAC-SHA256(payload, secret) počítaný vůči tělu požadavku a X-Zuboklik-Timestamp.
X-Zuboklik-Timestamp1752670800Unix timestamp v sekundách v době doručení. Slouží k ochraně proti replay útokům.
X-Zuboklik-Deliveryf1e2d3c4-…Unikátní ID doručení, vhodné pro deduplikaci na vaší straně.
X-Zuboklik-Eventpatient_createdTyp události (stejná hodnota jako event v těle payloadu).
Bezpečnost

Ověření HMAC podpisu

Každý webhook je podepsaný HMAC-SHA256. Podpis se počítá z těla požadavku a timestampu. Cílem je dvoufaktorové ověření: (1) sdílíte s námi secret, (2) každá zpráva je opatřena jedinečným podpisem.

Algoritmus

Podpis se počítá nad řetězcem složeným z těla požadavku a timestampu. Obě strany tak musí počítat ze stejných vstupních dat — v opačném případě podpis nesedí.

  1. 1
    Vezměte surové tělo požadavku (raw body, ne parsovaný JSON).
  2. 2
    Připojte k němu X-Zuboklik-Timestamp oddělený tečkou: {body}.{timestamp}.
  3. 3
    Spočítejte HMAC-SHA256 s vaším webhook secretem.
  4. 4
    Porovnejte výsledek (hex) s hlavičkou X-Zuboklik-Signature — musí se shodovat bit po bitu.
  5. 5
    Volitelně zkontrolujte, že X-Zuboklik-Timestamp není starší než 5 minut (ochrana proti replay).
Ruby
require 'openssl'

def verify(body, timestamp, signature, secret)
  expected = OpenSSL::HMAC.hexdigest(
    'SHA256',
    secret,
    "#{body}.#{timestamp}"
  )
  ActiveSupport::SecurityUtils.secure_compare(expected, signature)
end

# V controlleru (Rails):
# def webhook
#   body = request.raw_post
#   ts   = request.headers['X-Zuboklik-Timestamp']
#   sig  = request.headers['X-Zuboklik-Signature']
#   ok   = verify(body, ts, sig, ENV['ZUBOKLIK_WEBHOOK_SECRET'])
#   head :unauthorized unless ok
#   # ... zpracování payloadu ...
# end
schedule

Ochrana proti replay útokům

Při ověření podpisu vždy zkontrolujte, že X-Zuboklik-Timestamp není starší než 5 minut (případně jiný rozumný limit podle vašeho use-case). Tím zabráníte tomu, aby útočník mohl zachycenou zprávu odeslat znovu.

Časté dotazy

Praktické odpovědi na otázky, které typicky řeší partneři při prvním napojení.

Chcete napojit svůj systém?

Pokud máte zájem o partnerské API, napište nám. Probereme vaše potřeby, nasdílíme vám API klíč pro vaši testovací ordinaci a pomůžeme s integrací.

Objednat se