REST API for connecting your system to Zuboklik
Complete partner API documentation: authentication, patient and appointment endpoints, webhook registration, and HMAC signature verification.
# Create a new patient
curl -X POST https://app.zuboklik.cz/api/v1/patients \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"patient": {
"first_name": "John",
"last_name": "Doe",
"phone_number": "+420777111222",
"pin": "9001011234"
}
}'Office authentication
Every office that enables the API has a single master API key. You receive the key directly from the doctor — the doctor generates it in the office settings and passes it into your application.
How to obtain the API key
- 1
The doctor generates the key in office settings
In Zuboklik, go to Office settings → Connections → API key. The doctor generates or reveals the key and copies it.
- 2
The doctor passes the key to your application
Paste the key into your system's settings (practice management software or custom app). Zuboklik does not send the key automatically — the doctor passes it manually.
- 3
Store the API key securely
The key is shared once. Keep it in a secrets manager (Vault, AWS Secrets Manager, Keychain). Never store it in a git repository.
# The office API key is sent in a header # Authorization: ApiKey <office_api_key> # alternative: X-Api-Key: <office_api_key> # It is used in only two scenarios: # 1) registering a new ConnectedSystem # 2) disconnecting (DELETE) a ConnectedSystem # # Everywhere else (read/write patients, appointments, # webhook management) use your ConnectedSystem's # Bearer token, see the next section.
Two types of keys, do not confuse them
The office API key (ApiKey) proves ownership of the office. Your ConnectedSystem's Bearer token proves you are already connected. They are different things — in production traffic you will typically use only the Bearer token; the ApiKey is only for bootstrap and disconnect.
Connecting your system
After obtaining the office API key, register your system with a single HTTP request. In the response you receive a Bearer token that you will use for all further communication.
Register a new system
The name of your system must be unique within the office. It is used for identification in the audit log and for visualisation in the Zuboklik admin.
Request headers
Request body
Response 201 Created
save the api_key: The API key in the response is shown only once. Store it in your secrets manager immediately — only its SHA-256 digest is kept in the database. If you lose the key, you must create a new ConnectedSystem.
Disconnect the system
The system is disconnected with a single request. After disconnection, its Bearer token no longer works and cannot be restored — to reconnect, you must create a new ConnectedSystem.
Request headers
Optionally, you can add a reason as a query parameter: ?reason=migration-to-v2. The reason is stored in the office's audit log.
Security check: DELETE requires both your system's Bearer token and the office API key. This ensures that an attacker with a stolen Bearer token cannot disconnect a different office's system.
Patient management
CRUD endpoints for patients. All operations are automatically scoped to the office that owns the Bearer token. Patients from other offices are not accessible.
Returns active patients of the office (deactivated ones are excluded), sorted by last name and first name. Default pagination is 25 patients per page, maximum 100.
Example request
Example 200 OK response
Returns a single active patient by their relative ID in the office.
Example request
Example 200 OK response
Creates a new patient. The PIN is used for login in Zuboklik. If a patient with the same PIN already exists in the office, the API returns a 422 error.
Example request
Example 201 Created response
Updates selected attributes of a patient. Attributes that are not sent remain unchanged.
Example request
Example 200 OK response
The patient is not physically deleted (deletion would violate statutory retention obligations) — only deactivated_at and deactivate_reason are set. The patient no longer appears in lists and cannot be used for new appointments.
Example request
Response
Appointment management (Entry)
Appointments in Zuboklik are called Entry. Each entry has a type (EntryType), a state (EntryState) and optionally a link to a patient via patient_symptoms.
Returns the office's entries, newest first. Vacation entries are skipped — those only serve to internally block the calendar.
Example request
Paginated 200 OK response
Returns a single entry including patient_symptoms.
Example request
200 OK response
Creates a new entry for the office. If the entry requires a patient link, the request must include patient_symptoms_attributes with patient_id and symptom_id. The patient must exist in the same office.
Example request
201 Created response
Updates selected attributes. If patient_symptoms_attributes is in the body, existing links are replaced by the new ones.
Example request
200 OK response
Physically removes the entry from the database. This operation is irreversible — if you only want to cancel the appointment, change entry_state_id to the appropriate state (e.g. canceled).
Example request
Response
Full Entry object example
This is the full response structure returned by GET /api/v1/entries/:id and POST /api/v1/entries.
Symptom list
Symptoms are a global catalogue. Each symptom has an ID, code and localized name. When creating an entry via the API, use the `symptom_id` from this list inside `patient_symptoms_attributes`.
Returns all available symptoms ordered by type (acute primary, acute, chronic, hygiene, no complaints) and ID. Names are localized according to the request language.
Example request
Example 200 OK response
Real-time change notifications
Webhooks let you react to changes in Zuboklik in real time — no periodic polling. Every CRUD on an appointment or patient of your office POSTs to your endpoint with an HMAC signature.
1. Register your URL
POST to /api/v1/webhooks with your endpoint URL. You receive a one-time HMAC secret in the response.
2. Verify the signature
Always verify the X-Zuboklik-Signature header against the request body and X-Zuboklik-Timestamp.
3. Respond quickly
Your endpoint must respond 2xx within 10 seconds. If the response is slow, delivery is retried.
Register a new webhook
The URL must be https and must not point to a private IP address or a cloud metadata service (SSRF protection). Maximum URL length is 2048 characters.
Request
201 Created response
save the secret: The HMAC secret is returned only once. Store it in your secrets manager immediately. Zuboklik only keeps its SHA-256 digest. If you lose it, you must create a new webhook (with a new secret).
Payload format
The payload is intentionally minimal: only the ID and type of the event. To get details, call back into the relevant endpoint.
Why is the payload thin? If the webhook URL is compromised, only IDs and types leak. Full data can be retrieved only if the attacker also knows your Bearer token.
Delivery HTTP headers
| Header | Example | Meaning |
|---|---|---|
| X-Zuboklik-Signature | a1b2c3d4…(64 hex chars) | HMAC-SHA256(payload, secret) computed over the request body and X-Zuboklik-Timestamp. |
| X-Zuboklik-Timestamp | 1752670800 | Unix timestamp in seconds at delivery time. Used to protect against replay attacks. |
| X-Zuboklik-Delivery | f1e2d3c4-… | Unique delivery ID, suitable for deduplication on your side. |
| X-Zuboklik-Event | patient_created | Event type (same value as event in the payload body). |
HMAC signature verification
Every webhook is signed with HMAC-SHA256. The signature is computed from the request body and the timestamp. The goal is two-factor verification: (1) you share a secret with us, (2) every message is signed uniquely.
Algorithm
The signature is computed over a string composed of the request body and the timestamp. Both sides must therefore compute from the same inputs — otherwise the signature does not match.
- Take the raw request body (not the parsed JSON).
- Append X-Zuboklik-Timestamp separated by a dot: {body}.{timestamp}.
- Compute HMAC-SHA256 with your webhook secret.
- Compare the result (hex) to the X-Zuboklik-Signature header — they must match bit-for-bit.
- Optionally check that X-Zuboklik-Timestamp is no older than 5 minutes (replay protection).
require 'openssl'
def verify(body, timestamp, signature, secret)
expected = OpenSSL::HMAC.hexdigest(
'SHA256',
secret,
"#{body}.#{timestamp}"
)
ActiveSupport::SecurityUtils.secure_compare(expected, signature)
end
# In your controller (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
# # ... process payload ...
# endReplay attack protection
When verifying the signature, always check that X-Zuboklik-Timestamp is no older than 5 minutes (or another reasonable limit for your use case). This prevents an attacker from re-sending a captured message.
Frequently asked questions
Practical answers to the questions that partners typically have when connecting for the first time.
Yes — create a test office through the standard registration form (Wizards::Office) and grab the API key from the settings. All operations are isolated at the office level, so you don't have to worry about side effects.
The API allows 600 requests per minute per ConnectedSystem. When exceeded, you receive HTTP 429 Too Many Requests with a Retry-After header. We recommend implementing exponential backoff.
The API returns JSON with an errors array containing the attribute name and a human-readable message. Display these messages to the end user of your system — they will receive clear feedback.
No. The webhook URL must be publicly reachable and point to an https endpoint. This restriction protects against SSRF attacks on Zuboklik's internal infrastructure.
Contact us immediately at technicka@zuboklik.cz. We invalidate the key, all your ConnectedSystems get disconnected and you will need to register new ones. Old keys will never work again.
Yes, in production we require HTTPS exclusively. HTTP requests receive a 426 Upgrade Required response. For local development, you can use a reverse proxy (e.g. ngrok).
Want to connect your system?
If you are interested in the partner API, get in touch. We will discuss your needs, help you set up a test office, and help with the integration.