For developers
API & webhooks.
Data where you need it.
Move bookings into your accounting, add them to your own CRM or build a custom interface. Read through the REST API and hear about every change via webhook - no polling.
Machine-readable specification: openapi.yaml
1. Your first request in five minutes
The API is part of the Business plan. You issue the key yourself in the dashboard under API & webhooks - no need to ask anyone for access.
We show the full key value only once, right after it is created. After that we keep only a fingerprint, so not even we can recover it. If you lose the key, issue a new one and revoke the old one.
You send the key in the Authorization header and it always belongs to one workspace - responses are automatically limited to its data:
curl https://api.eazo.eu/public/v1/me \
-H "Authorization: Bearer eazo_sk_..."
The response tells you which workspace the key belongs to and what it may do. It is the fastest way to check that everything is set up correctly.
Key permissions
When issuing a key you choose between two levels:
- Read only - reads services, staff, customers, bookings and free slots. Accounting needs nothing more.
- Read and write - also creates bookings, moves them and changes their status. For a CRM that also needs to write to the calendar.
An issued key’s permissions never change. If you need more, issue a new key - the old one keeps working meanwhile, so you can swap them without downtime.
2. What the API can do
Everything runs at https://api.eazo.eu/public/v1 and responds with JSON.
GET /me- key check and workspace informationGET /services,GET /services/{id}- service catalogueGET /staff,GET /staff/{id}- staffGET /customers,GET /customers/{id}- customer directoryGET /availability- free slots for a serviceGET /bookings,GET /bookings/{id}- bookingsPOST /bookings- create a bookingPATCH /bookings/{id}- reschedule, internal note, video call linkPOST /bookings/{id}/confirm,/reject,/cancel- status change
Pagination and incremental sync
Lists return data in an envelope with the record count. Page through with the limit (at most 100) and offset parameters:
{
"data": [ … ],
"meta": { "total": 248, "limit": 50, "offset": 0 }
}
For regular syncing use the updatedSince parameter - it returns only records changed since the given moment, so you don’t have to download the whole history every time:
curl "https://api.eazo.eu/public/v1/bookings?updatedSince=2026-09-01T00:00:00%2B02:00&status=confirmed" \
-H "Authorization: Bearer eazo_sk_..."
Times are sent and accepted in ISO 8601 with your workspace’s time zone offset. Identifiers are always UUIDs.
Creating a booking
A booking made through the API is created on behalf of the business - already confirmed, with the same e-mails and calendar entry as if you created it in the dashboard:
curl -X POST https://api.eazo.eu/public/v1/bookings \
-H "Authorization: Bearer eazo_sk_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: objednavka-2026-4711" \
-d '{
"serviceId": "9f1c…",
"date": "2026-09-15",
"time": "14:30",
"name": "Jana Nováková",
"email": "[email protected]",
"phone": "734156239"
}'
The Idempotency-Key header is optional but worth it. If your connection drops while sending, you don’t know whether the booking was created. With the same key, a repeated request within 24 hours returns the same response instead of a second booking - you can tell by the Idempotent-Replay: true header.
If the slot has been taken in the meantime, you get 409 with the code SLOT_UNAVAILABLE. Check free times beforehand with GET /availability - they follow the same rules the customer sees in the widget, including opening hours, holidays, time off and the connected calendar.
3. Webhooks instead of polling
Downloading the booking list every few minutes works, but it is wasteful on both sides. Instead, enter your system’s address as a webhook and we send every selected event to it as soon as it happens.
Set it up in the dashboard under API & webhooks → Add webhook. Choose the address and the events you want to receive:
booking.created- a booking was created (from the widget, the booking page or the API)booking.confirmed- a pending booking was confirmedbooking.rejected- a pending booking was rejectedbooking.cancelled- a confirmed booking was cancelledbooking.rescheduled- the appointment was movedbooking.no_show- the customer did not show up
The request body always has the same envelope; data holds an object in the same shape the REST API returns - so you don’t have to maintain two different mappings:
{
"id": "3f2b…",
"type": "booking.confirmed",
"createdAt": "2026-09-06T11:24:03+02:00",
"workspaceId": "a71c…",
"data": { "booking": { … } }
}
We also send the id value in the Eazo-Event-Id header. Use it to detect repeated deliveries - the same id means the same event, even if it arrives twice.
When your server is not responding
Any 2xx response counts as success. If none arrives, we try delivery six times in total with increasing delays. So respond quickly - acknowledge receipt and do the processing in the background.
If the address stays unresponsive (fifteen times in a row), we turn the webhook off and e-mail you. We don’t keep hammering an address nobody listens to. Every attempt, including your server’s response, is in the dashboard and you can resend an undelivered event from there - with the same body it should have had the first time.
We keep the delivery history for 30 days. Events carry customer contact details, so we don’t want to store them longer than needed.
4. Signature verification
A webhook address is usually public, so anyone can send a request to it. That is why we sign every request with a header:
Eazo-Signature: t=1757155443,v1=9c8f2b…
t is the send time (a Unix timestamp) and v1 is the HMAC-SHA256 of the string "{t}.{request body}", signed with your secret. You get the secret when creating the webhook - like the key, only once.
Verification in PHP looks like this:
function overitPodpis(string $telo, string $hlavicka, string $tajemstvi): bool
{
// split t=…,v1=… into pairs
parse_str(str_replace(',', '&', $hlavicka), $casti);
// Reject old timestamps so the request cannot be replayed later
if (abs(time() - (int) $casti['t']) > 300) {
return false;
}
$ocekavany = hash_hmac('sha256', $casti['t'] . '.' . $telo, $tajemstvi);
// Constant-time comparison - a plain === reveals over time how far the match got
return hash_equals($ocekavany, $casti['v1']);
}
Always compute the signature from the raw request body. If you first parse it into an object and back to JSON, the key order or whitespace may change - and the signature won’t match even though it is valid.
You can rotate the secret in the dashboard at any time. The old one stops working immediately, so update it on your side too.
5. Limits and errors
Each key is limited to 120 requests per minute. The X-RateLimit-Remaining and X-RateLimit-Reset headers tell you how many you have left; when exceeded you get 429 with a Retry-After header.
Errors always have the same shape - a machine-readable code and an explanation for humans. It is in English, or in Czech with the Accept-Language: cs header:
{
"error": "VALIDATION_ERROR",
"message": "Some of the data is invalid.",
"fields": { "email": "Enter a valid e-mail address." }
}
The ones you will meet most often:
UNAUTHORIZED(401) - the key is missing or invalidPLAN_REQUIRED(403) - the workspace is not on the Business planINSUFFICIENT_SCOPE(403) - the key is read-onlyRESOURCE_NOT_FOUND(404) - the resource does not exist in the key’s workspaceSLOT_UNAVAILABLE(409) - the slot is takenINVALID_STATE(409) - the booking is not in a state that allows the operationRATE_LIMITED(429) - request limit exhausted
A key reaching for another workspace gets 404, not 403. If they differed, the API could be used to find out what competitors have in the system.
6. Machine-readable specification
The whole interface is described by the openapi.yaml file in OpenAPI 3.1 format. Generate a client in your language, import it into Postman or build tests from it.
We extend the contract, we don’t change it: we never rename or remove response keys or event names, we only add new ones. An integration that works today won’t break after our deployment.
Frequently asked questions
Which plan includes the API?
The Business plan. You issue the key yourself in the dashboard under API & webhooks - no approval or access request needed.
Do I have to keep polling for new bookings?
No. Enter your system’s address as a webhook and Eazo sends every selected event to it as soon as it happens. Regular polling becomes unnecessary.
How do I know a webhook really came from Eazo?
Every request carries an Eazo-Signature header with a timestamp and an HMAC-SHA256 signature of the body and your secret. Compute the same and compare - there is an example a little higher up.
What happens if my server is not responding?
We retry delivery six times with increasing delays. If the address stays unresponsive, we turn the webhook off and e-mail you. Every attempt, including the server response, is in the dashboard and the event can be resent.
Can I create bookings through the API too?
Yes, if you give the key write access. A booking created through the API behaves just like one made in the dashboard - e-mails go out, it is written to the connected calendar and webhooks fire.