The YONA API for business
A salon can connect its own software to YONA — 1C, its own CRM, a bot, a website. There are two halves: you ask us (REST requests), and we call you when something happens (webhooks). Polling every minute is neither needed nor wise: subscribing to events is free and spends none of your request allowance.
Who has it
The API and webhooks come with the PRO and ENTERPRISE plans. A trial gets a sandbox: a key is issued, requests work at a low limit, the test button sends, but real bookings are not pushed out. SOLO does not have the API.
The key
The owner of the salon issues it under «Интеграции». It is shown once — we keep only a fingerprint and cannot show it again. A key carries rights: give it exactly the ones your program needs. A revoked key stops working from the next request.
How to call
Every route begins with /api/v1. The key travels in the Authorization header. To check a key, GET /api/v1/me — it answers with your salon, the key's rights and its limit.
curl -H "Authorization: Bearer yona_ab12cd34_…" \
https://yona.uz/api/v1/servicesLimits
PRO — 120 requests a minute per key, ENTERPRISE — 600, a trial — 20. Above that the answer is 429 with a Retry-After header. There is no metered billing: we do not charge per call.
Routes
| HTTP | Right | What it does |
|---|---|---|
| GET /api/v1/me | catalog:read | The salon, the key's rights and its limit |
| GET /api/v1/services | catalog:read | Services with price and duration (?archived=1 includes archived ones) |
| GET /api/v1/masters | catalog:read | Staff, and which of them take bookings |
| GET /api/v1/slots?serviceId&date&masterId | schedule:read | Free time on a salon day (YYYY-MM-DD) |
| GET /api/v1/bookings?from&to&status&masterId&customerId&limit&cursor | bookings:read | Bookings, paged |
| GET /api/v1/bookings/{id} | bookings:read | One booking |
| POST /api/v1/bookings | bookings:write | Make a booking: serviceId, startTime and either customerId or customerName with customerPhone |
| POST /api/v1/bookings/{id}/cancel | bookings:write | Cancel a booking |
| GET /api/v1/customers?limit&cursor | customers:read | The salon's customers, paged |
Webhooks
The owner adds the address in the same place. We send a POST with a JSON body. Answer 2xx as quickly as you can: we do not read your body, and the work belongs in your own queue. The address must be https and reachable from the internet — we do not connect into private networks and we do not follow redirects.
Events
booking.created— A booking was madebooking.confirmed— A booking was confirmedbooking.changed— A booking was movedbooking.cancelled— A booking was cancelledbooking.completed— A visit was finished
The signature
Every push is signed with your secret. X-Yona-Signature carries t=<time>,v1=<signature>, the signature being HMAC-SHA256 over «time.body». Check it, and refuse anything older than five minutes — without that, a push can be replayed by somebody else.
Not checking the signature means taking bookings from anyone who learns your address.
const signature = request.headers['x-yona-signature']; // t=1758297600,v1=…
const [, timestamp, given] = /^t=(\d+),v1=([0-9a-f]+)$/.exec(signature) ?? [];
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return reject();
const expected = crypto
.createHmac('sha256', YOUR_WEBHOOK_SECRET)
.update(timestamp + '.' + rawBody) // the raw body, before JSON.parse
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(given, 'hex'))) return reject();Retries
If you answer anything but 2xx, or do not answer, we try again up to six times with a growing pause. The same event may reach you twice — key on the X-Yona-Delivery header and treat a repeat as already done. An address that fails fifteen deliveries in a row is switched off, and the screen's journal says so.
Refusals
An error answer is JSON with `error` and `code`. Branch on `code`: the `error` sentence is written for a person reading a log and may change.
When something is wrong
The delivery journal on the «Интеграции» screen shows every push: when, which event, what your server answered, and a button to send it again. Start there.