Base URL: https://your-instance.example.com/api/v1. Full machine-readable spec
at GET /api/v1/openapi.json (no auth required, so tooling can discover it).
Every endpoint except openapi.json needs an API key, created in
/admin/settings/api-keys:
Authorization: Bearer bk_live_xxxxxxxxxxxxxxxxxxxxxxxx
Keys are stored hashed — OpenCalendar shows you the token once, at creation. A
revoked or unknown key gets 401 { ok: false, error, code: "UNAUTHORIZED" }.
Every response is { ok: boolean, data?: ..., error?: string, code?: string }.
CORS is open (Access-Control-Allow-Origin: *) since the API is key-authed,
not cookie-authed — call it from a browser or a server.
300 req/min/key on reads, 30 req/min/key on writes (booking, cancel,
reschedule). A 429 includes code: "RATE_LIMITED".
Every active event type, including secret ones — API keys see everything a direct link would.
curl https://your-instance/api/v1/event-types \
-H "Authorization: Bearer bk_live_..."?from=2026-10-01&to=2026-10-07&tz=America/Chicago&duration=30
from/to are dates (inclusive), at most 70 days apart. tz defaults to the
event type's schedule timezone. duration only matters for event types that
offer more than one. Returns UTC ISO instants.
Free event types only — a paid type returns 402 with data.bookingUrl
pointing at the hosted booking page, since the invitee has to actually pay
there.
curl -X POST https://your-instance/api/v1/bookings \
-H "Authorization: Bearer bk_live_..." -H "Content-Type: application/json" \
-d '{
"slug": "intro-call",
"name": "Ada Lovelace",
"email": "ada@example.com",
"timezone": "America/New_York",
"startTime": "2026-10-02T14:00:00.000Z"
}'answers (object keyed by question id), guests (array of emails),
location, durationMinutes and utm are all optional — same fields the
booking page itself sends. A slot someone else just took comes back 409 with
code: "SLOT_TAKEN".
?status=CONFIRMED&from=2026-10-01T00:00:00Z&to=2026-11-01T00:00:00Z&email=ada@example.com&limit=25&cursor=...
Cursor-paginated, newest startTime first. data.nextCursor is null on the
last page.
Acts as the host — no invitee cutoff, and refunds a paid booking by default.
curl -X POST https://your-instance/api/v1/bookings/abc123/cancel \
-H "Authorization: Bearer bk_live_..." -H "Content-Type: application/json" \
-d '{"reason": "Host unavailable", "notify": true, "refund": true}'Also acts as the host — may move to any slot that's actually free, ignoring the invitee's cutoff window.
curl -X POST https://your-instance/api/v1/bookings/abc123/reschedule \
-H "Authorization: Bearer bk_live_..." -H "Content-Type: application/json" \
-d '{"startTime": "2026-10-03T15:00:00.000Z"}'docs/MCP.md— same booking engine, exposed to AI agents over MCP.docs/WEBHOOKS.md— get pushed to instead of pollingGET /bookings.