We understand selling. The software does the running. My accountContactDocs

API reference

Namespace 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

Routes.

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.

RouteWhat it does
GET /catalogServices, categories, extras, providers and locations — everything the wizard renders.
GET /slotsFree slots for a service, provider and date range, in the customer's timezone.
GET /findSoonest available, with day and time-of-day filters.
GET /eventsPublished events, tickets remaining and early-bird windows.
GET /extrasAdd-on services attached to a service.
GET /nonceA fresh nonce for the page that is about to post.
POST /orderCreate a booking. Money is recomputed server-side; the client only names a coupon.
POST /verifyVerify a gateway's signed return and mark the order paid.
POST /balanceTake an outstanding balance against an existing order.
POST /balance/verifyVerify that balance payment.
POST /couponValidate a coupon against a cart. Never returns a price the server did not compute.
POST /giftcardCheck a gift-card balance.
POST /giftcard-orderBuy a gift card.
POST /package-orderBuy a package or membership.
POST /event-orderBuy event tickets, with named attendees.
POST /courseBook a recurring course of treatment in one call.
POST /creditApply prepaid credit to an order.
POST /waitlistJoin a waiting list, or claim an offered slot.
POST /resumeResume an abandoned booking from its recovery link.
GET /lastbookingThe caller's most recent booking, for the manage page.
POST /agentThe booking assistant. Returns tool-verified values only.
POST /copilotThe admin copilot. Capability is checked per tool against the caller.
POST /whatsapp/inboundTwilio's inbound webhook. Signature-verified, and it stores no message body.

Authentication

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

Verifying a webhook

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

Seven events.

EventFires with
booking.createdkavita_order_created
booking.paidkavita_order_paid
booking.cancelledkavita_order_cancelled
booking.rescheduledkavita_appointment_rescheduled
booking.reassignedkavita_appointment_reassigned
booking.attendancekavita_attendance_set
booking.refundedkavita_order_refunded

PHP

Actions and filters.

kavita_order_createdkavita_order_paidkavita_order_settledkavita_order_cancelledkavita_order_refundedkavita_appointment_rescheduledkavita_appointment_reassignedkavita_attendance_setkavita_loyalty_issuedkavita_bookingskavita_manage

Plus 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

Four promises about this API.

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.

Capability, not trust

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.

Breaking changes get a version

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.

It is documented because it is used

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.

Kavita — free until you hire somebodyStart free
Our first plugin: Kavita — bookings and appointmentsTake a look