Docs · API quickstart

API quickstart

Call Cadence from your own backend with an organization key, or from the browser with the keyless public surface. Times are UTC ISO-8601 instants — render them in the customer's timezone.

Base URL & auth

Base URL

https://cadence.abhix-ai.com/api/v1

Auth header

Authorization: Bearer ck_…

Get a key in the dashboard under Settings → API keys (sign up first if needed). Cadence stores only a hash, so the key is shown once at creation. Keep it server-side — never embed it in a browser or the booking widget.

Keep your key on the server

The ck_… key can read and write everything in your org. Put it in a backend environment variable and proxy requests through your own route handlers, as in the example below. For browser apps, use the public surface instead.

List services

Every booking starts with a service id. List the active services for your org:

Request

bash
curl https://cadence.abhix-ai.com/api/v1/services \
  -H "Authorization: Bearer ck_live_…"

Then fetch open slots for one (practitioner_id is optional — omit it for "any available"):

Availability

bash
curl "https://cadence.abhix-ai.com/api/v1/availability?service_id=3f9a1c20-8b6e-4f2a-9d1c-2e7b6a5c4d10" \
  -H "Authorization: Bearer ck_live_…"

All ids (service, practitioner, booking, customer) are UUIDs returned by the list endpoints — the 3f9a1c20-8b6e-4f2a-9d1c-2e7b6a5c4d10 above is just an example. Copy the real ones from GET /services, /practitioners, etc.

Create a booking

Post a booking against an open slot. Omit practitioner_id and Cadence picks a free, least-loaded practitioner. Send an Idempotency-Key header so retries are safe — a replay returns the existing booking with 200.

Use a fresh key per distinct booking

The key must be unique to each booking — reusing a key returns the first booking regardless of the new body. Generate a new Idempotency-Key for every distinct booking (a UUID per checkout), and reuse the same one only when retrying that exact request.

Request

bash
curl -X POST https://cadence.abhix-ai.com/api/v1/bookings \
  -H "Authorization: Bearer ck_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: visit-42" \
  -d '{
    "service_id": "3f9a1c20-8b6e-4f2a-9d1c-2e7b6a5c4d10",
    "start": "2026-06-01T13:00:00Z",
    "customer": { "name": "Pat Patient", "email": "pat@example.com" }
  }'

Responses: 201 created · 200 idempotent replay · 404 unknown service/practitioner · 422 not an open slot · 409 the slot was taken by a concurrent booking. The no-double-booking guarantee is enforced in Postgres.

To manage a booking later: GET /bookings/{id}, POST /bookings/{id}/reschedule (body { "start": "…" }), and POST /bookings/{id}/cancel.

Keep the key server-side

The pattern: your frontend calls your own route handler, which adds the key and forwards to Cadence. The secret never reaches the browser.

Next.js route handler

ts
// app/api/bookings/route.ts — the key never reaches the browser.
export async function POST(req: Request) {
  const body = await req.json();

  const res = await fetch("https://cadence.abhix-ai.com/api/v1/bookings", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.CADENCE_ORG_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": body.idem,
    },
    body: JSON.stringify(body),
  });

  return Response.json(await res.json(), { status: res.status });
}

Public surface (browser-safe, no key)

For client-side widgets and pages, the publishable surface resolves a tenant by slug and needs no API key. It exposes only the booking surface; abuse is bounded by the platform rate limiter.

  • GET /api/v1/public/orgs/{slug} → org id, name, timezone, branding
  • GET /api/v1/public/orgs/{slug}/services · /practitioners
  • GET /api/v1/public/orgs/{slug}/availability?service_id=&from=&to=
  • POST /api/v1/public/orgs/{slug}/bookings — same semantics as POST /bookings

No-key example

bash
# No key — resolve a tenant by slug. Safe to call from the browser.
curl https://cadence.abhix-ai.com/api/v1/public/orgs/movela/services

