API
The API lets your own code — or an AI agent — do what the app does: check availability, book, reschedule and cancel, mint On The Fly Links, and read everything back.
If you only want to react when something is booked, use webhooks instead. They push to you, so you do not have to poll.
API keys
API keys are a Pro feature, and only the space owner can manage them. Admins cannot. See the billing guide.
- Open Settings → API keys.
- Choose Create API key and give it a name.
- Pick its scopes: Full access, Read only, or Custom to choose exactly what it can do (see Scopes). Then choose Create.
- Copy the key and store it somewhere safe. It is shown once and never again.
- A key looks like sk_prefix_secret. The list only ever shows the prefix.
- A space can hold up to 10 active keys.
- Choose Revoke to stop a key working. Revoking takes effect immediately and cannot be undone. Create a new key to replace it.
- Keys from a space that is no longer on Pro stop working. Requests come back as 403 with the error code SPACE_NOT_PRO.
Scopes
Every key has scopes: the specific things it is allowed to do. Choose them when you create the key — Full access, Read only, or Custom — or pick them one at a time. Keys made before scopes existed keep full access. No scope includes another: for example, bookings:write does not include the guest manage link.
- bookings:read — list bookings, get one booking, and list open slots. GET /bookings, GET /bookings/:bookingId, GET /availability.
- bookings:write — create, reschedule, ask to reschedule, edit details, cancel, and cancel a whole series. POST /bookings, POST /bookings/:bookingId/reschedule, POST /bookings/:bookingId/request-reschedule, PATCH /bookings/:bookingId, POST /bookings/:bookingId/cancel, POST /bookings/:bookingId/cancel-series.
- bookings:manage_link — get the guest manage link for a booking. GET /bookings/:bookingId/manage-link. This is a guest credential, so it is never included in bookings:write.
- teammates:write — add a teammate to a booking, remove one, and change the host. POST /bookings/:bookingId/teammates, DELETE /bookings/:bookingId/teammates/:userId, POST /bookings/:bookingId/reassign.
- setup:read — list event types, booking pages, and space teammates. GET /event-types, GET /sheets, GET /teammates.
- setup:write — set the event types a booking page offers. PUT /sheets/:sheetId/event-types.
- links:read — list and get On The Fly Links. GET /scheduling-links, GET /scheduling-links/:linkId.
- links:write — create, edit, share and revoke On The Fly Links. POST /scheduling-links, , , , .
A request that needs a scope the key does not have gets 403 with the error code INSUFFICIENT_SCOPE.
Making a request
- Send your key in the Authorization header, as Bearer followed by the key.
- Every space gets 60 requests per minute, shared by the REST API and the MCP server together. The limit is also shared by all the keys in the space, so extra keys do not buy you more. Over the limit you get 429, with a Retry-After header saying how many seconds to wait.
- Calls that email a guest (creating a booking, rescheduling one and asking a guest to reschedule) are capped at 200 per space per day, counted in UTC and shared by the REST API and the MCP server. Past the cap you get 429 with DAILY_EMAIL_LIMIT_EXCEEDED until midnight UTC. Bookings guests make on your booking pages do not count.
- A request body can be at most 256 KB. A larger body gets 413 with PAYLOAD_TOO_LARGE.
- A request that fails validation gets 400 with INVALID_INPUT. The message names each field that is wrong and why. It does not repeat what you sent.
- A key only ever sees the space it was created in.
What you can do
- List bookings. Filter by event type, status and a start-time range. Long lists are paged: pass the cursor from the last response to get the next page.
- Get one booking by its id, with its guests and the answers they gave to your questions.
- Cancel a booking. This does what cancelling in the app does: the slot goes back on the booking page, the usual cancellation emails go out, and the event is removed from the connected calendar. You can send a reason, or send none. Cancelling twice is safe. A booking that has already started cannot be cancelled and comes back as 422.
- List event types. Use the ids you get back to filter the booking list. sheetIds lists the booking pages that offer each event type. isPublic is deprecated. It now means the event type is on at least one booking page.
- List open slots for an event type, across every booking page in the space. This includes pages that do not list the event type, because this call is for booking on the host's side. Guests only see the event types a page lists. This is how you propose times to someone: query, present the options, then book their pick. One query covers at most 62 days from from to to. A longer window gets 400.
- Create a booking for a guest. The confirmation and invite emails go out exactly as for a page booking. Booking the same guest into the same slot twice hands the existing booking back rather than making a second one, including its additionalGuests, which a replay does not change. Send additionalGuests to bring others along — only accepted when the event type's “Let guests add other guests” setting is on, up to the limit the host set (at most 10). Repeats are merged. The response lists them on the booking and on the invitee. They are not invites: no seat, no manage link. Sending them when the setting is off, or with an invalid address, the guest's own address, or too many addresses, comes back as 422 with ADDITIONAL_GUESTS_NOT_ALLOWED or ADDITIONAL_GUESTS_INVALID.
Booking locations
Every booking has a location that says where the meeting happens and how to join it. Each event type has a locationType, so you can see the kind before you book.
- type is how the event type is set up: in_person, custom_link, google_meet_auto, zoom_auto, teams_link, phone_guest_calls or phone_host_calls.
- joinUrl is the link to join. It is a web link for Zoom, Google Meet, Microsoft Teams and custom links. It is a tel: link when the guest calls the host.
- address is the venue. It is only set for an in-person booking.
- For phone_host_calls, the host calls the guest. There is no joinUrl. The number is the invitee's phone. To book this type, send the guest's number as the answer to the question phone.
- For google_meet_auto and zoom_auto, the meeting is made just after the booking. Until then pending is true and there is no joinUrl. Read the booking again later to get the link.
- label is one line of text to show to people, the same as in the emails. Do not parse it.
Webhooks describe the location the same way, so a webhook and an API read of one booking always agree.
AI agents (MCP)
Everything above is also available as Model Context Protocol tools, so an AI agent can drive scheduling directly. Point your MCP client at /api/mcp on your app domain and authenticate with a space API key as the Bearer token — the same key, the same Pro requirement, the same rate limit. Clients such as Claude and ChatGPT can sign in instead.
For step-by-step setup in Claude, ChatGPT, Claude Code, Cursor and other clients, see Connect AI assistants.
There is one tool per capability — list_bookings, get_availability, create_booking, reschedule_booking, create_scheduling_link and the rest — each answering with a short summary plus the full structured result. create_booking takes the same optional additionalGuests argument as the REST endpoint, with the same rules and error codes.
The booking tools return the same location and invitee phone as the REST API, and list_event_types returns each locationType. The tool descriptions tell the agent what each value means.
Guest emails use list_event_emails, save_event_email, and reset_event_email. List first and edit the seed that comes back. Saving replaces one email. Resetting puts the standard wording back. The same safety rules as the in-app editor apply, and a test send still only goes to the signed-in host from the app.
Booking pages and On The Fly Links use set_sheet_event_types, update_scheduling_link, get_scheduling_link_url, and rotate_scheduling_link. They need the same scopes as the matching REST calls.
Signing in instead of an API key
The MCP server also accepts OAuth access tokens, so a client can sign in with a CallSlot account instead of sending a key. This is for the MCP server only. The REST API accepts only API keys.
- Discovery. A request with no token gets 401 with a WWW-Authenticate header that names the protected resource metadata at /.well-known/oauth-protected-resource/api/mcp. That document names the authorization server.
- Client registration. Clients identify themselves with a Client ID Metadata Document: the client ID is the HTTPS URL of a JSON document that describes the client. Dynamic client registration is not offered. The flow is the authorization code flow with PKCE (S256).
- One space per grant. The person signing in picks one space they own on Pro and chooses which scopes to allow. The scopes are the API key scopes, plus offline_access for a refresh token. The token works on that space only, with the same rate limit and email cap as a key.
- A missing scope. A tool call that needs a scope the token does not have gets HTTP 403 with WWW-Authenticate: Bearer error="insufficient_scope", scope="…", so the client can ask the person to allow it. A key gets the same refusal as a tool result instead.
- Revoking. The space owner disconnects an app in Settings → Connections. Its next call gets 401, and its refresh token stops working.
Full reference
Every field, error code and example lives in the live API reference, where you can try each call. The same thing as a machine readable OpenAPI file is there too. Settings → API keys links straight to it.