Developers

Build on Cals

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.

Quick start

  1. Open Dashboard → Developers. Owners and admins can create keys.
  2. Name the key after your integration, choose its scopes and select Create key.
  3. Copy the key straight away. Cals shows it once and stores only a hash.
  4. Store it as a server-side secret and make your first request:
Shell
export CALS_API_KEY="cals_live_…"
curl https://cals.to/api/v1/event-types \
  -H "Authorization: Bearer $CALS_API_KEY"
Response
{
  "data": [{ "id": "3f0c2a8e-6a3b-4d6e-9a43-0b1d2c3e4f50", "slug": "intro-call", "title": "Intro call", "duration": 30, "active": true }],
  "next_cursor": "eyJrIjoiMjAyNi0xMC0wN1QxMjowMDowMC4wMDBaIiwiaSI6IjNmMGMifQ"
}

Authentication

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:read

reads open times for a meeting type.

bookings:read

lists and reads bookings, including guest names and emails.

bookings:write

creates, cancels and reschedules bookings and emails guests.

event_types:read

lists meeting types and their settings.

event_types:write

creates 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.

Endpoints

All endpoints live under /api/v1, accept and return JSON, and need the scope shown. Times are UTC ISO 8601.

List meeting types

GET/api/v1/event-types

Scope event_types:read

Oldest first. Each item includes hosts, questions and its public booking_url.

Shell
curl https://cals.to/api/v1/event-types?limit=20 \
  -H "Authorization: Bearer $CALS_API_KEY"

Create a meeting type

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.

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

Update a meeting type

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.

Shell
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 availability

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.

Shell
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"

List bookings

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.

Shell
curl "https://cals.to/api/v1/bookings?status=confirmed&from=2026-11-01T00:00:00Z&limit=50" \
  -H "Authorization: Bearer $CALS_API_KEY"

Get a booking

GET/api/v1/bookings/{id}

Scope bookings:read

Includes the guest's name, email, answers, status and meeting link once confirmed.

Shell
curl https://cals.to/api/v1/bookings/9b2e7c1d-4f5a-4c3b-8d2e-1a0b9c8d7e6f \
  -H "Authorization: Bearer $CALS_API_KEY"

Create a booking

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.

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

Cancel a booking

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.

Shell
curl -X POST https://cals.to/api/v1/bookings/9b2e7c1d-4f5a-4c3b-8d2e-1a0b9c8d7e6f/cancel \
  -H "Authorization: Bearer $CALS_API_KEY"

Reschedule a booking

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.

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

MCP setup

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/mcp

Claude Desktop

Add this to claude_desktop_config.json (Settings → Developer → Edit config). mcp-remote forwards the Authorization header; restart Claude afterwards.

claude_desktop_config.json
{
  "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 Code

Shell
claude mcp add --transport http cals https://cals.to/api/public/mcp \
  --header "Authorization: Bearer $CALS_API_KEY"

Cursor

Add this to ~/.cursor/mcp.json, or .cursor/mcp.json in a project you do not commit secrets from.

~/.cursor/mcp.json
{
  "mcpServers": {
    "cals": {
      "url": "https://cals.to/api/public/mcp",
      "headers": {
        "Authorization": "Bearer cals_live_…"
      }
    }
  }
}

ChatGPT and the OpenAI API

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:

JSON
{
  "type": "mcp",
  "server_label": "cals",
  "server_url": "https://cals.to/api/public/mcp",
  "headers": {
    "Authorization": "Bearer cals_live_…"
  },
  "require_approval": "always"
}

Host tools

list_event_types

List this workspace's meeting types with their settings, hosts and booking links. Paginate with cursor.

Scope event_types:read

create_event_type

Create 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_availability

Open 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_bookings

List bookings ordered by start time, optionally filtered by status and a from/to start range. Paginate with cursor.

Scope bookings:read

create_booking

Book 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_booking

Cancel an upcoming confirmed booking. Cals emails the guest and removes the calendar event. Confirm with the user first.

Scope bookings:write

reschedule_booking

Move 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.

Rate limits

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

Errors use one envelope with a stable machine-readable code and a human message. Branch on the code, never on the message.

JSON
{
  "error": { "code": "insufficient_scope", "message": "This key needs the bookings:write scope." }
}
StatusCodesMeaning
400invalid_inputinvalid_jsoninvalid_cursorinvalid_rangeidempotency_key_requiredFix the request; the message names the first bad field.
401invalid_api_keyMissing, malformed or revoked key.
402subscription_requiredpro_requiredteams_requiredplan_unavailableThe workspace plan does not include this.
403insufficient_scopeThe key lacks the scope the endpoint needs.
404event_type_not_foundbooking_not_foundNo such record in this workspace.
409slot_takenslot_expiredslug_takencalendar_requiredcannot_reschedulebooking_retry_unavailableThe state changed or conflicts; read it again before retrying.
415json_requiredSend Content-Type: application/json.
429rate_limitedbooking_allowanceWait for Retry-After, or the monthly allowance resets.
503calendar_unavailableno_hostsintegration_unavailableA dependency is unavailable; retry later. Cals never invents availability.

Pagination

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.

Shell
curl "https://cals.to/api/v1/bookings?limit=100&cursor=$NEXT_CURSOR" \
  -H "Authorization: Bearer $CALS_API_KEY"

Idempotency

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.

Webhooks

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 contract

This page is also available as Markdown for agents and tools.