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, má svůj hlavní API klíč. Klíč získáte přímo od doktora — doktor ho vygeneruje v nastavení ordinace a předá ho do vaší aplikace.

Jak získat API klíč

  1. 1

    Doktor vygeneruje klíč v nastavení ordinace

    V Zubokliku přejděte do Nastavení ordinace → Propojení → API klíč. Doktor tam klíč vygeneruje, zobrazí a zkopíruje.

  2. 2

    Doktor předá klíč do vaší aplikace

    Klíč vložíte do nastavení svého systému (ambulantního softwaru nebo vlastní aplikace). Zuboklik klíč neposílá automaticky — předává ho sám doktor.

  3. 3

    API klíč uložte bezpečně

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

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" } }
Endpointy

Seznam symptomů

Symptomy jsou globální číselník. Každý symptom má své ID, kód a lokalizovaný název. Při vytváření objednávky přes API použijte `symptom_id` z tohoto seznamu uvnitř `patient_symptoms_attributes`.

GET/api/v1/symptoms
Seznam symptomů

Vrátí všechny dostupné symptomy seřazené podle typu (akutní primární, akutní, chronické, hygienické, bez potíží) a ID. Názvy se lokalizují podle jazyka požadavku.

Příklad požadavku
GET /api/v1/symptoms HTTP/1.1 Host: app.zuboklik.cz Authorization: Bearer <system_api_key>
Příklad odpovědi 200 OK
{ "symptoms": [ { "id": 1, "code": "big_pain", "name": "Silná bolest", "symptom_type_code": "primary" }, { "id": 2, "code": "small_pain", "name": "Malá bolest", "symptom_type_code": "secondary" } ] }
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. Vezměte surové tělo požadavku (raw body, ne parsovaný JSON).
  2. Připojte k němu X-Zuboklik-Timestamp oddělený tečkou: {body}.{timestamp}.
  3. Spočítejte HMAC-SHA256 s vaším webhook secretem.
  4. Porovnejte výsledek (hex) s hlavičkou X-Zuboklik-Signature — musí se shodovat bit po bitu.
  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

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, pomůžeme vám s nastavením testovací ordinace a s integrací.

Objednat se