availability:readreads open times for a meeting type.
Developers
A REST API for meeting types, availability and bookings, and the same operations as MCP tools for AI assistants. One workspace key, scoped to exactly what your integration needs.
export CALS_API_KEY="cals_live_…"
curl https://cals.to/api/v1/event-types \
-H "Authorization: Bearer $CALS_API_KEY"{
"data": [{ "id": "3f0c2a8e-6a3b-4d6e-9a43-0b1d2c3e4f50", "slug": "intro-call", "title": "Intro call", "duration": 30, "active": true }],
"next_cursor": "eyJrIjoiMjAyNi0xMC0wN1QxMjowMDowMC4wMDBaIiwiaSI6IjNmMGMifQ"
}Every v1 request sends the key as a bearer token. The key identifies the workspace, so no request ever names one, and you can only ever see that workspace's data. Session cookies are ignored on these routes.
Authorization: Bearer cals_live_…Each key carries scopes. A request outside them returns 403 insufficient_scope.
availability:readreads open times for a meeting type.
bookings:readlists and reads bookings, including guest names and emails.
bookings:writecreates, cancels and reschedules bookings and emails guests.
event_types:readlists meeting types and their settings.
event_types:writecreates and edits meeting types.
Keys look like cals_live_ followed by 32 letters and digits. The dashboard shows only the first characters, the creation date and when the key was last used. Revoking a key stops it on the very next request.
A workspace without a paid plan can hold 1 active key; paid plans can hold 10.
The API is for servers. It sends no CORS headers, so browsers refuse cross-origin calls; keep keys out of web pages and mobile apps.
All endpoints live under /api/v1, accept and return JSON, and need the scope shown. Times are UTC ISO 8601.
GET/api/v1/event-types
Scope event_types:read
Oldest first. Each item includes hosts, questions and its public booking_url.
curl https://cals.to/api/v1/event-types?limit=20 \
-H "Authorization: Bearer $CALS_API_KEY"POST/api/v1/event-types
Scope event_types:write
Validated exactly like the dashboard. Defaults: unpublished, one host (the owner), Google Meet, 120 minutes notice, 60 day horizon. Questions need Pro; round-robin and collective need Teams.
curl -X POST https://cals.to/api/v1/event-types \
-H "Authorization: Bearer $CALS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"title":"Intro call","slug":"intro-call","duration":30,"location":"google_meet","active":true}'PATCH/api/v1/event-types/{id}
Scope event_types:write
Send only the fields you change. Publishing (active: true) needs every host to have a connected calendar.
curl -X PATCH https://cals.to/api/v1/event-types/3f0c2a8e-6a3b-4d6e-9a43-0b1d2c3e4f50 \
-H "Authorization: Bearer $CALS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"duration":45,"notice":240}'GET/api/v1/availability
Scope availability:read
Wraps the same slot engine as the booking page: calendars, buffers, notice, horizon and overrides all apply. Range up to 7 days; defaults to the next 7 days.
curl "https://cals.to/api/v1/availability?event_type_id=3f0c2a8e-6a3b-4d6e-9a43-0b1d2c3e4f50&from=2026-11-02T00:00:00Z&to=2026-11-06T00:00:00Z&timezone=Europe/London" \
-H "Authorization: Bearer $CALS_API_KEY"GET/api/v1/bookings
Scope bookings:read
Ordered by start time. Filter by status (pending, confirmed, rescheduling, cancelling, cancelled, recovering, failed) and by start with from/to.
curl "https://cals.to/api/v1/bookings?status=confirmed&from=2026-11-01T00:00:00Z&limit=50" \
-H "Authorization: Bearer $CALS_API_KEY"GET/api/v1/bookings/{id}
Scope bookings:read
Includes the guest's name, email, answers, status and meeting link once confirmed.
curl https://cals.to/api/v1/bookings/9b2e7c1d-4f5a-4c3b-8d2e-1a0b9c8d7e6f \
-H "Authorization: Bearer $CALS_API_KEY"POST/api/v1/bookings
Scope bookings:write
Goes through the same path as the booking page, including plan allowances. Returns 202 with status pending; confirmation follows within moments once the calendar event is written.
curl -X POST https://cals.to/api/v1/bookings \
-H "Authorization: Bearer $CALS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"event_type_id":"3f0c2a8e-6a3b-4d6e-9a43-0b1d2c3e4f50","start":"2026-11-03T14:00:00Z","name":"Ada Lovelace","email":"ada@example.com","timezone":"Europe/London"}'POST/api/v1/bookings/{id}/cancel
Scope bookings:write
Works for upcoming confirmed bookings. Cals removes the calendar event and emails the guest. Repeating the call is safe.
curl -X POST https://cals.to/api/v1/bookings/9b2e7c1d-4f5a-4c3b-8d2e-1a0b9c8d7e6f/cancel \
-H "Authorization: Bearer $CALS_API_KEY"POST/api/v1/bookings/{id}/reschedule
Scope bookings:write
Moves the booking to a new open time for the same meeting type and emails the guest. Returns the new booking; the old one moves through rescheduling.
curl -X POST https://cals.to/api/v1/bookings/9b2e7c1d-4f5a-4c3b-8d2e-1a0b9c8d7e6f/reschedule \
-H "Authorization: Bearer $CALS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"start":"2026-11-04T09:30:00Z"}'The Cals MCP server speaks Streamable HTTP. Without a key it offers the public guest-booking tools. With a key it adds host tools for your workspace, limited to the key's scopes; a request with an invalid key is refused rather than downgraded.
https://cals.to/api/public/mcpAdd this to claude_desktop_config.json (Settings → Developer → Edit config). mcp-remote forwards the Authorization header; restart Claude afterwards.
{
"mcpServers": {
"cals": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://cals.to/api/public/mcp",
"--header",
"Authorization:${AUTH_HEADER}"
],
"env": {
"AUTH_HEADER": "Bearer cals_live_…"
}
}
}
}claude mcp add --transport http cals https://cals.to/api/public/mcp \
--header "Authorization: Bearer $CALS_API_KEY"Add this to ~/.cursor/mcp.json, or .cursor/mcp.json in a project you do not commit secrets from.
{
"mcpServers": {
"cals": {
"url": "https://cals.to/api/public/mcp",
"headers": {
"Authorization": "Bearer cals_live_…"
}
}
}
}ChatGPT custom connectors support OAuth or no authentication, so in ChatGPT itself use the public guest tools at the same URL. For host tools, attach the server to the Responses API with this tool definition:
{
"type": "mcp",
"server_label": "cals",
"server_url": "https://cals.to/api/public/mcp",
"headers": {
"Authorization": "Bearer cals_live_…"
},
"require_approval": "always"
}list_event_typesList this workspace's meeting types with their settings, hosts and booking links. Paginate with cursor.
Scope event_types:read
create_event_typeCreate a meeting type. Requires title, slug and duration (minutes, multiple of 15). Defaults to an unpublished, one-host Google Meet meeting.
Scope event_types:write
get_availabilityOpen start times (UTC ISO) for a published meeting type over a range of up to 7 days. Pass timezone for local labels.
Scope availability:read
list_bookingsList bookings ordered by start time, optionally filtered by status and a from/to start range. Paginate with cursor.
Scope bookings:read
create_bookingBook a guest into an open slot. Always pass a new idempotency_key per intended booking and reuse it on retries. Cals emails the guest.
Scope bookings:write
cancel_bookingCancel an upcoming confirmed booking. Cals emails the guest and removes the calendar event. Confirm with the user first.
Scope bookings:write
reschedule_bookingMove an upcoming confirmed booking to a new open start time for the same meeting type. Confirm with the user first.
Scope bookings:write
Host tools can book, cancel and reschedule real meetings and email guests. Keep require_approval on, or the client's equivalent, for write tools.
Each key may make 120 requests per minute. Each IP may fail authentication 20 times per minute; after that, even valid keys from that IP wait for the next minute. The MCP endpoint also allows 120 requests per minute per IP. Over a limit you get 429 rate_limited with a Retry-After header in seconds.
Plan allowances apply exactly as in the dashboard: monthly booking limits, Pro-only booking questions and Teams-only round-robin and collective meetings.
Errors use one envelope with a stable machine-readable code and a human message. Branch on the code, never on the message.
{
"error": { "code": "insufficient_scope", "message": "This key needs the bookings:write scope." }
}| Status | Codes | Meaning |
|---|---|---|
400 | invalid_inputinvalid_jsoninvalid_cursorinvalid_rangeidempotency_key_required | Fix the request; the message names the first bad field. |
401 | invalid_api_key | Missing, malformed or revoked key. |
402 | subscription_requiredpro_requiredteams_requiredplan_unavailable | The workspace plan does not include this. |
403 | insufficient_scope | The key lacks the scope the endpoint needs. |
404 | event_type_not_foundbooking_not_found | No such record in this workspace. |
409 | slot_takenslot_expiredslug_takencalendar_requiredcannot_reschedulebooking_retry_unavailable | The state changed or conflicts; read it again before retrying. |
415 | json_required | Send Content-Type: application/json. |
429 | rate_limitedbooking_allowance | Wait for Retry-After, or the monthly allowance resets. |
503 | calendar_unavailableno_hostsintegration_unavailable | A dependency is unavailable; retry later. Cals never invents availability. |
List endpoints return { data, next_cursor }. Pass limit (1–100, default 50) and, for the next page, cursor set to the previous next_cursor. next_cursor is null on the last page. Cursors are opaque; keep your filters the same between pages.
curl "https://cals.to/api/v1/bookings?limit=100&cursor=$NEXT_CURSOR" \
-H "Authorization: Bearer $CALS_API_KEY"POST /bookings requires an Idempotency-Key header. Generate a new one (a UUID works) for each booking you intend to make and resend the same key when retrying a timeout. A repeat with the same key and body returns the original booking with Idempotent-Replayed: true; the same key with a different body returns 409. Keys are remembered for 24 hours and scoped to your workspace. Reschedule accepts the header too; without it, moving the same booking to the same time is treated as one request.
To react to bookings instead of polling, add a signed webhook in Dashboard → Agent workspace → Webhooks. Cals sends booking.confirmed, booking.cancelled and booking.failed, signed with HMAC-SHA256 in X-Cals-Signature, with at-least-once delivery and retries.
Read the webhook contractThis page is also available as Markdown for agents and tools.