The server owns the money
No endpoint accepts an amount. Send a coupon code, a package id or a gift card; the total is computed server-side every time and the client is only ever told the result.
API reference
kavita/v1.Twenty-three routes, eleven PHP actions and seven webhook events. Public routes are nonce-protected and rate-limited; anything that writes checks a capability against the caller rather than trusting the request.
REST
Base URL /wp-json/kavita/v1. The server recomputes every
amount — a request may name a coupon, a package or a card, never a price.
| Route | What it does |
|---|---|
GET /catalog | Services, categories, extras, providers and locations — everything the wizard renders. |
GET /slots | Free slots for a service, provider and date range, in the customer's timezone. |
GET /find | Soonest available, with day and time-of-day filters. |
GET /events | Published events, tickets remaining and early-bird windows. |
GET /extras | Add-on services attached to a service. |
GET /nonce | A fresh nonce for the page that is about to post. |
POST /order | Create a booking. Money is recomputed server-side; the client only names a coupon. |
POST /verify | Verify a gateway's signed return and mark the order paid. |
POST /balance | Take an outstanding balance against an existing order. |
POST /balance/verify | Verify that balance payment. |
POST /coupon | Validate a coupon against a cart. Never returns a price the server did not compute. |
POST /giftcard | Check a gift-card balance. |
POST /giftcard-order | Buy a gift card. |
POST /package-order | Buy a package or membership. |
POST /event-order | Buy event tickets, with named attendees. |
POST /course | Book a recurring course of treatment in one call. |
POST /credit | Apply prepaid credit to an order. |
POST /waitlist | Join a waiting list, or claim an offered slot. |
POST /resume | Resume an abandoned booking from its recovery link. |
GET /lastbooking | The caller's most recent booking, for the manage page. |
POST /agent | The booking assistant. Returns tool-verified values only. |
POST /copilot | The admin copilot. Capability is checked per tool against the caller. |
POST /whatsapp/inbound | Twilio's inbound webhook. Signature-verified, and it stores no message body. |
Front-end routes take a
WordPress nonce from /nonce, tied to the page that requested it. Server-to-server
callers on the Practice plan use an API key in a header. There is no route that writes without
one or the other.
# fetch a nonce, then post an order
curl https://your-site.com/wp-json/kavita/v1/nonce
curl -X POST https://your-site.com/wp-json/kavita/v1/order \
-H 'X-WP-Nonce: <nonce>' \
-H 'Content-Type: application/json' \
-d '{"service":12,"provider":3,"start":"2026-09-04T10:00:00+05:30",
"name":"Meera S.","email":"m@example.com","coupon":"FIRST10"}'
Every delivery carries a timestamp and an HMAC over the raw body. Compare in constant time, and reject anything older than five minutes so a captured request cannot be replayed at you later.
# headers on every delivery
X-Kavita-Event: booking.paid
X-Kavita-Timestamp: 1788350400
X-Kavita-Signature: sha256=<hex>
# verify
$expected = hash_hmac('sha256', $timestamp . '.' . $raw_body, $secret);
hash_equals($expected, $signature); // constant time
Webhooks
| Event | Fires with |
|---|---|
booking.created | kavita_order_created |
booking.paid | kavita_order_paid |
booking.cancelled | kavita_order_cancelled |
booking.rescheduled | kavita_appointment_rescheduled |
booking.reassigned | kavita_appointment_reassigned |
booking.attendance | kavita_attendance_set |
booking.refunded | kavita_order_refunded |
PHP
kavita_order_createdkavita_order_paidkavita_order_settledkavita_order_cancelledkavita_order_refundedkavita_appointment_rescheduledkavita_appointment_reassignedkavita_attendance_setkavita_loyalty_issuedkavita_bookingskavita_managePlus filters for the licence endpoint, the slot
query, notification templates and the assistant's tool list. Every hook name is prefixed
kavita_, because a plugin that squats on a bare name deserves what it gets.
Rules we hold ourselves to
No endpoint accepts an amount. Send a coupon code, a package id or a gift card; the total is computed server-side every time and the client is only ever told the result.
Every write checks a capability against the person or key making the call. The copilot uses the same check, which is why it can never exceed the menu its operator already has.
kavita/v1 keeps its
shape. Anything that would break a caller lands as v2 with both running, not as a
quiet change on a Tuesday.
The wizard itself is the biggest consumer of these routes. There is no private API doing the real work behind a public one that is merely tolerated.
Design reference. This one answers from a fixed list, with no model behind it. The real agent will call our own endpoint — never OpenAI directly, because a key in this page is a key anyone can read.