curl -X POST https://cadence.abhix-ai.com/api/v1/public/orgs/movela/bookings \
  -H "Content-Type: application/json" \
  -d '{
    "service_id": "3f9a1c20-8b6e-4f2a-9d1c-2e7b6a5c4d10",
    "start": "2026-06-01T13:00:00Z",
    "customer": { "name": "Pat", "phone": "+1…" }
  }'

Webhooks

Register an endpoint to receive a signed POST when bookings change, so your systems stay in sync without polling. The signing secret is returned once at creation (add an endpoint in Settings).

Subscribe to all events, or a subset. The full set:

  • booking.created — a new booking was made
  • booking.cancelled — a booking was cancelled
  • booking.rescheduled — a booking moved to a new time
  • booking.completed — a booking was marked completed
  • booking.no_show — a booking was marked a no-show
  • video.started — the provider started the video call (video-enabled orgs only)
  • video.ended — the video call ended (video-enabled orgs only)

Delivery headers

http
X-Cadence-Event: booking.created
X-Cadence-Signature: sha256=<hex hmac of the raw body>

Every delivery shares one envelope: an event id/type/created_at plus a data.booking object. Note the booking carries a customer_id (not the customer's name/email) — resolve PII with a keyed read if you need it.

Payload

json
{
  "id": "9b1d4e7a-2c3f-4a8b-9e1d-6f0a2b3c4d5e",
  "type": "booking.created",
  "created_at": "2026-06-01T12:00:00+00:00",
  "data": {
    "booking": {
      "id": "7c2b1c20-8b6e-4f2a-9d1c-2e7b6a5c4d10",
      "status": "confirmed",
      "practitioner_id": "…",
      "service_id": "3f9a1c20-8b6e-4f2a-9d1c-2e7b6a5c4d10",
      "customer_id": "…",
      "start": "2026-06-01T13:00:00+00:00",
      "end": "2026-06-01T13:30:00+00:00",
      "location_type": "virtual"
    }
  }
}

Verify every delivery. The signature is an HMAC-SHA256 of the raw request body, keyed by your endpoint's signing secret. Recompute it and compare in constant time before trusting the payload:

Verify (Python)

python
from cadence_client import verify_signature

if not verify_signature(secret, raw_request_body, request.headers["X-Cadence-Signature"]):
    raise HTTPException(401)

SDKs

Prefer a typed client? Both wrap the same API and throw a typed error on non-2xx (409 taken · 422 not open).

Private betaThe @cadence/client (TS) and cadence-client (Python) packages are in private beta and not yet published, so npm install @cadence/client / pip install cadence-client won't resolve just yet. Use the raw REST calls above today — the SDKs are the ergonomics you'll reach for once they ship. Want early access?

TypeScript / JavaScript

bash
npm install @cadence/client
ts
import { CadenceClient } from "@cadence/client";

// Server-side, full API — the ck_… key is a server secret.
const cadence = new CadenceClient({ apiKey: process.env.CADENCE_ORG_API_KEY! });

const services = await cadence.listServices();
const avail = await cadence.getAvailability({ serviceId: services[0].id });

const booking = await cadence.createBooking({
  serviceId: services[0].id,
  start: avail.slots[0].start,        // omit practitionerId → any available
  customer: { name: "Pat", email: "pat@example.com" },
  idempotencyKey: "visit-42",         // safe retries
});

Python

bash
pip install cadence-client
python
from cadence_client import CadenceClient

async with CadenceClient("https://cadence.abhix-ai.com", api_key="ck_live_…") as cad:
    services = await cad.list_services()
    avail = await cad.get_availability(service_id=services[0].id)
    booking = await cad.create_booking(
        service_id=services[0].id,
        start=avail.slots[0].start,          # any available practitioner
        customer={"name": "Pat", "email": "pat@example.com"},
        idempotency_key="visit-42",
    )

There's also a browser-safe CadencePublicClient (TS) for the keyless public surface — see the prebuilt UI guide for the React components built on it.

Full reference

Every endpoint, query parameter, and response shape — generated from the live OpenAPI schema, with try-it-in-the-browser requests — lives at cadence.abhix-ai.com/api/docs. The raw schema is at /api/openapi.json.