# AI agents
Source: https://cal.com/docs/agents
How AI agents can use the Cal.com API v2 to manage scheduling
This guide explains how to build AI agents that interact with Cal.com's scheduling infrastructure.
## Recommended: Use the Cal.com CLI
For agents that can execute commands on a machine, use the official Cal.com CLI instead of calling the API directly. The CLI handles authentication, versioning, and common workflows automatically.
```bash theme={null}
npm install -g @calcom/cli
```
```bash theme={null}
curl -fsSL https://cal.com/install.sh | bash
```
The CLI includes `--help` flags on all commands to learn about available options:
```bash theme={null}
calcom --help
calcom bookings --help
calcom slots --help
```
Only fall back to the API approach below if your agent cannot install or execute the CLI on its machine.
## Overview
Cal.com API v2 enables AI agents to:
* **Check availability** - Query available time slots for any user or team
* **Create bookings** - Schedule meetings on behalf of users
* **Manage event types** - Create and configure different meeting types
* **Handle rescheduling and cancellations** - Modify existing bookings
* **Manage schedules** - Set and update user availability
* **Track credit usage** - Check balances and charge credits for agent interactions
## Authentication
AI agents should authenticate using an **API key**. Generate one in [Settings → Developer → API Keys](https://app.cal.com/settings/developer/api-keys).
```bash theme={null}
curl -X GET "https://api.cal.com/v2/me" \
-H "Authorization: Bearer cal_live_xxxxxxxxxxxx" \
-H "cal-api-version: 2024-08-13"
```
The `cal-api-version` header is required for all v2 endpoints. If you omit it, requests will return a 404. Always include `cal-api-version: 2024-08-13`.
## Common workflows
### 1. Check availability
Query available slots using username and event slug—no need to look up IDs first:
```bash theme={null}
curl -X GET "https://api.cal.com/v2/slots?username=bailey&eventSlug=15min&startTime=2024-01-15T00:00:00Z&endTime=2024-01-16T23:59:59Z" \
-H "cal-api-version: 2024-08-13"
```
Response includes available time slots:
```json theme={null}
{
"status": "success",
"data": {
"slots": {
"2024-01-15": [
{ "time": "2024-01-15T09:00:00Z" },
{ "time": "2024-01-15T10:00:00Z" },
{ "time": "2024-01-15T14:00:00Z" }
]
}
}
}
```
### 2. Create a booking
Schedule a meeting using username and event slug. This is the most common pattern for AI agents that extract scheduling intent from natural language (e.g., "book 15min with bailey"):
```bash theme={null}
curl -X POST "https://api.cal.com/v2/bookings" \
-H "Content-Type: application/json" \
-H "cal-api-version: 2024-08-13" \
-d '{
"eventTypeSlug": "15min",
"username": "bailey",
"start": "2024-01-15T09:00:00Z",
"attendee": {
"name": "John Doe",
"email": "john@example.com",
"timeZone": "America/New_York"
}
}'
```
The booking creation endpoint is public and does not require authentication. This allows agents to book on behalf of external users.
### 3. Handle custom booking questions
Most Cal.com users have required questions on their booking pages (e.g., "What is this meeting about?"). If you omit required fields, the API returns a 400 error.
Include responses in `bookingFieldsResponses`:
```bash theme={null}
curl -X POST "https://api.cal.com/v2/bookings" \
-H "Content-Type: application/json" \
-H "cal-api-version: 2024-08-13" \
-d '{
"eventTypeSlug": "consultation",
"username": "bailey",
"start": "2024-01-15T09:00:00Z",
"attendee": {
"name": "John Doe",
"email": "john@example.com",
"timeZone": "America/New_York"
},
"bookingFieldsResponses": {
"notes": "Discussing the new AI integration",
"company_size": "10-50"
}
}'
```
To discover which fields are required, fetch the event type details first. The `bookingFields` array shows all custom questions and their requirements.
### 4. Instant bookings for urgent requests
For teams handling urgent requests, use the `instant` flag to bypass normal scheduling and immediately ring available team members:
```bash theme={null}
curl -X POST "https://api.cal.com/v2/bookings" \
-H "Content-Type: application/json" \
-H "cal-api-version: 2024-08-13" \
-d '{
"eventTypeSlug": "urgent-support",
"username": "support-team",
"start": "2024-01-15T09:00:00Z",
"instant": true,
"attendee": {
"name": "John Doe",
"email": "john@example.com",
"timeZone": "America/New_York"
}
}'
```
Instant bookings require the event type to be configured for instant meetings. This is ideal for AI agents routing urgent customer requests.
### 5. Get user's event types
Retrieve available event types to offer booking options:
```bash theme={null}
curl -X GET "https://api.cal.com/v2/event-types?username=bailey" \
-H "Authorization: Bearer cal_live_xxxxxxxxxxxx" \
-H "cal-api-version: 2024-08-13"
```
### 6. Reschedule a booking
Move an existing booking to a new time:
```bash theme={null}
curl -X POST "https://api.cal.com/v2/bookings/{bookingUid}/reschedule" \
-H "Content-Type: application/json" \
-H "cal-api-version: 2024-08-13" \
-d '{
"start": "2024-01-16T10:00:00Z",
"rescheduleReason": "Attendee requested different time"
}'
```
### 7. Cancel a booking
```bash theme={null}
curl -X POST "https://api.cal.com/v2/bookings/{bookingUid}/cancel" \
-H "Content-Type: application/json" \
-H "cal-api-version: 2024-08-13" \
-d '{
"cancellationReason": "Meeting no longer needed"
}'
```
## Credit management
If your AI agent runs on the Cal.com platform, you can track usage by checking and charging credits through the API. This is useful for metering agent interactions against your account's credit balance.
### Check available credits
Before performing a billable action, verify that the user or team has sufficient credits:
```bash theme={null}
curl -X GET "https://api.cal.com/v2/credits/available" \
-H "Authorization: Bearer cal_live_xxxxxxxxxxxx" \
-H "cal-api-version: 2024-08-13"
```
Response:
```json theme={null}
{
"status": "success",
"data": {
"hasCredits": true,
"balance": {
"monthlyRemaining": 450,
"additional": 200
}
}
}
```
The `balance` object breaks down your remaining credits:
* `monthlyRemaining` — credits included in your current billing cycle
* `additional` — purchased credits outside the monthly allowance
### Charge credits
After a completed agent interaction, charge the appropriate number of credits:
```bash theme={null}
curl -X POST "https://api.cal.com/v2/credits/charge" \
-H "Authorization: Bearer cal_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "cal-api-version: 2024-08-13" \
-d '{
"credits": 5,
"creditFor": "AI_AGENT",
"externalRef": "agent-thread-abc-1711432800000"
}'
```
Response:
```json theme={null}
{
"status": "success",
"data": {
"charged": true,
"remainingBalance": {
"monthlyRemaining": 445,
"additional": 200
}
}
}
```
| Parameter | Required | Description |
| ------------- | -------- | ------------------------------------------------------------------------------------------------- |
| `credits` | Yes | Number of credits to charge (minimum 1) |
| `creditFor` | Yes | Usage type — use `AI_AGENT` for agent interactions |
| `externalRef` | No | Unique reference string for idempotency. Prevents double-charging if the same request is retried. |
Always include an `externalRef` tied to your agent's conversation or thread ID. This prevents duplicate charges if a network retry sends the same request twice.
### Example: check-then-charge pattern
```typescript theme={null}
const headers = {
"Authorization": "Bearer cal_live_xxxxxxxxxxxx",
"Content-Type": "application/json",
"cal-api-version": "2024-08-13"
};
// 1. Check credits before starting work
const available = await fetch("https://api.cal.com/v2/credits/available", { headers });
const { data } = await available.json();
if (!data.hasCredits) {
throw new Error("No credits available");
}
// 2. Perform the agent interaction
const result = await runAgentTask();
// 3. Charge credits after completion
await fetch("https://api.cal.com/v2/credits/charge", {
method: "POST",
headers,
body: JSON.stringify({
credits: 5,
creditFor: "AI_AGENT",
externalRef: `thread-${threadId}-${Date.now()}`
})
});
```
## Best practices for AI agents
### Handle time zones correctly
Always specify time zones explicitly. Store user preferences and pass them in requests:
```json theme={null}
{
"attendee": {
"timeZone": "Europe/London"
}
}
```
### Implement retry logic
API requests may occasionally fail. Implement exponential backoff:
```typescript theme={null}
async function callWithRetry(fn: () => Promise, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fn();
if (response.ok) return response;
if (response.status === 429) {
await sleep(Math.pow(2, i) * 1000);
continue;
}
throw new Error(`API error: ${response.status}`);
} catch (error) {
if (i === maxRetries - 1) throw error;
await sleep(Math.pow(2, i) * 1000);
}
}
}
```
### Validate availability before booking
Always check slots before attempting to create a booking to avoid conflicts:
```typescript theme={null}
// 1. Get available slots
const slots = await getAvailableSlots("bailey", "15min", startDate, endDate);
// 2. Verify the desired slot is available
const isAvailable = slots.some(slot => slot.time === desiredTime);
// 3. Create booking only if slot is available
if (isAvailable) {
await createBooking("bailey", "15min", desiredTime, attendee);
}
```
### Use webhooks for real-time updates
Configure webhooks to receive notifications when bookings are created, rescheduled, or cancelled:
```bash theme={null}
curl -X POST "https://api.cal.com/v2/webhooks" \
-H "Authorization: Bearer cal_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "cal-api-version: 2024-08-13" \
-d '{
"subscriberUrl": "https://your-agent.com/webhooks/cal",
"eventTriggers": ["BOOKING_CREATED", "BOOKING_RESCHEDULED", "BOOKING_CANCELLED"],
"active": true
}'
```
## Rate limits
API key authentication allows **120 requests per minute** by default. Contact support if you need higher limits for production agents.
## API reference
For complete endpoint documentation, see the [API v2 Reference](/docs/api-reference/v2/introduction).
Key endpoints for agents:
| Endpoint | Description |
| ----------------------------------- | -------------------------- |
| `GET /v2/slots` | Check available time slots |
| `POST /v2/bookings` | Create a new booking |
| `POST /v2/bookings/:uid/reschedule` | Reschedule a booking |
| `POST /v2/bookings/:uid/cancel` | Cancel a booking |
| `GET /v2/event-types` | List event types |
| `GET /v2/schedules` | Get user schedules |
| `POST /v2/webhooks` | Configure webhooks |
| `GET /v2/credits/available` | Check credit balance |
| `POST /v2/credits/charge` | Charge credits for usage |
# Access Control
Source: https://cal.com/docs/api-reference/v2/access-control
Roles, permissions, and OAuth scopes for organizations and teams API v2 endpoints
Organization and team API endpoints can use one of the three layers of access control: **roles**, **PBAC (Permission-Based Access Control)**, and **OAuth access token scopes**. Roles are the default mechanism — every organization and team endpoint requires a minimum membership role, for example authenticated user must have a team admin membership to access certain endpoints. PBAC is an opt-in feature that adds fine-grained permissions on top. OAuth scopes determine which endpoints an OAuth access token can reach.
## Roles
Every organization and team endpoint requires the authenticated user to have a membership with a minimum role. There are three roles, from highest to lowest privilege:
| Level | Roles (highest to lowest) |
| ------------ | ---------------------------- |
| Organization | `owner` > `admin` > `member` |
| Team | `owner` > `admin` > `member` |
### Role hierarchy
Higher roles can access endpoints that require a lower role. For example, if an endpoint requires `admin`, a user with the `owner` role can also access it.
### Role assignment limits
When creating or updating memberships, the caller cannot assign a role higher than their own, and cannot modify a member whose current role is higher than their own. For example, an `admin` cannot:
* Assign the `owner` role to another member.
* Self-promote to `owner` by updating their own membership.
* Update or change the role of an existing `owner`.
Attempts to do any of the above return `403 Forbidden`. This applies to both organization and team membership endpoints, including invite-by-email.
### Removing team owners
Extra rules apply when deleting a team membership whose role is `owner`:
* Only a user with the `team owner` role can remove another `team owner`. `team admin` cannot remove an owner, even with the `team.remove` PBAC permission.
* Organization `admin` and `owner` are exempt and can remove any team owner.
* A `team owner` cannot remove their own membership. Transfer ownership first, or have another owner remove them.
Violations return `403 Forbidden`. These rules apply to both `DELETE /v2/organizations/{orgId}/teams/{teamId}/memberships/{membershipId}` and `DELETE /v2/teams/{teamId}/memberships/{membershipId}`.
### Organization roles grant team access
Organization-level roles carry over to team endpoints:
* **Org `admin` or `owner`** can access any team endpoint, regardless of team membership or the required team role. Organization level is above team level in terms of permissions.
* **Org `member`** must have a separate team membership. Their team role is then checked against the required team role.
For example, if a team endpoint requires `team admin`:
* A user with `org admin` or `org owner` membership can access it directly — no team membership needed.
* A user with `org member` membership needs a `team admin` (or `team owner`) membership in that specific team.
### Managing memberships
Use these endpoints to manage organization and team memberships:
**Organization memberships**
| Method | Endpoint |
| -------- | ------------------------------------------------------------------------------------------------------------- |
| `POST` | [/v2/organizations//memberships](https://cal.com/docs/api-reference/v2/orgs-memberships/create-a-membership) |
| `GET` | [/v2/organizations//memberships](https://cal.com/docs/api-reference/v2/orgs-memberships/get-all-memberships) |
| `GET` | [/v2/organizations//memberships/](https://cal.com/docs/api-reference/v2/orgs-memberships/get-a-membership) |
| `PATCH` | [/v2/organizations//memberships/](https://cal.com/docs/api-reference/v2/orgs-memberships/update-a-membership) |
| `DELETE` | [/v2/organizations//memberships/](https://cal.com/docs/api-reference/v2/orgs-memberships/delete-a-membership) |
**Team memberships**
| Method | Endpoint |
| -------- | -------------------------------------------------------------------------------------------------------------------------- |
| `POST` | [/v2/organizations//teams//memberships](https://cal.com/docs/api-reference/v2/orgs-teams-memberships/create-a-membership) |
| `GET` | [/v2/organizations//teams//memberships](https://cal.com/docs/api-reference/v2/orgs-teams-memberships/get-all-memberships) |
| `GET` | [/v2/organizations//teams//memberships/](https://cal.com/docs/api-reference/v2/orgs-teams-memberships/get-a-membership) |
| `PATCH` | [/v2/organizations//teams//memberships/](https://cal.com/docs/api-reference/v2/orgs-teams-memberships/update-a-membership) |
| `DELETE` | [/v2/organizations//teams//memberships/](https://cal.com/docs/api-reference/v2/orgs-teams-memberships/delete-a-membership) |
## PBAC (Permission-Based Access Control)
PBAC is an opt-in feature enabled per organization. It lets you define custom roles with specific permissions for organization members. Instead of relying solely on admin/member roles, you can create granular roles like "Booking Manager" or "Team Lead" that have access to only the endpoints they need.
### How it works
Each endpoint has both a required membership role and a PBAC permission (e.g. `eventType.update`). Access is determined as follows:
1. **PBAC is not enabled** — the system checks if the authenticated user has a membership with the required role (e.g. `org admin`). Users with a higher role (e.g. `org owner`) can also access endpoints that require a lower role.
2. **PBAC is enabled and user has the required permission** — access is granted and the membership role check is skipped.
3. **PBAC is enabled but user is missing the permission** — falls back to the membership role check in step 1.
### Setting up PBAC
1. **Create a custom role** with specific permissions using the [Roles API](#managing-roles-and-permissions)
2. **Assign the role** to an organization or team membership
3. When the member makes API requests, PBAC checks if their role includes the required permission for that endpoint
### Managing roles and permissions
Use the following endpoints to create roles, assign permissions, and manage access for your organization members.
#### Roles
| Method | Endpoint |
| -------- | -------------------------------------------------------------------------------------------------------------- |
| `POST` | [/v2/organizations//roles](https://cal.com/docs/api-reference/v2/orgs-roles/create-a-new-organization-role) |
| `GET` | [/v2/organizations//roles](https://cal.com/docs/api-reference/v2/orgs-roles/get-all-organization-roles) |
| `GET` | [/v2/organizations//roles/](https://cal.com/docs/api-reference/v2/orgs-roles/get-a-specific-organization-role) |
| `PATCH` | [/v2/organizations//roles/](https://cal.com/docs/api-reference/v2/orgs-roles/update-an-organization-role) |
| `DELETE` | [/v2/organizations//roles/](https://cal.com/docs/api-reference/v2/orgs-roles/delete-an-organization-role) |
#### Role permissions
| Method | Endpoint |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `POST` | [/v2/organizations//roles//permissions](https://cal.com/docs/api-reference/v2/orgs-roles-permissions/add-permissions-to-an-organization-role-single-or-batch) |
| `GET` | [/v2/organizations//roles//permissions](https://cal.com/docs/api-reference/v2/orgs-roles-permissions/list-permissions-for-an-organization-role) |
| `PUT` | [/v2/organizations//roles//permissions](https://cal.com/docs/api-reference/v2/orgs-roles-permissions/replace-all-permissions-for-an-organization-role) |
| `DELETE` | [/v2/organizations//roles//permissions/](https://cal.com/docs/api-reference/v2/orgs-roles-permissions/remove-a-permission-from-an-organization-role) |
| `DELETE` | [/v2/organizations//roles//permissions](https://cal.com/docs/api-reference/v2/orgs-roles-permissions/remove-multiple-permissions-from-an-organization-role) |
### Endpoint descriptions
Each organization and team endpoint description mentions the minimum membership role and PBAC permission required to access it.
## OAuth Access Token Scopes
When accessing the API using an **OAuth access token**, the scopes granted during the authorization flow determine which endpoints the token can call. Any request to an endpoint outside the granted scopes will be rejected.
### How it works
1. When creating an OAuth client, you select the scopes your application needs.
2. During the authorization flow, the user sees which scopes your application is requesting and grants access.
3. The issued access token can only call endpoints covered by the granted scopes.
### How to tell if an endpoint supports OAuth
If an endpoint description mentions a specific OAuth access token scope (e.g. *"If accessed using an OAuth access token, the `BOOKING_READ` scope is required"*), you can access it using an OAuth access token with that scope.
If no OAuth scope is mentioned in the endpoint description, the endpoint is not accessible using an OAuth access token.
### Scope levels
Scopes are organized into three levels:
| Level | Prefix | Example | Description |
| ------------ | -------- | ------------------- | ----------------------------------------------- |
| Individual | *(none)* | `BOOKING_READ` | Access the authenticated user's own resources |
| Team | `TEAM_` | `TEAM_BOOKING_READ` | Access resources belonging to a specific team |
| Organization | `ORG_` | `ORG_BOOKING_READ` | Access resources across the entire organization |
An `ORG_` scope automatically grants the corresponding `TEAM_` scope. For example, a token with `ORG_PROFILE_READ` can also access endpoints that require `TEAM_PROFILE_READ`.
For a full list of available scopes and the endpoints they cover, see [OAuth — Available Scopes](https://cal.com/docs/api-reference/v2/oauth#available-scopes).
# Get the Cal.com IP allowlist of one service
Source: https://cal.com/docs/api-reference/v2/allowlists/get-the-calcom-ip-allowlist-of-one-service
/api-reference/v2/openapi.json get /v2/allowlists/{service}
# Get the Cal.com IP allowlists
Source: https://cal.com/docs/api-reference/v2/allowlists/get-the-calcom-ip-allowlists
/api-reference/v2/openapi.json get /v2/allowlists
Returns the IPv4 ranges Cal.com sends traffic from and receives traffic on, keyed by service. Poll this endpoint instead of hard-coding addresses, because they can change.
# Refresh API Key
Source: https://cal.com/docs/api-reference/v2/api-keys/refresh-api-key
/api-reference/v2/openapi.json post /v2/api-keys/refresh
Generate a new API key and delete the current one. Provide API key to refresh as a Bearer token in the Authorization header (e.g. "Authorization: Bearer ").
# Check if email verification is required
Source: https://cal.com/docs/api-reference/v2/bookings--email-verification/check-if-email-verification-is-required
/api-reference/v2/openapi.json get /v2/bookings/verification/email/check
Checks whether email verification is required for the given email. Returns true if verification is needed.
# Send email verification code
Source: https://cal.com/docs/api-reference/v2/bookings--email-verification/send-email-verification-code
/api-reference/v2/openapi.json post /v2/bookings/verification/email/send-code
Sends a one-time verification code to the specified email address.
# Verify email with code
Source: https://cal.com/docs/api-reference/v2/bookings--email-verification/verify-email-with-code
/api-reference/v2/openapi.json post /v2/bookings/verification/email/verify-code
Verifies an email address using the one-time code that was previously sent.
# Add an attendee to a booking
Source: https://cal.com/docs/api-reference/v2/bookings-attendees/add-an-attendee-to-a-booking
/api-reference/v2/openapi.json post /v2/bookings/{bookingUid}/attendees
Add a new attendee to an existing booking by its UID.
**Seated Events:**
This endpoint does not support seated event bookings. For seated events, each attendee must be added by creating a new booking for the same event type and time slot via `POST /v2/bookings`. Attempting to add an attendee to a seated event booking will return a 400 error.
**Side effects:**
- The booking's attendee list is updated in the database
- The calendar event is updated on connected calendars (Google Calendar, Outlook, etc.) to include the new attendee
- An email notification is sent to the new attendee with the booking details
**Permissions:**
- The authenticated user must be either the booking organizer, an existing attendee, or have the `booking.update` permission for the team
The cal-api-version header is required for this endpoint. Without it, the request will fail with a 404 error.
If accessed using an OAuth access token, the `BOOKING_WRITE` scope is required.
# Get a specific attendee for a booking
Source: https://cal.com/docs/api-reference/v2/bookings-attendees/get-a-specific-attendee-for-a-booking
/api-reference/v2/openapi.json get /v2/bookings/{bookingUid}/attendees/{attendeeId}
Retrieve a specific attendee by their ID for a booking identified by its UID.
The cal-api-version header is required for this endpoint. Without it, the request will fail with a 404 error.
If accessed using an OAuth access token, the `BOOKING_READ` scope is required.
# Get all attendees for a booking
Source: https://cal.com/docs/api-reference/v2/bookings-attendees/get-all-attendees-for-a-booking
/api-reference/v2/openapi.json get /v2/bookings/{bookingUid}/attendees
Retrieve all attendees for a specific booking by its UID.
The cal-api-version header is required for this endpoint. Without it, the request will fail with a 404 error.
If accessed using an OAuth access token, the `BOOKING_READ` scope is required.
# Add guests to an existing booking
Source: https://cal.com/docs/api-reference/v2/bookings-guests/add-guests-to-an-existing-booking
/api-reference/v2/openapi.json post /v2/bookings/{bookingUid}/guests
Add one or more guests to an existing booking. Maximum 10 guests per request, with a limit of 30 total guests per booking.
**Rate Limiting:**
This endpoint is rate limited to 5 requests per minute to prevent abuse.
**Seated Events:**
This endpoint does not support seated event bookings. For seated events, each guest must be added by creating a new booking for the same event type and time slot via `POST /v2/bookings`. Attempting to add guests to a seated event booking will return a 400 error.
**Email Notifications:**
When guests are added, the following notifications are sent (unless disabled by event type settings):
- **Organizer & Team Members:** Receive an "Add Guests" notification email informing them that new guests have been added to the booking.
- **New Guests:** Receive a "Scheduled Event" email with full booking details and calendar invite. If they have a phone number, they also receive an SMS notification.
- **Existing Guests:** Receive an "Add Guests" notification email informing them that additional guests have been added to the booking.
The cal-api-version header is required for this endpoint. Without it, the request will fail with a 404 error.
If accessed using an OAuth access token, the `BOOKING_WRITE` scope is required.
# Get the routing trace for a booking
Source: https://cal.com/docs/api-reference/v2/bookings-routing-trace/get-the-routing-trace-for-a-booking
/api-reference/v2/openapi.json get /v2/bookings/{bookingUid}/routing-trace
Retrieve the step-by-step routing trace for a booking identified by its UID. Shows how the booking was routed through routing forms, CRM lookups, and host selection logic.
Returns presented (human-readable) trace steps grouped by routing round, along with the routing form submission if one was used.
If no routing trace exists for the booking, empty steps and groups are returned.
The cal-api-version header is required for this endpoint.
If accessed using an OAuth access token, the `BOOKING_READ` scope is required.
# Cancel a booking
Source: https://cal.com/docs/api-reference/v2/bookings/cancel-a-booking
/api-reference/v2/openapi.json post /v2/bookings/{bookingUid}/cancel
:bookingUid can be :bookingUid of an usual booking, individual recurrence or recurring booking to cancel all recurrences.
Cancelling normal bookings:
If the booking is not seated and not recurring, simply pass :bookingUid in the request URL `/bookings/:bookingUid/cancel` and optionally cancellationReason in the request body `{"cancellationReason": "Will travel"}`.
Cancelling seated bookings:
It is possible to cancel specific seat within a booking as an attendee or all of the seats as the host.
1. As an attendee - provide :bookingUid in the request URL `/bookings/:bookingUid/cancel` and seatUid in the request body `{"seatUid": "123-123-123"}` . This will remove this particular attendance from the booking.
2. As the host or org admin of host - host can cancel booking for all attendees aka for every seat, this also applies to org admins. Provide :bookingUid in the request URL `/bookings/:bookingUid/cancel` and cancellationReason in the request body `{"cancellationReason": "Will travel"}` and `Authorization: Bearer token` request header where token is event type owner (host) credential. This will cancel the booking for all attendees.
Cancelling recurring seated bookings:
For recurring seated bookings it is not possible to cancel all of them with 1 call
like with non-seated recurring bookings by providing recurring bookind uid - you have to cancel each recurrence booking by its bookingUid + seatUid.
If you are cancelling a seated booking for an event type with 'show attendees' disabled, then to retrieve attendees in the response either set 'show attendees' to true on event type level or
you have to provide an authentication method of event type owner, host, team admin or owner or org admin or owner.
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
# Confirm a booking
Source: https://cal.com/docs/api-reference/v2/bookings/confirm-a-booking
/api-reference/v2/openapi.json post /v2/bookings/{bookingUid}/confirm
The provided authorization header refers to the owner of the booking.
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
If accessed using an OAuth access token, the `BOOKING_WRITE` scope is required.
# Create a booking
Source: https://cal.com/docs/api-reference/v2/bookings/create-a-booking
/api-reference/v2/openapi.json post /v2/bookings
POST /v2/bookings is used to create regular bookings, recurring bookings and instant bookings. The request bodies for all 3 are almost the same except:
If eventTypeId in the request body is id of a regular event, then regular booking is created.
If it is an id of a recurring event type, then recurring booking is created.
Meaning that the request bodies are equal but the outcome depends on what kind of event type it is with the goal of making it as seamless for developers as possible.
For team event types it is possible to create instant meeting. To do that just pass `"instant": true` to the request body.
The start needs to be in UTC aka if the timezone is GMT+2 in Rome and meeting should start at 11, then UTC time should have hours 09:00 aka without time zone.
Finally, there are 2 ways to book an event type belonging to an individual user:
1. Provide `eventTypeId` in the request body.
2. Provide `eventTypeSlug` and `username` and optionally `organizationSlug` if the user with the username is within an organization.
And 2 ways to book and event type belonging to a team:
1. Provide `eventTypeId` in the request body.
2. Provide `eventTypeSlug` and `teamSlug` and optionally `organizationSlug` if the team with the teamSlug is within an organization.
If you are creating a seated booking for an event type with 'show attendees' disabled, then to retrieve attendees in the response either set 'show attendees' to true on event type level or
you have to provide an authentication method of event type owner, host, team admin or owner or org admin or owner.
For event types that have SMS reminders workflow, you need to pass the attendee's phone number in the request body via `attendee.phoneNumber` (e.g., "+19876543210" in international format). This is an optional field, but becomes required when SMS reminders are enabled for the event type. For the complete attendee object structure, see the [attendee object](https://cal.com/docs/api-reference/v2/bookings/create-a-booking#body-attendee) documentation.
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
# Decline a booking
Source: https://cal.com/docs/api-reference/v2/bookings/decline-a-booking
/api-reference/v2/openapi.json post /v2/bookings/{bookingUid}/decline
The provided authorization header refers to the owner of the booking.
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
If accessed using an OAuth access token, the `BOOKING_WRITE` scope is required.
# Get a booking
Source: https://cal.com/docs/api-reference/v2/bookings/get-a-booking
/api-reference/v2/openapi.json get /v2/bookings/{bookingUid}
`:bookingUid` can be
1. uid of a normal booking
2. uid of one of the recurring booking recurrences
3. uid of recurring booking which will return an array of all recurring booking recurrences (stored as recurringBookingUid on one of the individual recurrences).
If you are fetching a seated booking for an event type with 'show attendees' disabled, then to retrieve attendees in the response either set 'show attendees' to true on event type level or
you have to provide an authentication method of event type owner, host, team admin or owner or org admin or owner.
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
# Get a booking by seat UID
Source: https://cal.com/docs/api-reference/v2/bookings/get-a-booking-by-seat-uid
/api-reference/v2/openapi.json get /v2/bookings/by-seat/{seatUid}
Get a seated booking by its seat reference UID. This is useful when you have a seatUid from a seated booking and want to retrieve the full booking details.
If you are fetching a seated booking for an event type with 'show attendees' disabled, then to retrieve attendees in the response either set 'show attendees' to true on event type level or
you have to provide an authentication method of event type owner, host, team admin or owner or org admin or owner.
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
# Get 'Add to Calendar' links for a booking
Source: https://cal.com/docs/api-reference/v2/bookings/get-add-to-calendar-links-for-a-booking
/api-reference/v2/openapi.json get /v2/bookings/{bookingUid}/calendar-links
Retrieve calendar links for a booking that can be used to add the event to various calendar services. Returns links for Google Calendar, Microsoft Office, Microsoft Outlook, and a downloadable ICS file.
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
If accessed using an OAuth access token, the `BOOKING_READ` scope is required.
# Get all bookings
Source: https://cal.com/docs/api-reference/v2/bookings/get-all-bookings
/api-reference/v2/openapi.json get /v2/bookings
Cursor-based pagination. Pass the `pagination.nextCursor` from the previous response as the `cursor` query parameter to fetch the next page. Omit `cursor` to fetch the first page. `pagination.hasMore` is `false` and `pagination.nextCursor` is `null` when you've reached the last page.
Must pass `cal-api-version: 2026-05-01` header.
**`status` filters to a single status.** Pass one (e.g. `?status=upcoming`) or omit to walk all statuses. To list bookings across multiple statuses, issue parallel requests — one per status — and merge client-side.
The sort direction and the page-1 anchor are derived from `status`:
| `status` | Sort | Walk direction | Page-1 anchor (uuid bound) |
|---|---|---|---|
| _(omitted)_ | `Booking.uuid DESC` | Backward from far-future | upper bound at year 2100 |
| `upcoming` | `Booking.uuid ASC` | Forward in time | lower bound at `NOW() − 1h` |
| `recurring` | `Booking.uuid ASC` | Forward in time | lower bound at `NOW() − 1h` |
| `unconfirmed` | `Booking.uuid ASC` | Forward in time | lower bound at `NOW() − 1h` |
| `past` | `Booking.uuid DESC` | Backward in time | upper bound at `NOW()` |
| `cancelled` | `Booking.uuid DESC` | Backward from far-future | upper bound at year 2100 |
If accessed using an OAuth access token, the `BOOKING_READ` scope is required.
# Get all the recordings for the booking
Source: https://cal.com/docs/api-reference/v2/bookings/get-all-the-recordings-for-the-booking
/api-reference/v2/openapi.json get /v2/bookings/{bookingUid}/recordings
Fetches all the recordings for the booking `:bookingUid`. Requires authentication and proper authorization. Access is granted if you are the booking organizer, team admin or org admin/owner.
Recording download links are valid for 1 hour only. Make a new request to generate fresh links after expiration.
cal-api-version: `2026-02-25` is required in the request header.
If accessed using an OAuth access token, the `BOOKING_READ` scope is required.
# Get booking references
Source: https://cal.com/docs/api-reference/v2/bookings/get-booking-references
/api-reference/v2/openapi.json get /v2/bookings/{bookingUid}/references
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
If accessed using an OAuth access token, the `BOOKING_READ` scope is required.
# Get Cal Video real time transcript download links for the booking
Source: https://cal.com/docs/api-reference/v2/bookings/get-cal-video-real-time-transcript-download-links-for-the-booking
/api-reference/v2/openapi.json get /v2/bookings/{bookingUid}/transcripts
Fetches all the transcript download links for the booking `:bookingUid`
Transcripts are generated when clicking "Transcribe" during a Cal Video meeting. Download links are valid for 1 hour only - make a new request to generate fresh links after expiration.
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
If accessed using an OAuth access token, the `BOOKING_READ` scope is required.
# Get Video Meeting Sessions. Only supported for Cal Video
Source: https://cal.com/docs/api-reference/v2/bookings/get-video-meeting-sessions-only-supported-for-cal-video
/api-reference/v2/openapi.json get /v2/bookings/{bookingUid}/conferencing-sessions
Requires authentication and proper authorization. Access is granted if you are the booking organizer, team admin or org admin/owner.
cal-api-version: `2026-02-25` is required in the request header.This endpoint is rate-limited to 60 requests per minute per caller (per API key, access token, OAuth client, or IP). Exceeding the limit returns a 429 response and blocks further calls for 60 seconds.If the upstream conferencing provider rate-limits the request, this endpoint responds with HTTP 429 and a `Retry-After` header carrying the number of seconds to wait before retrying. Clients should honor this hint.
If accessed using an OAuth access token, the `BOOKING_READ` scope is required.
# Mark a booking absence
Source: https://cal.com/docs/api-reference/v2/bookings/mark-a-booking-absence
/api-reference/v2/openapi.json post /v2/bookings/{bookingUid}/mark-absent
The provided authorization header refers to the owner of the booking.
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
If accessed using an OAuth access token, the `BOOKING_WRITE` scope is required.
# Reassign a booking to a specific host
Source: https://cal.com/docs/api-reference/v2/bookings/reassign-a-booking-to-a-specific-host
/api-reference/v2/openapi.json post /v2/bookings/{bookingUid}/reassign/{userId}
Currently only supports reassigning host for round robin bookings. The provided authorization header refers to the owner of the booking.
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
If accessed using an OAuth access token, the `BOOKING_WRITE` scope is required.
# Reassign a booking to auto-selected host
Source: https://cal.com/docs/api-reference/v2/bookings/reassign-a-booking-to-auto-selected-host
/api-reference/v2/openapi.json post /v2/bookings/{bookingUid}/reassign
Currently only supports reassigning host for round robin bookings. The provided authorization header refers to the owner of the booking.
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
If accessed using an OAuth access token, the `BOOKING_WRITE` scope is required.
# Request to reschedule a booking
Source: https://cal.com/docs/api-reference/v2/bookings/request-to-reschedule-a-booking
/api-reference/v2/openapi.json post /v2/bookings/{bookingUid}/request-reschedule
Request to reschedule a booking. The booking will be cancelled and the attendee will receive an email with a link to reschedule.
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
If accessed using an OAuth access token, the `BOOKING_WRITE` scope is required.
# Reschedule a booking
Source: https://cal.com/docs/api-reference/v2/bookings/reschedule-a-booking
/api-reference/v2/openapi.json post /v2/bookings/{bookingUid}/reschedule
Reschedule a booking or seated booking.
Reschedulable booking statuses:
- `accepted` — the confirmed booking is moved to the new start time.
- `pending` — a booking awaiting host confirmation can be rescheduled. The new booking stays `pending` until the host confirms or declines it.
Non-reschedulable booking statuses (endpoint responds with `400 Bad Request`):
- `cancelled` — the booking has already been cancelled. If it was cancelled because it was previously rescheduled, the error message includes the UID of the booking it was rescheduled to.
- `rejected` — the host declined the original confirmation-required request. Create a new booking instead.
- `awaiting_host` — an instant meeting that is live and waiting for a host to join. Create a new booking instead.
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
# Update booking location for an existing booking
Source: https://cal.com/docs/api-reference/v2/bookings/update-booking-location-for-an-existing-booking
/api-reference/v2/openapi.json patch /v2/bookings/{bookingUid}/location
Updates the booking location in Cal.com and updates the corresponding calendar event via the organizer's connected credentials. For integration locations (e.g. Zoom, Google Meet, Cal Video), the endpoint also provisions a conference link. Attendees are notified of the location change by email.
The cal-api-version header is required for this endpoint. Without it, the request will fail with a 404 error.
If accessed using an OAuth access token, the `BOOKING_WRITE` scope is required.
# Get meeting details from calendar
Source: https://cal.com/docs/api-reference/v2/cal-unified-calendars/get-meeting-details-from-calendar
/api-reference/v2/openapi.json get /v2/calendars/{calendar}/event/{eventUid}
Returns detailed information about a meeting including attendance metrics. If accessed using an OAuth access token, the `APPS_READ` scope is required.
# Update meeting details in calendar
Source: https://cal.com/docs/api-reference/v2/cal-unified-calendars/update-meeting-details-in-calendar
/api-reference/v2/openapi.json patch /v2/calendars/{calendar}/events/{eventUid}
Updates event information in the specified calendar provider. If accessed using an OAuth access token, the `APPS_WRITE` scope is required.
# Check a calendar connection
Source: https://cal.com/docs/api-reference/v2/calendars/check-a-calendar-connection
/api-reference/v2/openapi.json get /v2/calendars/{calendar}/check
If accessed using an OAuth access token, the `APPS_READ` scope is required.
# Check an ICS feed
Source: https://cal.com/docs/api-reference/v2/calendars/check-an-ics-feed
/api-reference/v2/openapi.json get /v2/calendars/ics-feed/check
If accessed using an OAuth access token, the `APPS_READ` scope is required.
# Disconnect a calendar
Source: https://cal.com/docs/api-reference/v2/calendars/disconnect-a-calendar
/api-reference/v2/openapi.json post /v2/calendars/{calendar}/disconnect
If accessed using an OAuth access token, the `APPS_WRITE` scope is required.
# Get all calendars
Source: https://cal.com/docs/api-reference/v2/calendars/get-all-calendars
/api-reference/v2/openapi.json get /v2/calendars
If accessed using an OAuth access token, the `APPS_READ` scope is required.
# Get busy times
Source: https://cal.com/docs/api-reference/v2/calendars/get-busy-times
/api-reference/v2/openapi.json get /v2/calendars/busy-times
Get busy times from a calendar. Example request URL is `https://api.cal.com/v2/calendars/busy-times?timeZone=Europe%2FMadrid&dateFrom=2024-12-18&dateTo=2024-12-18&calendarsToLoad[0][credentialId]=135&calendarsToLoad[0][externalId]=skrauciz%40gmail.com`. Note: loggedInUsersTz is deprecated, use timeZone instead. If accessed using an OAuth access token, the `APPS_READ` scope is required.
# Get OAuth connect URL
Source: https://cal.com/docs/api-reference/v2/calendars/get-oauth-connect-url
/api-reference/v2/openapi.json get /v2/calendars/{calendar}/connect
If accessed using an OAuth access token, the `APPS_WRITE` scope is required.
# Save an ICS feed
Source: https://cal.com/docs/api-reference/v2/calendars/save-an-ics-feed
/api-reference/v2/openapi.json post /v2/calendars/ics-feed/save
If accessed using an OAuth access token, the `APPS_WRITE` scope is required.
# Save Apple calendar credentials
Source: https://cal.com/docs/api-reference/v2/calendars/save-apple-calendar-credentials
/api-reference/v2/openapi.json post /v2/calendars/{calendar}/credentials
If accessed using an OAuth access token, the `APPS_WRITE` scope is required.
# Save Google or Outlook calendar credentials
Source: https://cal.com/docs/api-reference/v2/calendars/save-google-or-outlook-calendar-credentials
/api-reference/v2/openapi.json get /v2/calendars/{calendar}/save
# Conferencing Apps
Source: https://cal.com/docs/api-reference/v2/conferencing-apps
Connect Zoom, Microsoft Teams or Google Meet to a user or a team
Every Cal.com booking gets a location. By default that is Cal Video, and you can point it at Zoom, Microsoft Teams or Google Meet instead once the app is connected to the user or team that owns the event type.
Which endpoint you use depends on whether the app needs an OAuth handshake:
| App | `app` slug | How to connect |
| --------------- | ------------- | ---------------------------------------------------- |
| Cal Video | `daily-video` | Always available — nothing to connect |
| Google Meet | `google-meet` | A single `POST /v2/conferencing/google-meet/connect` |
| Zoom | `zoom` | The OAuth redirect flow below |
| Microsoft Teams | `msteams` | The OAuth redirect flow below |
[`POST /v2/conferencing/{app}/connect`](/docs/api-reference/v2/conferencing/connect-your-conferencing-application) only accepts `google-meet`. Sending `zoom` or `msteams` to it returns `400` with `Invalid conferencing app. Available apps: GOOGLE_MEET.` — those apps use the OAuth flow instead.
## Authentication
Every request below takes an `Authorization: Bearer ` header, where the token is an API key prefixed with `cal_`, a managed user access token, or an OAuth access token.
**The credential is stored against whoever the token belongs to.** To connect Zoom for one of your managed users, call these endpoints with *that user's* access token, not with your API key. OAuth access tokens additionally need the `APPS_WRITE` scope to connect and `APPS_READ` to read.
## Connecting Zoom
Connecting Zoom is a redirect flow: you ask us for an authorization URL, send the user to it, and Zoom returns them to us so we can store the credential.
```bash theme={null}
curl -G 'https://api.cal.com/v2/conferencing/zoom/oauth/auth-url' \
--data-urlencode 'returnTo=https://example.com/settings/conferencing' \
--data-urlencode 'onErrorReturnTo=https://example.com/settings/conferencing?error=zoom' \
-H 'Authorization: Bearer '
```
```json theme={null}
{
"status": "success",
"data": {
"url": "https://zoom.us/oauth/authorize?response_type=code&client_id=...&redirect_uri=...&state=..."
}
}
```
`returnTo` and `onErrorReturnTo` are where we send the user once Zoom is done. Each must be a relative path (`/settings/conferencing`) or an absolute `http(s)` URL — protocol-relative values like `//example.com` and `javascript:` URLs are rejected.
The `state` embedded in that URL is signed and **expires 10 minutes after you request it**. Fetch the URL and redirect immediately — don't cache it, queue it, or email it to the user.
See [Get OAuth conferencing app auth URL](/docs/api-reference/v2/conferencing/get-oauth-conferencing-app-auth-url).
Navigate the user's browser to `data.url` — a top-level redirect, not a `fetch`. Zoom shows its consent screen and then redirects back to `https://api.cal.com/v2/conferencing/zoom/oauth/callback`.
On our hosted API that callback URL is already registered on Cal.com's Zoom app, so you do not configure a redirect URI. Either way, you should not call the callback endpoint yourself. Self-hosted instances register their own — see [Self-hosting](#self-hosting).
The [callback](/docs/api-reference/v2/conferencing/conferencing-app-oauth-callback) exchanges the code with Zoom, resolves the access token carried in `state` back to the user, stores the Zoom credential against them, and `301`s to your `returnTo`. If anything fails — the user denies consent, the state has expired — the user lands on `onErrorReturnTo` instead.
Reconnecting replaces the user's existing Zoom credential rather than adding a second one.
Connecting Zoom makes it *available* as a location. To make new bookings use it by default:
```bash theme={null}
curl -X POST 'https://api.cal.com/v2/conferencing/zoom/default' \
-H 'Authorization: Bearer '
```
This returns `400` with `zoom not connected.` if the previous steps didn't complete. Pass `daily-video` to switch back to Cal Video.
### Checking and removing the connection
* [`GET /v2/conferencing`](/docs/api-reference/v2/conferencing/list-your-conferencing-applications) lists the user's connected conferencing apps. An entry with `"invalid": true` means the stored credential stopped working and the user has to reconnect.
* [`GET /v2/conferencing/default`](/docs/api-reference/v2/conferencing/get-your-default-conferencing-application) returns the current default.
* [`DELETE /v2/conferencing/zoom/disconnect`](/docs/api-reference/v2/conferencing/disconnect-your-conferencing-application) removes it.
## Connecting Zoom for a team
Team-owned conferencing apps follow exactly the same flow, against the organization endpoints:
```
GET /v2/organizations/{orgId}/teams/{teamId}/conferencing/zoom/oauth/auth-url
GET /v2/organizations/{orgId}/teams/{teamId}/conferencing
GET /v2/organizations/{orgId}/teams/{teamId}/conferencing/default
POST /v2/organizations/{orgId}/teams/{teamId}/conferencing/zoom/default
DELETE /v2/organizations/{orgId}/teams/{teamId}/conferencing/zoom/disconnect
```
The caller must be a **team admin**. The PBAC permission depends on the endpoint: listing the team's apps and reading its default need `team.read`, while requesting an auth URL, setting a default and disconnecting need `team.update`. For OAuth access tokens the scope is `TEAM_APPS_WRITE` to connect, set a default or disconnect, and `TEAM_APPS_READ` to list the team's apps or read its default. See [Access Control](/docs/api-reference/v2/access-control).
For **platform organizations** these endpoints additionally require the `ESSENTIALS` plan or above; a platform organization on a lower plan — or with no subscription — gets a `403`. Organizations that are not platform organizations are unaffected by the plan check.
Zoom still redirects to the same Cal.com-registered callback. The organization and team ids travel inside the signed `state`, which is how the credential ends up on the team instead of on the admin who authorized it.
## Connecting Google Meet
Google Meet needs no OAuth handshake of its own, because it rides on the user's Google Calendar connection:
```bash theme={null}
curl -X POST 'https://api.cal.com/v2/conferencing/google-meet/connect' \
-H 'Authorization: Bearer '
```
The user (or team) must already have a working Google Calendar connection — otherwise this returns `400` with `Google Meet requires a Google Calendar connection.`, or asks for a reconnect if the existing calendar credential has gone invalid. Calling it twice returns `400` with `Google Meet is already connected for this user.`
## Self-hosting
On Cal.com's hosted API the Zoom and Microsoft Teams OAuth apps are ours and nothing needs provisioning. On a self-hosted instance you must configure the app's client id and secret from the admin apps page first — without them the auth-url request returns `404`, with `Zoom app not found` for `zoom` and `Office365 app not found` for `msteams`.
The OAuth callback is built from your API base URL rather than hardcoded, so it is `/v2/conferencing/{app}/oauth/callback` — `https://api.cal.com/v2/conferencing/zoom/oauth/callback` on our hosted API, and your own host elsewhere. Register that exact URL as the redirect URI on the Zoom or Microsoft app you provision, otherwise the handshake fails when the provider redirects back.
# Conferencing app OAuth callback
Source: https://cal.com/docs/api-reference/v2/conferencing/conferencing-app-oauth-callback
/api-reference/v2/openapi.json get /v2/conferencing/{app}/oauth/callback
# Connect your conferencing application
Source: https://cal.com/docs/api-reference/v2/conferencing/connect-your-conferencing-application
/api-reference/v2/openapi.json post /v2/conferencing/{app}/connect
Only `google-meet` can be connected this way, and only for a user who already has a valid Google Calendar connection. Zoom and Microsoft Teams require an OAuth handshake - start it with `GET /v2/conferencing/{app}/oauth/auth-url` instead. Walkthrough: https://cal.com/docs/api-reference/v2/conferencing-apps. If accessed using an OAuth access token, the `APPS_WRITE` scope is required.
# Disconnect your conferencing application
Source: https://cal.com/docs/api-reference/v2/conferencing/disconnect-your-conferencing-application
/api-reference/v2/openapi.json delete /v2/conferencing/{app}/disconnect
If accessed using an OAuth access token, the `APPS_WRITE` scope is required.
# Get OAuth conferencing app auth URL
Source: https://cal.com/docs/api-reference/v2/conferencing/get-oauth-conferencing-app-auth-url
/api-reference/v2/openapi.json get /v2/conferencing/{app}/oauth/auth-url
Returns the URL to send the user to so they can authorize Zoom or Microsoft Teams. Redirect the user's browser to it - the provider then returns them to a callback already registered by Cal.com, which stores the credential against the owner of the access token that started the flow. `returnTo` and `onErrorReturnTo` must each be a relative path or an absolute http(s) URL. The returned URL expires 10 minutes after this call, so redirect immediately rather than caching it. Walkthrough: https://cal.com/docs/api-reference/v2/conferencing-apps. If accessed using an OAuth access token, the `APPS_WRITE` scope is required.
# Get your default conferencing application
Source: https://cal.com/docs/api-reference/v2/conferencing/get-your-default-conferencing-application
/api-reference/v2/openapi.json get /v2/conferencing/default
If accessed using an OAuth access token, the `APPS_READ` scope is required.
# List your conferencing applications
Source: https://cal.com/docs/api-reference/v2/conferencing/list-your-conferencing-applications
/api-reference/v2/openapi.json get /v2/conferencing
If accessed using an OAuth access token, the `APPS_READ` scope is required.
# Set your default conferencing application
Source: https://cal.com/docs/api-reference/v2/conferencing/set-your-default-conferencing-application
/api-reference/v2/openapi.json post /v2/conferencing/{app}/default
If accessed using an OAuth access token, the `APPS_WRITE` scope is required.
# Charge credits
Source: https://cal.com/docs/api-reference/v2/credits/charge-credits
/api-reference/v2/openapi.json post /v2/credits/charge
Charge credits for an authenticated user. Uses externalRef for idempotency to prevent double-charging. Third-party OAuth tokens require the `CREDITS_WRITE` scope.
# Check available credits
Source: https://cal.com/docs/api-reference/v2/credits/check-available-credits
/api-reference/v2/openapi.json get /v2/credits/available
Check if the authenticated user (or their org/team) has available credits and return the current balance. Third-party OAuth tokens require the `CREDITS_READ` scope.
# Create a managed user
Source: https://cal.com/docs/api-reference/v2/deprecated:-platform-managed-users/create-a-managed-user
/api-reference/v2/openapi.json post /v2/oauth-clients/{clientId}/users
These endpoints are deprecated and will be removed in the future.
# Delete a managed user
Source: https://cal.com/docs/api-reference/v2/deprecated:-platform-managed-users/delete-a-managed-user
/api-reference/v2/openapi.json delete /v2/oauth-clients/{clientId}/users/{userId}
These endpoints are deprecated and will be removed in the future.
# Force refresh tokens
Source: https://cal.com/docs/api-reference/v2/deprecated:-platform-managed-users/force-refresh-tokens
/api-reference/v2/openapi.json post /v2/oauth-clients/{clientId}/users/{userId}/force-refresh
These endpoints are deprecated and will be removed in the future. If you have lost managed user access or refresh token, then you can get new ones by using OAuth credentials. Access token is valid for 60 minutes and refresh token for 1 year. Make sure to store them in your database, for example, in your User database model `calAccessToken` and `calRefreshToken` fields.
Response also contains `accessTokenExpiresAt` and `refreshTokenExpiresAt` fields, but if you decode the jwt token the payload will contain `clientId` (OAuth client ID), `ownerId` (user to whom token belongs ID), `iat` (issued at time) and `expiresAt` (when does the token expire) fields.
# Get a managed user
Source: https://cal.com/docs/api-reference/v2/deprecated:-platform-managed-users/get-a-managed-user
/api-reference/v2/openapi.json get /v2/oauth-clients/{clientId}/users/{userId}
These endpoints are deprecated and will be removed in the future.
# Get all managed users
Source: https://cal.com/docs/api-reference/v2/deprecated:-platform-managed-users/get-all-managed-users
/api-reference/v2/openapi.json get /v2/oauth-clients/{clientId}/users
These endpoints are deprecated and will be removed in the future.
# Refresh managed user tokens
Source: https://cal.com/docs/api-reference/v2/deprecated:-platform-managed-users/refresh-managed-user-tokens
/api-reference/v2/openapi.json post /v2/oauth/{clientId}/refresh
These endpoints are deprecated and will be removed in the future. If managed user access token is expired then get a new one using this endpoint - it will also refresh the refresh token, because we use
"refresh token rotation" mechanism. Access token is valid for 60 minutes and refresh token for 1 year. Make sure to store them in your database, for example, in your User database model `calAccessToken` and `calRefreshToken` fields.
Response also contains `accessTokenExpiresAt` and `refreshTokenExpiresAt` fields, but if you decode the jwt token the payload will contain `clientId` (OAuth client ID), `ownerId` (user to whom token belongs ID), `iat` (issued at time) and `expiresAt` (when does the token expire) fields.
# Update a managed user
Source: https://cal.com/docs/api-reference/v2/deprecated:-platform-managed-users/update-a-managed-user
/api-reference/v2/openapi.json patch /v2/oauth-clients/{clientId}/users/{userId}
These endpoints are deprecated and will be removed in the future.
# Create an OAuth client
Source: https://cal.com/docs/api-reference/v2/deprecated:-platform-oauth-clients/create-an-oauth-client
/api-reference/v2/openapi.json post /v2/oauth-clients
These endpoints are deprecated and will be removed in the future.
# Delete an OAuth client
Source: https://cal.com/docs/api-reference/v2/deprecated:-platform-oauth-clients/delete-an-oauth-client
/api-reference/v2/openapi.json delete /v2/oauth-clients/{clientId}
These endpoints are deprecated and will be removed in the future.
# Get all OAuth clients
Source: https://cal.com/docs/api-reference/v2/deprecated:-platform-oauth-clients/get-all-oauth-clients
/api-reference/v2/openapi.json get /v2/oauth-clients
These endpoints are deprecated and will be removed in the future.
# Get an OAuth client
Source: https://cal.com/docs/api-reference/v2/deprecated:-platform-oauth-clients/get-an-oauth-client
/api-reference/v2/openapi.json get /v2/oauth-clients/{clientId}
These endpoints are deprecated and will be removed in the future.
# Update an OAuth client
Source: https://cal.com/docs/api-reference/v2/deprecated:-platform-oauth-clients/update-an-oauth-client
/api-reference/v2/openapi.json patch /v2/oauth-clients/{clientId}
These endpoints are deprecated and will be removed in the future.
# Create a webhook
Source: https://cal.com/docs/api-reference/v2/deprecated:-platform-webhooks/create-a-webhook
/api-reference/v2/openapi.json post /v2/oauth-clients/{clientId}/webhooks
These endpoints are deprecated and will be removed in the future.
# Delete a webhook
Source: https://cal.com/docs/api-reference/v2/deprecated:-platform-webhooks/delete-a-webhook
/api-reference/v2/openapi.json delete /v2/oauth-clients/{clientId}/webhooks/{webhookId}
These endpoints are deprecated and will be removed in the future.
# Delete all webhooks
Source: https://cal.com/docs/api-reference/v2/deprecated:-platform-webhooks/delete-all-webhooks
/api-reference/v2/openapi.json delete /v2/oauth-clients/{clientId}/webhooks
These endpoints are deprecated and will be removed in the future.
# Get a webhook
Source: https://cal.com/docs/api-reference/v2/deprecated:-platform-webhooks/get-a-webhook
/api-reference/v2/openapi.json get /v2/oauth-clients/{clientId}/webhooks/{webhookId}
These endpoints are deprecated and will be removed in the future.
# Get all webhooks
Source: https://cal.com/docs/api-reference/v2/deprecated:-platform-webhooks/get-all-webhooks
/api-reference/v2/openapi.json get /v2/oauth-clients/{clientId}/webhooks
These endpoints are deprecated and will be removed in the future.
# Update a webhook
Source: https://cal.com/docs/api-reference/v2/deprecated:-platform-webhooks/update-a-webhook
/api-reference/v2/openapi.json patch /v2/oauth-clients/{clientId}/webhooks/{webhookId}
These endpoints are deprecated and will be removed in the future.
# Update destination calendars
Source: https://cal.com/docs/api-reference/v2/destination-calendars/update-destination-calendars
/api-reference/v2/openapi.json put /v2/destination-calendars
If accessed using an OAuth access token, the `APPS_WRITE` scope is required.
# Create a private link for an event type
Source: https://cal.com/docs/api-reference/v2/event-types-private-links/create-a-private-link-for-an-event-type
/api-reference/v2/openapi.json post /v2/event-types/{eventTypeId}/private-links
If accessed using an OAuth access token, the `EVENT_TYPE_WRITE` scope is required.
# Delete a private link for an event type
Source: https://cal.com/docs/api-reference/v2/event-types-private-links/delete-a-private-link-for-an-event-type
/api-reference/v2/openapi.json delete /v2/event-types/{eventTypeId}/private-links/{linkId}
If accessed using an OAuth access token, the `EVENT_TYPE_WRITE` scope is required.
# Get all private links for an event type
Source: https://cal.com/docs/api-reference/v2/event-types-private-links/get-all-private-links-for-an-event-type
/api-reference/v2/openapi.json get /v2/event-types/{eventTypeId}/private-links
If accessed using an OAuth access token, the `EVENT_TYPE_READ` scope is required.
# Update a private link for an event type
Source: https://cal.com/docs/api-reference/v2/event-types-private-links/update-a-private-link-for-an-event-type
/api-reference/v2/openapi.json patch /v2/event-types/{eventTypeId}/private-links/{linkId}
If accessed using an OAuth access token, the `EVENT_TYPE_WRITE` scope is required.
# Create a webhook
Source: https://cal.com/docs/api-reference/v2/event-types-webhooks/create-a-webhook
/api-reference/v2/openapi.json post /v2/event-types/{eventTypeId}/webhooks
If accessed using an OAuth access token, the `EVENT_TYPE_WRITE` scope is required.
# Delete a webhook
Source: https://cal.com/docs/api-reference/v2/event-types-webhooks/delete-a-webhook
/api-reference/v2/openapi.json delete /v2/event-types/{eventTypeId}/webhooks/{webhookId}
If accessed using an OAuth access token, the `EVENT_TYPE_WRITE` scope is required.
# Delete all webhooks
Source: https://cal.com/docs/api-reference/v2/event-types-webhooks/delete-all-webhooks
/api-reference/v2/openapi.json delete /v2/event-types/{eventTypeId}/webhooks
If accessed using an OAuth access token, the `EVENT_TYPE_WRITE` scope is required.
# Get a webhook
Source: https://cal.com/docs/api-reference/v2/event-types-webhooks/get-a-webhook
/api-reference/v2/openapi.json get /v2/event-types/{eventTypeId}/webhooks/{webhookId}
If accessed using an OAuth access token, the `EVENT_TYPE_READ` scope is required.
# Get all webhooks
Source: https://cal.com/docs/api-reference/v2/event-types-webhooks/get-all-webhooks
/api-reference/v2/openapi.json get /v2/event-types/{eventTypeId}/webhooks
If accessed using an OAuth access token, the `EVENT_TYPE_READ` scope is required.
# Update a webhook
Source: https://cal.com/docs/api-reference/v2/event-types-webhooks/update-a-webhook
/api-reference/v2/openapi.json patch /v2/event-types/{eventTypeId}/webhooks/{webhookId}
If accessed using an OAuth access token, the `EVENT_TYPE_WRITE` scope is required.
# Add custom booking fields
Source: https://cal.com/docs/api-reference/v2/event-types/add-custom-booking-fields
/api-reference/v2/openapi.json post /v2/event-types/{eventTypeId}/booking-fields
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Adds custom booking fields. Default and workflow-added fields cannot be added through this endpoint. If accessed using an OAuth access token, the `EVENT_TYPE_WRITE` scope is required.
# Create an event type
Source: https://cal.com/docs/api-reference/v2/event-types/create-an-event-type
/api-reference/v2/openapi.json post /v2/event-types
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section.
If `bookingFields` is omitted, the event type is created with its default booking fields. At least one contact field—`email` or `attendeePhoneNumber`—must remain visible and required. Other default fields, such as additional notes, can be hidden. Provide `bookingFields` to customize the default fields or add custom booking fields. If accessed using an OAuth access token, the `EVENT_TYPE_WRITE` scope is required.
# Delete a custom booking field
Source: https://cal.com/docs/api-reference/v2/event-types/delete-a-custom-booking-field
/api-reference/v2/openapi.json delete /v2/event-types/{eventTypeId}/booking-fields/{slug}
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Deletes the custom booking field identified by `slug`. Default fields cannot be deleted, set hidden instead. If accessed using an OAuth access token, the `EVENT_TYPE_WRITE` scope is required.
# Delete an event type
Source: https://cal.com/docs/api-reference/v2/event-types/delete-an-event-type
/api-reference/v2/openapi.json delete /v2/event-types/{eventTypeId}
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
If accessed using an OAuth access token, the `EVENT_TYPE_WRITE` scope is required.
# Get an event type
Source: https://cal.com/docs/api-reference/v2/event-types/get-an-event-type
/api-reference/v2/openapi.json get /v2/event-types/{eventTypeId}
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Returns hidden booking fields only to callers who are allowed to view them. If accessed using an OAuth access token, the `EVENT_TYPE_READ` scope is required.
# Get booking fields
Source: https://cal.com/docs/api-reference/v2/event-types/get-booking-fields
/api-reference/v2/openapi.json get /v2/event-types/{eventTypeId}/booking-fields
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Returns default and custom booking fields, plus any read-only workflow-added fields. If accessed using an OAuth access token, the `EVENT_TYPE_READ` scope is required.
# Get event type history
Source: https://cal.com/docs/api-reference/v2/event-types/get-event-type-history
/api-reference/v2/openapi.json get /v2/event-types/{eventTypeId}/history
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
If accessed using an OAuth access token, the `EVENT_TYPE_READ` scope is required.
Only the event type owner and applicable administrators can view its history. General event type read access does not include access to its history.
# Get scheduling configuration
Source: https://cal.com/docs/api-reference/v2/event-types/get-scheduling-configuration
/api-reference/v2/openapi.json get /v2/event-types/{eventTypeId}/scheduling-config
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
The response varies by the event type's scheduling type, and non-team event types are rejected.
If accessed using an OAuth access token, the `EVENT_TYPE_READ` scope is required.
# List CRM sync errors for an event type
Source: https://cal.com/docs/api-reference/v2/event-types/list-crm-sync-errors-for-an-event-type
/api-reference/v2/openapi.json get /v2/event-types/{eventTypeId}/crm-sync-errors
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
By default, only active CRM sync errors are returned; pass `includeDismissed=true` to include dismissed errors.
If accessed using an OAuth access token, the `EVENT_TYPE_READ` scope is required.
# List event types
Source: https://cal.com/docs/api-reference/v2/event-types/list-event-types
/api-reference/v2/openapi.json get /v2/event-types
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Hidden event types and hidden booking fields are returned only when listing your own event types. Other callers see public event types and visible booking fields.
# Replace booking fields
Source: https://cal.com/docs/api-reference/v2/event-types/replace-booking-fields
/api-reference/v2/openapi.json put /v2/event-types/{eventTypeId}/booking-fields
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Replaces all booking fields. Omitted custom fields are deleted, while omitted default fields are restored with their default settings. Use `PATCH` to update specific fields. Workflow-added fields are read-only and ignored. If accessed using an OAuth access token, the `EVENT_TYPE_WRITE` scope is required.
# Update an event type
Source: https://cal.com/docs/api-reference/v2/event-types/update-an-event-type
/api-reference/v2/openapi.json patch /v2/event-types/{eventTypeId}
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Omit `bookingFields` to keep the existing booking fields. If provided, the array replaces them, and any omitted default fields are restored with their default settings. If accessed using an OAuth access token, the `EVENT_TYPE_WRITE` scope is required.
# Update booking fields
Source: https://cal.com/docs/api-reference/v2/event-types/update-booking-fields
/api-reference/v2/openapi.json patch /v2/event-types/{eventTypeId}/booking-fields
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Updates the booking fields identified by `slug`. Omitted properties keep their current values. Workflow-added fields are read-only. If accessed using an OAuth access token, the `EVENT_TYPE_WRITE` scope is required.
# Get an Event
Source: https://cal.com/docs/api-reference/v2/events/get-an-event
/api-reference/v2/openapi.json get /v2/events/{uuid}
Returns one Event by UUID. The authenticated user must be its owner, one of its hosts, an accepted co-host, or a member of the owning team with permission to read its event types. Events are fixed-date RSVP pages served at cal.com/{slug}; they are not event types (bookable meeting templates) or calendar events.
# List Events
Source: https://cal.com/docs/api-reference/v2/events/list-events
/api-reference/v2/openapi.json get /v2/events
Returns the Events the authenticated user hosts: Events they own, plus Events they have accepted a co-host invitation to. A co-hosted Event may be owned by a team, so a team-owned Event can appear here — but team-owned Events the user neither hosts nor co-hosts are not returned by this endpoint. Events are fixed-date RSVP pages served at cal.com/{slug}; they are not event types (bookable meeting templates) or calendar events.
# Get average booking duration
Source: https://cal.com/docs/api-reference/v2/insights/get-average-booking-duration
/api-reference/v2/openapi.json post /v2/insights/bookings/average-duration
Get average booking duration for user, team, or organization insights.
# Get booking event trends
Source: https://cal.com/docs/api-reference/v2/insights/get-booking-event-trends
/api-reference/v2/openapi.json post /v2/insights/bookings/event-trends
Get booking trend stats for user, team, or organization insights.
# Get booking KPI stats
Source: https://cal.com/docs/api-reference/v2/insights/get-booking-kpi-stats
/api-reference/v2/openapi.json post /v2/insights/bookings/kpi-stats
Get booking KPI stats for user, team, or organization insights.
# Get booking member stats
Source: https://cal.com/docs/api-reference/v2/insights/get-booking-member-stats
/api-reference/v2/openapi.json post /v2/insights/bookings/members
Get booking member stats for user, team, or organization insights.
# Get failed bookings by routing field
Source: https://cal.com/docs/api-reference/v2/insights/get-failed-bookings-by-routing-field
/api-reference/v2/openapi.json post /v2/insights/routings/failed-bookings-by-field
Get failed booking counts grouped by routing form field for user, team, or organization insights.
# Get routed-to users per period
Source: https://cal.com/docs/api-reference/v2/insights/get-routed-to-users-per-period
/api-reference/v2/openapi.json post /v2/insights/routings/routed-to-per-period
Get routed-to booking counts by user and period for user, team, or organization insights.
# Get routing form field options
Source: https://cal.com/docs/api-reference/v2/insights/get-routing-form-field-options
/api-reference/v2/openapi.json post /v2/insights/routings/form-field-options
Get routing form fields and options for user, team, or organization insights.
# Get routing form response headers
Source: https://cal.com/docs/api-reference/v2/insights/get-routing-form-response-headers
/api-reference/v2/openapi.json post /v2/insights/routings/form-response-headers
Get routing form response headers for user, team, or organization insights.
# Get routing form responses
Source: https://cal.com/docs/api-reference/v2/insights/get-routing-form-responses
/api-reference/v2/openapi.json post /v2/insights/routings/form-responses
Get paginated routing form responses for user, team, or organization insights.
# Get routing forms by status
Source: https://cal.com/docs/api-reference/v2/insights/get-routing-forms-by-status
/api-reference/v2/openapi.json post /v2/insights/routings/forms-by-status
Get routing form response counts by booking status for user, team, or organization insights.
# Introduction to API v2
Source: https://cal.com/docs/api-reference/v2/introduction
Introduction to Cal.com API v2 endpoints
## Authentication
The Cal.com API has 3 authentication methods:
1. OAuth
2. API key
3. Platform (Deprecated)
### 1. Create an OAuth client and "Continue with Cal.com"
In order to be listed as an official partner and App in our App Store: cal.com/apps you need to create and get a verified OAuth client.
You can request it here: [https://cal.com/docs/api-reference/v2/oauth](https://cal.com/docs/api-reference/v2/oauth).
### 2. API key
While API keys can be created easily, bear in mind we almost always recommend using OAuth credentials, especially when building integrations or applications with Cal.com.
You can view and manage your API keys in your settings page under the security tab in Cal.com.
API Keys are under Settings > Security
Test mode secret keys have the prefix `cal_` and live mode secret keys have the prefix `cal_live_`.
Your API keys carry many privileges, so be sure to keep them secure! Do not share your secret API keys in publicly accessible areas such as GitHub, client-side code, and so forth.
Authentication to the API is performed via the Authorization header. For example, the request would go something like:
```
'Authorization': 'Bearer YOUR_API_KEY'
```
in your request header.
All API requests must be made over HTTPS. Calls made over plain HTTP will fail. API requests without authentication will also fail.
## Teams endpoints
Teams customers have all the endpoints except the ones prefixed with "Platform" and "Orgs".
## Organizations endpoints
Organizations customers have all the endpoints except the ones prefixed with "Platform" and "Teams" and "Orgs / Orgs" because
children organizations are only allowed in the platform plan right now.
## Rate limits
There are three authentication methods for the API, and each of them has the following rate limits:
1. API Key - 120 requests per minute. This can be increased to a reasonable amount, such as 200 requests per minute. If you require a higher rate limit, such as 800 requests per minute, it is possible, but it may involve extra charges. To request this, please contact support.
If no authentication method is provided, the default rate limit is 120 requests per minute.
## Deprecated & Maintenance for existing users only
As of 15th December 2025, we're currently undergoing a restructuring of our "Platform"-offering. Until further we continue to provide enterprise support for existing customers but no longer offer new signups for any "Platform" plan.
### 2. Platform OAuth client credentials
You need to use OAuth credentials when:
1. Managing managed users [API reference](https://cal.com/docs/api-reference/v2/platform-managed-users/create-a-managed-user)
2. Creating OAuth client webhooks [API reference](https://cal.com/docs/api-reference/v2/platform-webhooks/create-a-webhook)
3. Refreshing tokens of a managed user [API reference](https://cal.com/docs/api-reference/v2/platform-managed-users/refresh-managed-user-tokens)
4. Teams related endpoints: Managing organization teams [API reference](https://cal.com/docs/api-reference/v2/orgs-teams/create-a-team), adding managed users as members to teams [API reference](https://cal.com/docs/api-reference/v2/orgs-teams-memberships/create-a-membership), creating team event types [API reference](https://cal.com/docs/api-reference/v2/orgs-event-types/create-an-event-type).
OAuth credentials can be accessed in the platform dashboard [https://app.cal.com/settings/platform](https://app.cal.com/settings/platform) after you have created an OAuth client. Each one has an ID and secret. You then need to pass them as request headers:
1. `x-cal-client-id` - ID of the OAuth client.
2. `x-cal-secret-key` - secret of the OAuth client.
### 3. Platform Managed user access token
After you create a managed user you will receive its access and refresh tokens. The response also includes managed user's id, so we recommend you to add new properties to your users table calAccessToken, calRefreshToken and calManagedUserId to store this information.
You need to use access token when managing managed user's:
1. Schedules [API reference](https://cal.com/docs/api-reference/v2/schedules/create-a-schedule)
2. Event types [API reference](https://cal.com/docs/api-reference/v2/event-types/create-an-event-type)
3. Bookings - some endpoints like creating a booking is public, but some like getting all managed user's bookings require managed user's access token [API reference](https://cal.com/docs/api-reference/v2/bookings/get-all-bookings)
It is passed as an authorization bearer request header Authorization: Bearer \.
Validity period: access tokens are valid for 60 minutes and refresh tokens for 1 year, and tokens can be refreshed using the refresh endpoint [API reference](https://cal.com/docs/api-reference/v2/oauth/post-v2oauth-refresh). After refreshing you will receive the new access and refresh tokens that you have to store in your database.
Recovering tokens: if you ever lose managed user's access or refresh tokens, you can force refresh them using the OAuth client credentials and store them in your database [API reference](https://cal.com/docs/api-reference/v2/platform-managed-users/force-refresh-tokens).
## Platform endpoints
Platform customers have the following endpoints available:
1. Endpoints prefixed with "Platform".
2. Endpoints with no prefix e.g "Bookings", "Event Types".
3. If you are at least on the ESSENTIALS plan, then all endpoints prefixed with "Orgs" except "Orgs / Attributes", "Orgs / Attributes / Options" and "Orgs / Teams / Routing forms / Responses".
# Create an organization within an organization
Source: https://cal.com/docs/api-reference/v2/managed-orgs/create-an-organization-within-an-organization
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/organizations
For platform, the plan must be 'SCALE' or higher to access this endpoint. Required membership role: `org admin`. PBAC permission: `organization.create`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Delete an organization within an organization
Source: https://cal.com/docs/api-reference/v2/managed-orgs/delete-an-organization-within-an-organization
/api-reference/v2/openapi.json delete /v2/organizations/{orgId}/organizations/{managedOrganizationId}
For platform, the plan must be 'SCALE' or higher to access this endpoint. Required membership role: `org admin`. PBAC permission: `organization.delete`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Get all organizations within an organization
Source: https://cal.com/docs/api-reference/v2/managed-orgs/get-all-organizations-within-an-organization
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/organizations
For platform, the plan must be 'SCALE' or higher to access this endpoint. Required membership role: `org admin`. PBAC permission: `organization.readManagedOrganizations`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Get an organization within an organization
Source: https://cal.com/docs/api-reference/v2/managed-orgs/get-an-organization-within-an-organization
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/organizations/{managedOrganizationId}
For platform, the plan must be 'SCALE' or higher to access this endpoint. Required membership role: `org admin`. PBAC permission: `organization.readManagedOrganizations`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Update an organization within an organization
Source: https://cal.com/docs/api-reference/v2/managed-orgs/update-an-organization-within-an-organization
/api-reference/v2/openapi.json patch /v2/organizations/{orgId}/organizations/{managedOrganizationId}
For platform, the plan must be 'SCALE' or higher to access this endpoint. Required membership role: `org admin`. PBAC permission: `organization.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Clear my booking limit for one team
Source: https://cal.com/docs/api-reference/v2/me/clear-my-booking-limit-for-one-team
/api-reference/v2/openapi.json delete /v2/me/team-booking-limits/{teamId}
Drops your own numbers for this team so the team default applies to you again. This does not lift the team's limit. If accessed using an OAuth access token, the `PROFILE_WRITE` scope is required.
# Clear my booking limits
Source: https://cal.com/docs/api-reference/v2/me/clear-my-booking-limits
/api-reference/v2/openapi.json delete /v2/me/booking-limits
Removes all of the authenticated user's global booking limits. Only available to organization members — non-org accounts receive a 403. If accessed using an OAuth access token, the `PROFILE_WRITE` scope is required.
# Get my booking limits
Source: https://cal.com/docs/api-reference/v2/me/get-my-booking-limits
/api-reference/v2/openapi.json get /v2/me/booking-limits
Returns the authenticated user's global booking limits. Unset bounds are returned as null. Only available to organization members — non-org accounts receive a 403. If accessed using an OAuth access token, the `PROFILE_READ` scope is required.
# Get my profile
Source: https://cal.com/docs/api-reference/v2/me/get-my-profile
/api-reference/v2/openapi.json get /v2/me
If accessed using an OAuth access token, the `PROFILE_READ` scope is required.
# Get my team booking limits
Source: https://cal.com/docs/api-reference/v2/me/get-my-team-booking-limits
/api-reference/v2/openapi.json get /v2/me/team-booking-limits
Returns every team whose booking limit applies to you as a round-robin host, with the team's default, your own number where you set one, and what is enforced. Teams you host for that set no limit are omitted. Your own number only applies where you are a round-robin host: on collective events and events where you are a fixed host the team default keeps applying to you. If accessed using an OAuth access token, the `PROFILE_READ` scope is required.
# Set my booking limit for one team
Source: https://cal.com/docs/api-reference/v2/me/set-my-booking-limit-for-one-team
/api-reference/v2/openapi.json patch /v2/me/team-booking-limits/{teamId}
Sets your own numbers for this team's booking limit. Intervals you omit keep what you stored before, an interval sent as `null` goes back to inheriting the team default, and every value is capped at your own overall limit for the same interval. Only teams you are a round-robin host of accept this; the number is enforced on that team's round-robin events only, while collective events and events where you are a fixed host keep the team default. If accessed using an OAuth access token, the `PROFILE_WRITE` scope is required.
# Update my booking limits
Source: https://cal.com/docs/api-reference/v2/me/update-my-booking-limits
/api-reference/v2/openapi.json patch /v2/me/booking-limits
Partially updates the authenticated user's global booking limits. Only fields present in the request body are changed; omit a field to leave it untouched, or set it to null to remove that limit. Only available to organization members — non-org accounts receive a 403. If accessed using an OAuth access token, the `PROFILE_WRITE` scope is required.
# Update my profile
Source: https://cal.com/docs/api-reference/v2/me/update-my-profile
/api-reference/v2/openapi.json patch /v2/me
Updates the authenticated user's profile. Email changes require verification and the primary email stays unchanged until verification completes, unless the new email is already a verified secondary email or the user is platform-managed. If accessed using an OAuth access token, the `PROFILE_WRITE` scope is required.
# Create a Slack push linking intent
Source: https://cal.com/docs/api-reference/v2/notifications/create-a-slack-push-linking-intent
/api-reference/v2/openapi.json post /v2/notifications/subscriptions/slack/link-intents
# Create a Telegram push linking intent
Source: https://cal.com/docs/api-reference/v2/notifications/create-a-telegram-push-linking-intent
/api-reference/v2/openapi.json post /v2/notifications/subscriptions/telegram/link-intents
# Register an app push subscription
Source: https://cal.com/docs/api-reference/v2/notifications/register-an-app-push-subscription
/api-reference/v2/openapi.json post /v2/notifications/subscriptions/app-push
# Remove a Slack push subscription
Source: https://cal.com/docs/api-reference/v2/notifications/remove-a-slack-push-subscription
/api-reference/v2/openapi.json delete /v2/notifications/subscriptions/slack
# Remove a Telegram push subscription
Source: https://cal.com/docs/api-reference/v2/notifications/remove-a-telegram-push-subscription
/api-reference/v2/openapi.json delete /v2/notifications/subscriptions/telegram
# Remove an app push subscription
Source: https://cal.com/docs/api-reference/v2/notifications/remove-an-app-push-subscription
/api-reference/v2/openapi.json delete /v2/notifications/subscriptions/app-push
# OAuth
Source: https://cal.com/docs/api-reference/v2/oauth
Authorize apps with Cal.com accounts using OAuth
#### Get your OAuth "Continue with [Cal.com](http://Cal.com)" Badge
* [https://app.cal.com/continue-with-calcom-coss-ui.svg](https://app.cal.com/continue-with-calcom-coss-ui.svg)
* [https://app.cal.com/continue-with-calcom-dark-rounded.svg](https://app.cal.com/continue-with-calcom-dark-rounded.svg)
* [https://app.cal.com/continue-with-calcom-dark-squared.svg](https://app.cal.com/continue-with-calcom-dark-squared.svg)
* [https://app.cal.com/continue-with-calcom-light-rounded.svg](https://app.cal.com/continue-with-calcom-light-rounded.svg)
* [https://app.cal.com/continue-with-calcom-light-squared.svg](https://app.cal.com/continue-with-calcom-light-squared.svg)
* [https://app.cal.com/continue-with-calcom-neutral-rounded.svg](https://app.cal.com/continue-with-calcom-neutral-rounded.svg)
* [https://app.cal.com/continue-with-calcom-light-squared.svg](https://app.cal.com/continue-with-calcom-light-squared.svg)
## 1. OAuth Client Credentials
You can create an OAuth client via the following page [https://app.cal.com/settings/developer/oauth](https://app.cal.com/settings/developer/oauth). The OAuth client will be in a "pending" state
and not yet ready to use. You must select at least one scope when creating the OAuth client. You can register up to 10 redirect URIs per OAuth client.
An admin from Cal.com will then review your OAuth client and you will receive an email if it was accepted or rejected. If it was accepted then your OAuth client
is ready to be used.
### Available Scopes
Scopes control which API endpoints the OAuth token can access. Once a user authorizes your client with a given set of scopes, the issued access token can only be used to call endpoints covered by those scopes — any request to an endpoint outside the granted scopes will be rejected. The following scopes are available:
| Scope | Description | Endpoints |
| -------------------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `EVENT_TYPE_READ` | View event types | [Get all event types](https://cal.com/docs/api-reference/v2/event-types/get-all-event-types), [Get an event type](https://cal.com/docs/api-reference/v2/event-types/get-an-event-type), [Get event type private links](https://cal.com/docs/api-reference/v2/event-types-private-links/get-all-private-links-for-an-event-type), [Get all event type webhooks](https://cal.com/docs/api-reference/v2/event-types-webhooks/get-all-webhooks), [Get an event type webhook](https://cal.com/docs/api-reference/v2/event-types-webhooks/get-a-webhook) |
| `EVENT_TYPE_WRITE` | Create, edit, and delete event types | [Create an event type](https://cal.com/docs/api-reference/v2/event-types/create-an-event-type), [Update an event type](https://cal.com/docs/api-reference/v2/event-types/update-an-event-type), [Delete an event type](https://cal.com/docs/api-reference/v2/event-types/delete-an-event-type), [Create a private link](https://cal.com/docs/api-reference/v2/event-types-private-links/create-a-private-link-for-an-event-type), [Update a private link](https://cal.com/docs/api-reference/v2/event-types-private-links/update-a-private-link-for-an-event-type), [Delete a private link](https://cal.com/docs/api-reference/v2/event-types-private-links/delete-a-private-link-for-an-event-type), [Create an event type webhook](https://cal.com/docs/api-reference/v2/event-types-webhooks/create-a-webhook), [Update an event type webhook](https://cal.com/docs/api-reference/v2/event-types-webhooks/update-a-webhook), [Delete an event type webhook](https://cal.com/docs/api-reference/v2/event-types-webhooks/delete-a-webhook), [Delete all event type webhooks](https://cal.com/docs/api-reference/v2/event-types-webhooks/delete-all-webhooks) |
| `BOOKING_READ` | View bookings | [Get all bookings](https://cal.com/docs/api-reference/v2/bookings/get-all-bookings), [Get booking recordings](https://cal.com/docs/api-reference/v2/bookings/get-all-the-recordings-for-the-booking), [Get transcript download links](https://cal.com/docs/api-reference/v2/bookings/get-cal-video-real-time-transcript-download-links-for-the-booking), [Get calendar links](https://cal.com/docs/api-reference/v2/bookings/get-add-to-calendar-links-for-a-booking), [Get booking references](https://cal.com/docs/api-reference/v2/bookings/get-booking-references), [Get conferencing sessions](https://cal.com/docs/api-reference/v2/bookings/get-video-meeting-sessions-only-supported-for-cal-video), [Get all attendees for a booking](https://cal.com/docs/api-reference/v2/bookings-attendees/get-all-attendees-for-a-booking), [Get a specific attendee](https://cal.com/docs/api-reference/v2/bookings-attendees/get-a-specific-attendee-for-a-booking) |
| `BOOKING_WRITE` | Create, edit, and delete bookings | [Add guests to a booking](https://cal.com/docs/api-reference/v2/bookings-guests/add-guests-to-an-existing-booking), [Add an attendee to a booking](https://cal.com/docs/api-reference/v2/bookings-attendees/add-an-attendee-to-a-booking), [Update booking location](https://cal.com/docs/api-reference/v2/bookings/update-booking-location-for-an-existing-booking), [Mark a booking absence](https://cal.com/docs/api-reference/v2/bookings/mark-a-booking-absence), [Request to reschedule a booking](https://cal.com/docs/api-reference/v2/bookings/request-to-reschedule-a-booking), [Reassign to auto-selected host](https://cal.com/docs/api-reference/v2/bookings/reassign-a-booking-to-auto-selected-host), [Reassign to a specific host](https://cal.com/docs/api-reference/v2/bookings/reassign-a-booking-to-a-specific-host), [Confirm a booking](https://cal.com/docs/api-reference/v2/bookings/confirm-a-booking), [Decline a booking](https://cal.com/docs/api-reference/v2/bookings/decline-a-booking) |
| `SCHEDULE_READ` | View availability | [Get all schedules](https://cal.com/docs/api-reference/v2/schedules/get-all-schedules), [Get a schedule](https://cal.com/docs/api-reference/v2/schedules/get-a-schedule), [Get default schedule](https://cal.com/docs/api-reference/v2/schedules/get-default-schedule), [Get all out-of-office entries](https://cal.com/docs/api-reference/v2/out-of-office/get-all-out-of-office-entries-for-the-authenticated-user) |
| `SCHEDULE_WRITE` | Create, edit, and delete availability | [Create a schedule](https://cal.com/docs/api-reference/v2/schedules/create-a-schedule), [Update a schedule](https://cal.com/docs/api-reference/v2/schedules/update-a-schedule), [Delete a schedule](https://cal.com/docs/api-reference/v2/schedules/delete-a-schedule), [Create an out-of-office entry](https://cal.com/docs/api-reference/v2/out-of-office/create-an-out-of-office-entry-for-the-authenticated-user), [Update an out-of-office entry](https://cal.com/docs/api-reference/v2/out-of-office/update-an-out-of-office-entry-for-the-authenticated-user), [Delete an out-of-office entry](https://cal.com/docs/api-reference/v2/out-of-office/delete-an-out-of-office-entry-for-the-authenticated-user) |
| `APPS_READ` | View connected apps | [Get all calendars](https://cal.com/docs/api-reference/v2/calendars/get-all-calendars), [Get busy times](https://cal.com/docs/api-reference/v2/calendars/get-busy-times), [Check an ICS feed](https://cal.com/docs/api-reference/v2/calendars/check-an-ics-feed), [Check a calendar connection](https://cal.com/docs/api-reference/v2/calendars/check-a-calendar-connection), [List conferencing apps](https://cal.com/docs/api-reference/v2/conferencing/list-your-conferencing-applications), [Get default conferencing app](https://cal.com/docs/api-reference/v2/conferencing/get-your-default-conferencing-application), [Get meeting details from calendar](https://cal.com/docs/api-reference/v2/cal-unified-calendars/get-meeting-details-from-calendar) |
| `APPS_WRITE` | Connect and disconnect apps | [Save an ICS feed](https://cal.com/docs/api-reference/v2/calendars/save-an-ics-feed), [Get OAuth connect URL](https://cal.com/docs/api-reference/v2/calendars/get-oauth-connect-url), [Save Apple calendar credentials](https://cal.com/docs/api-reference/v2/calendars/save-apple-calendar-credentials), [Disconnect a calendar](https://cal.com/docs/api-reference/v2/calendars/disconnect-a-calendar), [Connect a conferencing app](https://cal.com/docs/api-reference/v2/conferencing/connect-your-conferencing-application), [Get conferencing OAuth URL](https://cal.com/docs/api-reference/v2/conferencing/get-oauth-conferencing-app-auth-url), [Set default conferencing app](https://cal.com/docs/api-reference/v2/conferencing/set-your-default-conferencing-application), [Disconnect a conferencing app](https://cal.com/docs/api-reference/v2/conferencing/disconnect-your-conferencing-application), [Add a selected calendar](https://cal.com/docs/api-reference/v2/selected-calendars/add-a-selected-calendar), [Delete a selected calendar](https://cal.com/docs/api-reference/v2/selected-calendars/delete-a-selected-calendar), [Update destination calendars](https://cal.com/docs/api-reference/v2/destination-calendars/update-destination-calendars), [Update meeting details in calendar](https://cal.com/docs/api-reference/v2/cal-unified-calendars/update-meeting-details-in-calendar) |
| `PROFILE_READ` | View personal info | [Get my profile](https://cal.com/docs/api-reference/v2/me/get-my-profile), [Get my booking limits](https://cal.com/docs/api-reference/v2/me/get-my-booking-limits) |
| `PROFILE_WRITE` | Edit personal info | [Update my profile](https://cal.com/docs/api-reference/v2/me/update-my-profile), [Update my booking limits](https://cal.com/docs/api-reference/v2/me/update-my-booking-limits), [Clear my booking limits](https://cal.com/docs/api-reference/v2/me/clear-my-booking-limits) |
| `WEBHOOK_READ` | View webhooks | [Get all webhooks](https://cal.com/docs/api-reference/v2/webhooks/get-all-webhooks), [Get a webhook](https://cal.com/docs/api-reference/v2/webhooks/get-a-webhook) |
| `WEBHOOK_WRITE` | Create, edit, and delete webhooks | [Create a webhook](https://cal.com/docs/api-reference/v2/webhooks/create-a-webhook), [Update a webhook](https://cal.com/docs/api-reference/v2/webhooks/update-a-webhook), [Delete a webhook](https://cal.com/docs/api-reference/v2/webhooks/delete-a-webhook) |
| `VERIFIED_RESOURCES_READ` | View verified emails and phone numbers | [Get list of verified emails](https://cal.com/docs/api-reference/v2/verified-resources/get-list-of-verified-emails), [Get verified email by id](https://cal.com/docs/api-reference/v2/verified-resources/get-verified-email-by-id), [Get list of verified phone numbers](https://cal.com/docs/api-reference/v2/verified-resources/get-list-of-verified-phone-numbers), [Get verified phone number by id](https://cal.com/docs/api-reference/v2/verified-resources/get-verified-phone-number-by-id) |
| `VERIFIED_RESOURCES_WRITE` | Request and verify emails and phone numbers | [Request email verification code](https://cal.com/docs/api-reference/v2/verified-resources/request-email-verification-code), [Verify an email](https://cal.com/docs/api-reference/v2/verified-resources/verify-an-email), [Request phone number verification code](https://cal.com/docs/api-reference/v2/verified-resources/request-phone-number-verification-code), [Verify a phone number](https://cal.com/docs/api-reference/v2/verified-resources/verify-a-phone-number) |
| `CREDITS_READ` | View credit balance | [Check available credits](https://cal.com/docs/api-reference/v2/credits/check-available-credits) |
| `CREDITS_WRITE` | Charge credits | [Charge credits](https://cal.com/docs/api-reference/v2/credits/charge-credits) |
| `INSIGHTS_READ` | View user insights | Insights endpoints for user-scoped data |
Some endpoints like `POST /v2/bookings` (create), `POST /v2/bookings/:bookingUid/cancel` (cancel), `POST /v2/bookings/:bookingUid/reschedule` (reschedule), and slot availability endpoints are public and do not require any scope. You can still pass an OAuth access token when calling these endpoints — the token is accepted but not required. This means you can use a consistent `Authorization: Bearer ` header across all API requests without worrying about whether a specific endpoint is public or scoped.
### Team Scopes
Team scopes control access to team-level resources. These are used for endpoints under `/v2/teams/:teamId/...` and `/v2/organizations/:orgId/teams/:teamId/...`.
| Scope | Description | Endpoints |
| ------------------------------- | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `TEAM_EVENT_TYPE_READ` | View team event types | [Get team event types](https://cal.com/docs/api-reference/v2/teams-event-types/get-team-event-types), [Get an event type](https://cal.com/docs/api-reference/v2/teams-event-types/get-an-event-type), [Get team event types (org)](https://cal.com/docs/api-reference/v2/orgs-teams-event-types/get-team-event-types), [Get an event type (org)](https://cal.com/docs/api-reference/v2/orgs-teams-event-types/get-an-event-type), [Get all webhooks for a team event type](https://cal.com/docs/api-reference/v2/teams-event-types-webhooks/get-all-webhooks-for-a-team-event-type), [Get a webhook for a team event type](https://cal.com/docs/api-reference/v2/teams-event-types-webhooks/get-a-webhook-for-a-team-event-type), [Get all private links for a team event type](https://cal.com/docs/api-reference/v2/orgs-teams-event-types-private-links/get-all-private-links-for-a-team-event-type) |
| `TEAM_EVENT_TYPE_WRITE` | Create, edit, and delete team event types | [Create an event type](https://cal.com/docs/api-reference/v2/teams-event-types/create-an-event-type), [Update a team event type](https://cal.com/docs/api-reference/v2/teams-event-types/update-a-team-event-type), [Delete a team event type](https://cal.com/docs/api-reference/v2/teams-event-types/delete-a-team-event-type), [Create a phone call](https://cal.com/docs/api-reference/v2/teams-event-types/create-a-phone-call), [Create an event type (org)](https://cal.com/docs/api-reference/v2/orgs-teams-event-types/create-an-event-type), [Update a team event type (org)](https://cal.com/docs/api-reference/v2/orgs-teams-event-types/update-a-team-event-type), [Delete a team event type (org)](https://cal.com/docs/api-reference/v2/orgs-teams-event-types/delete-a-team-event-type), [Create a phone call (org)](https://cal.com/docs/api-reference/v2/orgs-teams-event-types/create-a-phone-call), [Create a webhook for a team event type](https://cal.com/docs/api-reference/v2/teams-event-types-webhooks/create-a-webhook-for-a-team-event-type), [Update a webhook for a team event type](https://cal.com/docs/api-reference/v2/teams-event-types-webhooks/update-a-webhook-for-a-team-event-type), [Delete a webhook for a team event type](https://cal.com/docs/api-reference/v2/teams-event-types-webhooks/delete-a-webhook-for-a-team-event-type), [Delete all webhooks for a team event type](https://cal.com/docs/api-reference/v2/teams-event-types-webhooks/delete-all-webhooks-for-a-team-event-type), [Create a private link for a team event type](https://cal.com/docs/api-reference/v2/orgs-teams-event-types-private-links/create-a-private-link-for-a-team-event-type), [Update a private link for a team event type](https://cal.com/docs/api-reference/v2/orgs-teams-event-types-private-links/update-a-private-link-for-a-team-event-type), [Delete a private link for a team event type](https://cal.com/docs/api-reference/v2/orgs-teams-event-types-private-links/delete-a-private-link-for-a-team-event-type) |
| `TEAM_BOOKING_READ` | View team bookings | [Get team bookings](https://cal.com/docs/api-reference/v2/teams-bookings/get-team-bookings), [Get organization team bookings](https://cal.com/docs/api-reference/v2/orgs-teams-bookings/get-organization-team-bookings), [Get booking references](https://cal.com/docs/api-reference/v2/orgs-teams-bookings/get-booking-references) |
| `TEAM_SCHEDULE_READ` | View team schedules | [Get all team member schedules](https://cal.com/docs/api-reference/v2/teams-schedules/get-all-team-member-schedules), [Get all team member schedules (org)](https://cal.com/docs/api-reference/v2/orgs-teams-schedules/get-all-team-member-schedules), [Get schedules of a team member](https://cal.com/docs/api-reference/v2/orgs-teams-users-schedules/get-schedules-of-a-team-member), [Get all out-of-office entries for a team member](https://cal.com/docs/api-reference/v2/teams-users-ooo/get-all-out-of-office-entries-for-a-team-member) |
| `TEAM_SCHEDULE_WRITE` | Create, edit, and delete team schedules | [Create an out-of-office entry for a team member](https://cal.com/docs/api-reference/v2/teams-users-ooo/create-an-out-of-office-entry-for-a-team-member), [Update an out-of-office entry for a team member](https://cal.com/docs/api-reference/v2/teams-users-ooo/update-an-out-of-office-entry-for-a-team-member), [Delete an out-of-office entry for a team member](https://cal.com/docs/api-reference/v2/teams-users-ooo/delete-an-out-of-office-entry-for-a-team-member) |
| `TEAM_PROFILE_READ` | View team profiles | [Get teams](https://cal.com/docs/api-reference/v2/teams/get-teams), [Get a team](https://cal.com/docs/api-reference/v2/teams/get-a-team), [Get a team (org)](https://cal.com/docs/api-reference/v2/orgs-teams/get-a-team) |
| `TEAM_PROFILE_WRITE` | Create, edit, and delete teams | [Create a team](https://cal.com/docs/api-reference/v2/teams/create-a-team), [Update a team](https://cal.com/docs/api-reference/v2/teams/update-a-team), [Delete a team](https://cal.com/docs/api-reference/v2/teams/delete-a-team) |
| `TEAM_MEMBERSHIP_READ` | View team memberships | [Get all memberships](https://cal.com/docs/api-reference/v2/teams-memberships/get-all-memberships), [Get a membership](https://cal.com/docs/api-reference/v2/teams-memberships/get-a-membership), [Get all memberships (org)](https://cal.com/docs/api-reference/v2/orgs-teams-memberships/get-all-memberships), [Get a membership (org)](https://cal.com/docs/api-reference/v2/orgs-teams-memberships/get-a-membership) |
| `TEAM_MEMBERSHIP_WRITE` | Create, edit, and delete team memberships | [Create a membership](https://cal.com/docs/api-reference/v2/teams-memberships/create-a-membership), [Update membership](https://cal.com/docs/api-reference/v2/teams-memberships/update-membership), [Delete a membership](https://cal.com/docs/api-reference/v2/teams-memberships/delete-a-membership), [Create team invite link](https://cal.com/docs/api-reference/v2/teams-invite/create-team-invite-link), [Create a membership (org)](https://cal.com/docs/api-reference/v2/orgs-teams-memberships/create-a-membership), [Update a membership (org)](https://cal.com/docs/api-reference/v2/orgs-teams-memberships/update-a-membership), [Delete a membership (org)](https://cal.com/docs/api-reference/v2/orgs-teams-memberships/delete-a-membership), [Create team invite link (org)](https://cal.com/docs/api-reference/v2/orgs-teams-invite/create-team-invite-link) |
| `TEAM_APPS_READ` | View team connected apps | [List team conferencing applications](https://cal.com/docs/api-reference/v2/orgs-teams-conferencing/list-team-conferencing-applications), [Get team default conferencing application](https://cal.com/docs/api-reference/v2/orgs-teams-conferencing/get-team-default-conferencing-application) |
| `TEAM_APPS_WRITE` | Connect and disconnect team apps | [Connect your conferencing application to a team](https://cal.com/docs/api-reference/v2/orgs-teams-conferencing/connect-your-conferencing-application-to-a-team), [Get OAuth conferencing app's auth URL for a team](https://cal.com/docs/api-reference/v2/orgs-teams-conferencing/get-oauth-conferencing-apps-auth-url-for-a-team), [Set team default conferencing application](https://cal.com/docs/api-reference/v2/orgs-teams-conferencing/set-team-default-conferencing-application), [Disconnect team conferencing application](https://cal.com/docs/api-reference/v2/orgs-teams-conferencing/disconnect-team-conferencing-application), [Save conferencing app OAuth credentials](https://cal.com/docs/api-reference/v2/orgs-teams-conferencing/save-conferencing-app-oauth-credentials) |
| `TEAM_ROUTING_FORM_READ` | View team routing forms | [Get team routing forms](https://cal.com/docs/api-reference/v2/orgs-teams-routing-forms/get-team-routing-forms), [Get organization team routing form responses](https://cal.com/docs/api-reference/v2/orgs-teams-routing-forms-responses/get-organization-team-routing-form-responses) |
| `TEAM_ROUTING_FORM_WRITE` | Create, edit, and delete team routing form responses | [Create routing form response and get available slots](https://cal.com/docs/api-reference/v2/orgs-teams-routing-forms-responses/create-routing-form-response-and-get-available-slots), [Update routing form response](https://cal.com/docs/api-reference/v2/orgs-teams-routing-forms-responses/update-routing-form-response) |
| `TEAM_WORKFLOW_READ` | View team workflows | [Get organization team workflows](https://cal.com/docs/api-reference/v2/orgs-teams-workflows/get-organization-team-workflows), [Get organization team routing form workflows](https://cal.com/docs/api-reference/v2/orgs-teams-workflows/get-organization-team-routing-form-workflows), [Get organization team workflow](https://cal.com/docs/api-reference/v2/orgs-teams-workflows/get-organization-team-workflow), [Get organization team routing form workflow](https://cal.com/docs/api-reference/v2/orgs-teams-workflows/get-organization-team-routing-form-workflow) |
| `TEAM_WORKFLOW_WRITE` | Create, edit, and delete team workflows | [Create organization team workflow for event-types](https://cal.com/docs/api-reference/v2/orgs-teams-workflows/create-organization-team-workflow-for-event-types), [Create organization team workflow for routing-forms](https://cal.com/docs/api-reference/v2/orgs-teams-workflows/create-organization-team-workflow-for-routing-forms), [Update organization team workflow](https://cal.com/docs/api-reference/v2/orgs-teams-workflows/update-organization-team-workflow), [Update organization routing form team workflow](https://cal.com/docs/api-reference/v2/orgs-teams-workflows/update-organization-routing-form-team-workflow), [Delete organization team workflow](https://cal.com/docs/api-reference/v2/orgs-teams-workflows/delete-organization-team-workflow), [Delete organization team routing-form workflow](https://cal.com/docs/api-reference/v2/orgs-teams-workflows/delete-organization-team-routing-form-workflow) |
| `TEAM_VERIFIED_RESOURCES_READ` | View team verified emails and phone numbers | [Get list of verified emails of a team](https://cal.com/docs/api-reference/v2/teams-verified-resources/get-list-of-verified-emails-of-a-team), [Get verified email of a team by id](https://cal.com/docs/api-reference/v2/teams-verified-resources/get-verified-email-of-a-team-by-id), [Get list of verified phone numbers of a team](https://cal.com/docs/api-reference/v2/teams-verified-resources/get-list-of-verified-phone-numbers-of-a-team), [Get verified phone number of a team by id](https://cal.com/docs/api-reference/v2/teams-verified-resources/get-verified-phone-number-of-a-team-by-id), [Get list of verified emails of an org team](https://cal.com/docs/api-reference/v2/organization-team-verified-resources/get-list-of-verified-emails-of-an-org-team), [Get verified email of an org team by id](https://cal.com/docs/api-reference/v2/organization-team-verified-resources/get-verified-email-of-an-org-team-by-id), [Get list of verified phone numbers of an org team](https://cal.com/docs/api-reference/v2/organization-team-verified-resources/get-list-of-verified-phone-numbers-of-an-org-team), [Get verified phone number of an org team by id](https://cal.com/docs/api-reference/v2/organization-team-verified-resources/get-verified-phone-number-of-an-org-team-by-id) |
| `TEAM_VERIFIED_RESOURCES_WRITE` | Request and verify team emails and phone numbers | [Request email verification code](https://cal.com/docs/api-reference/v2/teams-verified-resources/request-email-verification-code), [Verify an email for a team](https://cal.com/docs/api-reference/v2/teams-verified-resources/verify-an-email-for-a-team), [Request phone number verification code](https://cal.com/docs/api-reference/v2/teams-verified-resources/request-phone-number-verification-code), [Verify a phone number for a team](https://cal.com/docs/api-reference/v2/teams-verified-resources/verify-a-phone-number-for-an-org-team), [Request email verification code (org)](https://cal.com/docs/api-reference/v2/organization-team-verified-resources/request-email-verification-code), [Verify an email for an org team](https://cal.com/docs/api-reference/v2/organization-team-verified-resources/verify-an-email-for-an-org-team), [Request phone number verification code (org)](https://cal.com/docs/api-reference/v2/organization-team-verified-resources/request-phone-number-verification-code), [Verify a phone number for an org team](https://cal.com/docs/api-reference/v2/organization-team-verified-resources/verify-a-phone-number-for-an-org-team) |
| `TEAM_INSIGHTS_READ` | View team insights | Insights endpoints for team-scoped data |
### Organization Scopes
Organization scopes control access to organization-wide resources. These are used for endpoints under `/v2/organizations/:orgId/...` that do not target a specific team.
An `ORG_` scope automatically grants the corresponding `TEAM_` scope. For example, a token with `ORG_PROFILE_READ` can also access endpoints that require `TEAM_PROFILE_READ`.
| Scope | Description | Endpoints |
| ------------------------ | -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ORG_EVENT_TYPE_READ` | View all event types across the organization | [Get all team event types](https://cal.com/docs/api-reference/v2/orgs-teams-event-types/get-all-team-event-types) |
| `ORG_BOOKING_READ` | View all bookings across the organization | [Get organization bookings](https://cal.com/docs/api-reference/v2/orgs-bookings/get-organization-bookings), [Get all bookings for an organization user](https://cal.com/docs/api-reference/v2/orgs-users-bookings/get-all-bookings-for-an-organization-user) |
| `ORG_SCHEDULE_READ` | View schedules across the organization | [Get all schedules](https://cal.com/docs/api-reference/v2/orgs-schedules/get-all-schedules), [Get all schedules (user)](https://cal.com/docs/api-reference/v2/orgs-users-schedules/get-all-schedules), [Get a schedule](https://cal.com/docs/api-reference/v2/orgs-users-schedules/get-a-schedule), [Get all out-of-office entries for a user](https://cal.com/docs/api-reference/v2/orgs-users-ooo/get-all-out-of-office-entries-for-a-user), [Get all out-of-office entries for organization users](https://cal.com/docs/api-reference/v2/orgs-users-ooo/get-all-out-of-office-entries-for-organization-users) |
| `ORG_SCHEDULE_WRITE` | Create, edit, and delete schedules across the organization | [Create a schedule](https://cal.com/docs/api-reference/v2/orgs-users-schedules/create-a-schedule), [Update a schedule](https://cal.com/docs/api-reference/v2/orgs-users-schedules/update-a-schedule), [Delete a schedule](https://cal.com/docs/api-reference/v2/orgs-users-schedules/delete-a-schedule), [Create an out-of-office entry for a user](https://cal.com/docs/api-reference/v2/orgs-users-ooo/create-an-out-of-office-entry-for-a-user), [Update an out-of-office entry for a user](https://cal.com/docs/api-reference/v2/orgs-users-ooo/update-an-out-of-office-entry-for-a-user), [Delete an out-of-office entry for a user](https://cal.com/docs/api-reference/v2/orgs-users-ooo/delete-an-out-of-office-entry-for-a-user) |
| `ORG_PROFILE_READ` | View organization teams | [Get all teams](https://cal.com/docs/api-reference/v2/orgs-teams/get-all-teams), [Get teams membership for user](https://cal.com/docs/api-reference/v2/orgs-teams/get-teams-membership-for-user) |
| `ORG_PROFILE_WRITE` | Create, edit, and delete organization teams | [Create a team](https://cal.com/docs/api-reference/v2/orgs-teams/create-a-team), [Update a team](https://cal.com/docs/api-reference/v2/orgs-teams/update-a-team), [Delete a team](https://cal.com/docs/api-reference/v2/orgs-teams/delete-a-team) |
| `ORG_MEMBERSHIP_READ` | View organization memberships and users | [Get all memberships](https://cal.com/docs/api-reference/v2/orgs-memberships/get-all-memberships), [Get a membership](https://cal.com/docs/api-reference/v2/orgs-memberships/get-a-membership), [Get all users](https://cal.com/docs/api-reference/v2/orgs-users/get-all-users) |
| `ORG_MEMBERSHIP_WRITE` | Create, edit, and delete organization memberships and users | [Create a membership](https://cal.com/docs/api-reference/v2/orgs-memberships/create-a-membership), [Update a membership](https://cal.com/docs/api-reference/v2/orgs-memberships/update-a-membership), [Delete a membership](https://cal.com/docs/api-reference/v2/orgs-memberships/delete-a-membership), [Create a user](https://cal.com/docs/api-reference/v2/orgs-users/create-a-user), [Update a user](https://cal.com/docs/api-reference/v2/orgs-users/update-a-user), [Delete a user](https://cal.com/docs/api-reference/v2/orgs-users/delete-a-user) |
| `ORG_ROUTING_FORM_READ` | View organization routing forms | [Get organization routing forms](https://cal.com/docs/api-reference/v2/orgs-routing-forms/get-organization-routing-forms), [Get routing form responses](https://cal.com/docs/api-reference/v2/orgs-routing-forms/get-routing-form-responses) |
| `ORG_ROUTING_FORM_WRITE` | Create, edit, and delete organization routing form responses | [Create routing form response and get available slots](https://cal.com/docs/api-reference/v2/orgs-routing-forms/create-routing-form-response-and-get-available-slots), [Update routing form response](https://cal.com/docs/api-reference/v2/orgs-routing-forms/update-routing-form-response) |
| `ORG_WEBHOOK_READ` | View organization webhooks | [Get all webhooks](https://cal.com/docs/api-reference/v2/orgs-webhooks/get-all-webhooks), [Get a webhook](https://cal.com/docs/api-reference/v2/orgs-webhooks/get-a-webhook) |
| `ORG_WEBHOOK_WRITE` | Create, edit, and delete organization webhooks | [Create a webhook](https://cal.com/docs/api-reference/v2/orgs-webhooks/create-a-webhook), [Update a webhook](https://cal.com/docs/api-reference/v2/orgs-webhooks/update-a-webhook), [Delete a webhook](https://cal.com/docs/api-reference/v2/orgs-webhooks/delete-a-webhook) |
| `ORG_INSIGHTS_READ` | View organization insights | Insights endpoints for organization-scoped data |
| `ORG_ATTRIBUTES_READ` | View organization attributes | [Get all attributes](https://cal.com/docs/api-reference/v2/orgs-attributes/get-all-attributes), [Get an attribute](https://cal.com/docs/api-reference/v2/orgs-attributes/get-an-attribute), [Get all attribute options](https://cal.com/docs/api-reference/v2/orgs-attributes-options/get-all-attribute-options) |
| `ORG_ATTRIBUTES_WRITE` | Assign, update, and unassign organization attributes for users | [Assign an attribute to a user](https://cal.com/docs/api-reference/v2/orgs-attributes-options/assign-an-attribute-to-a-user), [Update an attribute assignment for a user](https://cal.com/docs/api-reference/v2/orgs-attributes-options/update-an-attribute-assignment-for-a-user), [Unassign an attribute from a user](https://cal.com/docs/api-reference/v2/orgs-attributes-options/unassign-an-attribute-from-a-user) |
## 2. Authorize
To initiate the OAuth flow, direct users to the following authorization URL:
`https://app.cal.com/auth/oauth2/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&state=YOUR_STATE&scope=BOOKING_READ%20BOOKING_WRITE`
**URL Parameters:**
| Parameter | Required | Description |
| ---------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `client_id` | Yes | Your OAuth client ID |
| `redirect_uri` | Yes | Where users will be redirected after authorization. Must exactly match one of the registered redirect URIs. |
| `state` | Recommended | A securely generated random string to mitigate CSRF attacks |
| `scope` | Yes | Space or comma-separated list of scopes to request (e.g. `BOOKING_READ BOOKING_WRITE` or `BOOKING_READ,BOOKING_WRITE`). Must be a subset of scopes enabled on the OAuth client. |
| `code_challenge` | For public clients | PKCE code challenge (S256 method) |
After users click **Allow**, they will be redirected to the `redirect_uri` with `code` (authorization code) and `state` as URL parameters:
```
https://your-app.com/callback?code=AUTHORIZATION_CODE&state=YOUR_STATE
```
#### Error Handling
Errors during the authorization step are displayed directly to the user on the Cal.com authorization page. Your application will not receive a JSON error response for these cases:
* **Client not found**: No OAuth client exists with the provided `client_id`.
* **Client not approved**: The OAuth client has not been approved by a Cal.com admin yet.
* **Mismatched redirect URI**: The `redirect_uri` does not match any of the registered redirect URIs for the OAuth client.
If an error occurs after the client is validated, the user is redirected to the `redirect_uri` with an error:
* **Scope required**: If the `scope` parameter is missing, the error `scope parameter is required for this OAuth client` is displayed on the authorization page.
* **Unknown scope**: If the `scope` parameter includes scope values that do not exist, the user is redirected with `error=invalid_scope` and `error_description=Requested scope is not a recognized scope`. This applies to both regular and legacy clients.
* **Invalid scope**: If the `scope` parameter includes scopes not enabled on the OAuth client, the user is redirected with `error=invalid_request` and `error_description=Requested scope exceeds the client's registered scopes`.
* **Access denied**: If the user denies access or has insufficient permissions, the user is redirected with an error.
```
https://your-app.com/callback?error=invalid_request&error_description=Requested+scope+exceeds+the+client%27s+registered+scopes&state=YOUR_STATE
```
## 3. Exchange Token
Exchange an authorization code for access and refresh tokens. The token endpoint also accepts `application/x-www-form-urlencoded` content type.
**Endpoint:** `POST https://api.cal.com/v2/auth/oauth2/token`
### 3.1 Confidential Clients
Confidential clients authenticate with a `client_secret`. All parameters are required:
| Parameter | Description |
| --------------- | ------------------------------------------------------------- |
| `client_id` | Your OAuth client ID |
| `client_secret` | Your OAuth client secret |
| `grant_type` | Must be `authorization_code` |
| `code` | The authorization code received in the redirect URI |
| `redirect_uri` | Must match the redirect URI used in the authorization request |
```bash theme={null}
curl -X POST https://api.cal.com/v2/auth/oauth2/token \
-H "Content-Type: application/json" \
-d '{
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"grant_type": "authorization_code",
"code": "AUTHORIZATION_CODE",
"redirect_uri": "https://your-app.com/callback"
}'
```
```typescript theme={null}
const response = await fetch("https://api.cal.com/v2/auth/oauth2/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
client_id: "YOUR_CLIENT_ID",
client_secret: "YOUR_CLIENT_SECRET",
grant_type: "authorization_code",
code: "AUTHORIZATION_CODE",
redirect_uri: "https://your-app.com/callback",
}),
});
const tokens = await response.json();
```
```typescript theme={null}
import axios from "axios";
const { data } = await axios.post(
"https://api.cal.com/v2/auth/oauth2/token",
{
client_id: "YOUR_CLIENT_ID",
client_secret: "YOUR_CLIENT_SECRET",
grant_type: "authorization_code",
code: "AUTHORIZATION_CODE",
redirect_uri: "https://your-app.com/callback",
}
);
```
### 3.2 Public Clients (PKCE)
Public clients (e.g. single-page apps, mobile apps) use PKCE instead of a `client_secret`. You must have sent a `code_challenge` during the authorization step. All parameters are required:
| Parameter | Description |
| --------------- | --------------------------------------------------------------------- |
| `client_id` | Your OAuth client ID |
| `grant_type` | Must be `authorization_code` |
| `code` | The authorization code received in the redirect URI |
| `redirect_uri` | Must match the redirect URI used in the authorization request |
| `code_verifier` | The original PKCE code verifier used to generate the `code_challenge` |
```bash theme={null}
curl -X POST https://api.cal.com/v2/auth/oauth2/token \
-H "Content-Type: application/json" \
-d '{
"client_id": "YOUR_CLIENT_ID",
"grant_type": "authorization_code",
"code": "AUTHORIZATION_CODE",
"redirect_uri": "https://your-app.com/callback",
"code_verifier": "YOUR_CODE_VERIFIER"
}'
```
```typescript theme={null}
const response = await fetch("https://api.cal.com/v2/auth/oauth2/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
client_id: "YOUR_CLIENT_ID",
grant_type: "authorization_code",
code: "AUTHORIZATION_CODE",
redirect_uri: "https://your-app.com/callback",
code_verifier: "YOUR_CODE_VERIFIER",
}),
});
const tokens = await response.json();
```
```typescript theme={null}
import axios from "axios";
const { data } = await axios.post(
"https://api.cal.com/v2/auth/oauth2/token",
{
client_id: "YOUR_CLIENT_ID",
grant_type: "authorization_code",
code: "AUTHORIZATION_CODE",
redirect_uri: "https://your-app.com/callback",
code_verifier: "YOUR_CODE_VERIFIER",
}
);
```
#### Success Response (200)
```json theme={null}
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer",
"expires_in": 1800,
"scope": "BOOKING_READ BOOKING_WRITE"
}
```
Access tokens expire after 30 minutes (`expires_in: 1800`). Use the refresh token to obtain a new access token. The `scope` field contains the granted scopes as a space-separated string.
#### Error Responses
Error responses include `error` and `error_description` fields.
```json theme={null}
{
"error": "invalid_grant",
"error_description": "code_invalid_or_expired"
}
```
The authorization code has already been used, has expired, or is invalid. Request a new authorization code.
```json theme={null}
{
"error": "invalid_client",
"error_description": "invalid_client_credentials"
}
```
The `client_secret` does not match the `client_id`. Verify your credentials.
```json theme={null}
{
"error": "invalid_client",
"error_description": "client_not_found"
}
```
No OAuth client exists with the provided `client_id`.
```json theme={null}
{
"error": "invalid_request",
"error_description": "client_id is required"
}
```
The `client_id` field is missing from the request body.
```json theme={null}
{
"error": "invalid_request",
"error_description": "grant_type must be 'authorization_code' or 'refresh_token'"
}
```
The `grant_type` field must be either `authorization_code` or `refresh_token`.
## 4. Refresh Token
Refresh an expired access token using a refresh token.
**Endpoint:** `POST https://api.cal.com/v2/auth/oauth2/token`
### 4.1 Confidential Clients
Confidential clients authenticate with a `client_secret`. All parameters are required:
| Parameter | Description |
| --------------- | --------------------------------------------------------- |
| `client_id` | Your OAuth client ID |
| `client_secret` | Your OAuth client secret |
| `grant_type` | Must be `refresh_token` |
| `refresh_token` | The refresh token received from a previous token response |
```bash theme={null}
curl -X POST https://api.cal.com/v2/auth/oauth2/token \
-H "Content-Type: application/json" \
-d '{
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"grant_type": "refresh_token",
"refresh_token": "YOUR_REFRESH_TOKEN"
}'
```
```typescript theme={null}
const response = await fetch("https://api.cal.com/v2/auth/oauth2/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
client_id: "YOUR_CLIENT_ID",
client_secret: "YOUR_CLIENT_SECRET",
grant_type: "refresh_token",
refresh_token: "YOUR_REFRESH_TOKEN",
}),
});
const tokens = await response.json();
```
```typescript theme={null}
import axios from "axios";
const { data } = await axios.post(
"https://api.cal.com/v2/auth/oauth2/token",
{
client_id: "YOUR_CLIENT_ID",
client_secret: "YOUR_CLIENT_SECRET",
grant_type: "refresh_token",
refresh_token: "YOUR_REFRESH_TOKEN",
}
);
```
### 4.2 Public Clients
Public clients do not use a `client_secret`. All parameters are required:
| Parameter | Description |
| --------------- | --------------------------------------------------------- |
| `client_id` | Your OAuth client ID |
| `grant_type` | Must be `refresh_token` |
| `refresh_token` | The refresh token received from a previous token response |
```bash theme={null}
curl -X POST https://api.cal.com/v2/auth/oauth2/token \
-H "Content-Type: application/json" \
-d '{
"client_id": "YOUR_CLIENT_ID",
"grant_type": "refresh_token",
"refresh_token": "YOUR_REFRESH_TOKEN"
}'
```
```typescript theme={null}
const response = await fetch("https://api.cal.com/v2/auth/oauth2/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
client_id: "YOUR_CLIENT_ID",
grant_type: "refresh_token",
refresh_token: "YOUR_REFRESH_TOKEN",
}),
});
const tokens = await response.json();
```
```typescript theme={null}
import axios from "axios";
const { data } = await axios.post(
"https://api.cal.com/v2/auth/oauth2/token",
{
client_id: "YOUR_CLIENT_ID",
grant_type: "refresh_token",
refresh_token: "YOUR_REFRESH_TOKEN",
}
);
```
#### Success Response (200)
```json theme={null}
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer",
"expires_in": 1800,
"scope": "BOOKING_READ BOOKING_WRITE"
}
```
Scopes are preserved from the original authorization. You do not need to re-request scopes when refreshing tokens.
#### Error Responses
```json theme={null}
{
"error": "invalid_grant",
"error_description": "invalid_refresh_token"
}
```
The refresh token is invalid, expired, or malformed. The user must re-authorize.
```json theme={null}
{
"error": "invalid_client",
"error_description": "invalid_client_credentials"
}
```
The `client_secret` does not match the `client_id`.
```json theme={null}
{
"error": "invalid_client",
"error_description": "client_not_found"
}
```
No OAuth client exists with the provided `client_id`.
## 5. Client Secret Rotation
Cal.com supports rotating client secrets with zero downtime. You can have up to **2 active secrets** at a time, allowing you to deploy a new secret before revoking the old one.
### Why rotate secrets?
* A secret may have been accidentally exposed or compromised
* Your security policy requires periodic credential rotation
* An employee with access to the secret has left your organization
### How it works
1. **Generate a new secret** from your [OAuth client settings](https://app.cal.com/settings/developer/oauth). Your old secret continues to work — both secrets are valid simultaneously.
2. **Update your application** to use the new secret in all token exchange and refresh requests.
3. **Verify** that your application works correctly with the new secret.
4. **Revoke the old secret** from the settings page. Any requests using the old secret will immediately fail.
### Important notes
* You can have a **maximum of 2 secrets** per client. To generate a new one when you already have 2, revoke an existing secret first.
* New secrets are shown **only once** when generated. Copy and store them securely.
* Revoking a secret takes effect **immediately** — there is no grace period.
* **Existing access and refresh tokens remain valid** after secret rotation. Rotation only affects token exchange and refresh requests that require `client_secret`.
* Secret rotation applies only to **confidential clients**. Public clients (PKCE) do not use client secrets.
### What needs to change in your code
When you rotate a secret, update the `client_secret` parameter in these requests:
| Request | Affected? |
| ------------------------------------ | -------------------------------- |
| Authorization redirect (Step 2) | No — uses only `client_id` |
| Exchange code for tokens (Step 3) | **Yes** — update `client_secret` |
| Refresh access token (Step 4) | **Yes** — update `client_secret` |
| API calls with Bearer token (Step 5) | No — uses access token |
## Legacy Client Migration
If your OAuth client was created before scopes were introduced, it is considered a **legacy client**. A client is treated as legacy if it has no scopes configured, or if it only has the old legacy scope values (`READ_BOOKING` and/or `READ_PROFILE`). Access tokens issued by legacy clients can access any resource on behalf of the authorizing user — scopes are not enforced.
You can migrate a legacy client to use explicit scopes without creating a new client. **Order matters** — follow these steps to avoid breaking existing integrations:
### Step 1: Update your authorization URL
Add a `scope` query parameter to your authorization URL **before** changing any client settings. Legacy clients skip scope validation during authorization, so users can already authorize with a scope parameter even while the client is still in legacy mode.
```
https://app.cal.com/auth/oauth2/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&state=YOUR_STATE&scope=BOOKING_READ%20BOOKING_WRITE
```
New access tokens issued after this change will carry only the scopes you specified. For the full list of available scopes, see [Available Scopes](#available-scopes).
### Step 2: Update client scopes in settings
Once your authorization URL is updated and you have verified that new tokens are being issued with the correct scopes, open your OAuth client settings and select the matching scopes. Save the client.
After this step, the client is no longer treated as a legacy client. Scope validation is enforced for all new authorization requests.
Do **not** update the client scopes before updating your authorization URL. Doing so will immediately break the authorization flow for any user who visits the old URL without a `scope` parameter.
### Existing tokens
Tokens issued before the migration continue to work until users re-authorize. There is no forced invalidation of existing tokens during the migration.
### Re-approval
Changing properties below will trigger a re-review by Cal.com admins and set client to a "pending" state:
* Name
* Logo
* Website URL
* Redirect URI
* Scope expansion (adding new scopes that the client did not previously have)
While pending, the client can only be used by the client owner for testing — other users cannot authorize with it.
Changing properties below will NOT trigger a re-review and client will remain in the state it is:
* Adding a `_READ` scope when the corresponding `_WRITE` scope is already granted (e.g. adding `BOOKING_READ` when `BOOKING_WRITE` is already present)
* Removing scopes
* Purpose description change
* Updating scopes on a legacy client, as long as only user-level scopes are added — adding `TEAM_` or `ORG_` scopes to a legacy client will trigger re-approval. See [Legacy Client Migration](#legacy-client-migration) for details.
## 6. Verify Access Token
To verify the correct setup and functionality of OAuth credentials, use the following endpoint:
**Endpoint:** `GET https://api.cal.com/v2/me`
```bash theme={null}
curl -X GET https://api.cal.com/v2/me \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
```typescript theme={null}
const response = await fetch("https://api.cal.com/v2/me", {
headers: { Authorization: "Bearer YOUR_ACCESS_TOKEN" },
});
const user = await response.json();
```
```typescript theme={null}
import axios from "axios";
const { data } = await axios.get("https://api.cal.com/v2/me", {
headers: { Authorization: "Bearer YOUR_ACCESS_TOKEN" },
});
```
***
## 7. Onboarding Embed
The `` React component lets you embed Cal.com account creation, onboarding, and OAuth authorization directly inside your application. Users create a real Cal.com account, complete onboarding, and grant your app OAuth access — all without leaving your site. The component
also has an inbuilt "dark" and "light" theme.
For a demonstration of the onboarding embed flow, please refer to the video below.
The component supports two modes for receiving the authorization code:
* **Callback mode** — provide `onAuthorizationAllowed` to receive the authorization code via a callback. No page navigation occurs.
* **Redirect mode** — don't provide `onAuthorizationAllowed` and the browser navigates to your `redirectUri` with the code as a query parameter. Works like a standard OAuth redirect.
After a new user signs up through the embed, Cal.com sends them a verification email to confirm their email address.
```bash theme={null}
npm install @calcom/atoms
```
### Callback Mode
Provide `onAuthorizationAllowed` to receive the authorization code directly. The dialog closes and your callback fires after user authorizes your OAuth client — no page reload.
```tsx theme={null}
import { OnboardingEmbed } from "@calcom/atoms";
import { useState } from "react";
function App() {
const [state] = useState(() => crypto.randomUUID());
return (
{
fetch("/api/cal/exchange", {
method: "POST",
body: JSON.stringify({ code, state }),
});
}}
onError={(error) => console.error(error.code, error.message)}
onClose={() => console.log("Dialog dismissed")}
/>
);
}
```
### Redirect Mode
Omit `onAuthorizationAllowed` and the browser navigates to your `redirectUri` after the user completes onboarding and grants access:
```
https://your-app.com/cal/callback?code=AUTHORIZATION_CODE&state=YOUR_STATE
```
```tsx theme={null}
import { OnboardingEmbed } from "@calcom/atoms";
import { useState } from "react";
function App() {
const [state] = useState(() => crypto.randomUUID());
return (
console.error(error.code, error.message)}
/>
);
}
```
### Props
| Prop | Type | Required | Description |
| ------------------------ | ------------------------------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `oAuthClientId` | `string` | Yes | Your OAuth client ID from [Section 1](#1-oauth-client-credentials). |
| `host` | `string` | No | Cal.com host URL. Defaults to `https://app.cal.com`. Used for local development to point to cal web app. |
| `theme` | `"light" \| "dark"` | No | Theme for the embedded onboarding UI. Defaults to `"light"`. |
| `user` | `{ email?: string, name?: string, username?: string }` | No | Prefill user details in signup and profile steps. |
| `authorization` | `AuthorizationProps` | Yes | OAuth authorization parameters (see below). |
| `onAuthorizationAllowed` | `(result: { code: string }) => void` | No | Called with the authorization code on completion. If provided, enables callback mode. If omitted, enables redirect mode (browser navigates to `redirectUri`). |
| `onError` | `(error: OnboardingError) => void` | No | Called on unrecoverable error. |
| `onAuthorizationDenied` | `() => void` | No | Called when the user declines OAuth authorization. If provided, the callback fires and the dialog closes. If omitted, the browser navigates to `redirectUri?error=access_denied&state=YOUR_STATE`. |
| `onClose` | `() => void` | No | Called when the user dismisses the dialog before completing. |
| `trigger` | `ReactNode` | No | Custom trigger element. Defaults to a "Continue with Cal.com" button. |
### Authorization Props
| Prop | Type | Required | Description |
| --------------- | ---------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `redirectUri` | `string` | Yes | One of the redirect URIs registered on your OAuth client. The server validates this against the client's registered URIs. **Must share the same origin** (scheme + domain + port) **as the page hosting the ``**, because the iframe uses `postMessage` with this origin for secure communication. For example, if your OAuth client has redirect URI `https://your-app.com/cal/callback`, then pass it here exactly the same `https://your-app.com/cal/callback`. |
| `scope` | `string[]` | Yes | OAuth scopes to request. Must be a subset of scopes registered on the OAuth client. See [Available Scopes](#available-scopes). |
| `state` | `string` | Yes | CSRF token. Generate a unique value per session and verify it matches when you receive the authorization code. |
| `codeChallenge` | `string` | For public clients | PKCE code challenge (S256 method). Required for public OAuth clients. Generate a `code_verifier` (random 32-byte base64url string), hash it with SHA-256, and pass the result here. Store the `code_verifier` — you'll need it to exchange the authorization code for tokens. |
If the user signs up via Google, the `user` prop values are ignored — name, email, and username are inferred from the Google account instead.
### Trigger and Theme
The `theme` prop controls the appearance of the trigger button, the onboarding steps, and the authorization page. The default trigger renders a "Continue with Cal.com" button:
| Light theme (default) | Dark theme |
| --------------------- | -------------- |
| | |
You can pass a custom trigger element via the `trigger` prop:
```tsx theme={null}
Connect calendar}
// ...
/>
```
### Walkthrough — Callback Mode
Here's what happens when a user clicks the trigger with `onAuthorizationAllowed` provided and the `user` prop set:
```tsx theme={null}
{
alert(`Success! Auth code: ${code}`);
}}
/>
```
**1. Trigger** — The component renders a "Continue with Cal.com" button. The user clicks it to open the onboarding dialog.
**2. Login or Signup** — The dialog opens with the login form. Existing users can sign in with email or Google. The `user.email` prop prefills the email field.
New users click "Create account" to sign up with Google or email. When signing up with email, the `user.email` and `user.username` props are prefilled. When signing up with Google, the `user` prop values are ignored — name, email, and username are inferred from the Google account.
**3. Profile** — After signup, the user sets up their profile. The `user.name` prop prefills the name field.
**4. Connect Calendar** — The user can connect a calendar or skip this step.
**5. Authorize** — The user reviews the requested permissions and clicks "Allow". The displayed permissions (e.g. "View event types") correspond to the `scope` passed to the component — in this example, `["EVENT_TYPE_READ"]`.
**6. Done** — `onAuthorizationAllowed` fires with the authorization code. Exchange it for tokens using the [token endpoint](#3-exchange-token).
### Public Clients (PKCE)
Public OAuth clients cannot safely store a client secret (e.g. browser-only apps). Use PKCE to secure the authorization code exchange instead. Generate a `code_verifier`, derive a `code_challenge` from it, and pass the challenge to `OnboardingEmbed`. When you receive the authorization code, exchange it with the `code_verifier` instead of a client secret.
```tsx theme={null}
import { OnboardingEmbed } from "@calcom/atoms";
import { useMemo, useState } from "react";
async function generatePkce() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
const codeVerifier = btoa(String.fromCharCode(...array))
.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(codeVerifier));
const codeChallenge = btoa(String.fromCharCode(...new Uint8Array(digest)))
.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
return { codeVerifier, codeChallenge };
}
export function MyApp() {
const state = useMemo(() => crypto.randomUUID(), []);
const [pkce, setPkce] = useState<{ codeVerifier: string; codeChallenge: string } | null>(null);
useMemo(() => {
generatePkce().then(setPkce);
}, []);
if (!pkce) return null;
return (
{
// Exchange using code_verifier instead of client_secret
const res = await fetch("https://api.cal.com/v2/auth/oauth2/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
client_id: "your_client_id",
code_verifier: pkce.codeVerifier,
grant_type: "authorization_code",
code,
redirect_uri: "https://your-app.com/cal/callback",
}),
});
const { access_token, refresh_token } = await res.json();
}}
/>
);
}
```
### Error Types
The `onError` callback receives an error object with the following shape:
```ts theme={null}
interface OnboardingError {
code: "INVALID_PROPS" | "SIGNUP_FAILED" | "ONBOARDING_FAILED" | "AUTHORIZATION_FAILED" | "STATE_MISMATCH" | "UNKNOWN";
message: string;
}
```
| Code | Description |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `INVALID_PROPS` | Required props are missing or invalid (e.g. `oAuthClientId` does not exist, `redirectUri` does not match a registered URI, or required authorization fields are empty). |
| `SIGNUP_FAILED` | Account creation failed. |
| `ONBOARDING_FAILED` | An error occurred during the onboarding steps. |
| `AUTHORIZATION_FAILED` | The user denied access or OAuth consent failed. |
| `STATE_MISMATCH` | The `state` in the response did not match the `state` you provided. Possible CSRF attack. |
| `UNKNOWN` | An unexpected error occurred. |
### How It Works
The component opens a dialog containing an iframe that loads Cal.com's onboarding flow. The iframe runs on Cal.com's domain with a first-party session, so no third-party cookies are needed.
The flow automatically detects the user's state:
* **No session** — starts at signup/login, then profile setup, calendar connection, and OAuth consent.
* **Session with incomplete onboarding** — resumes from where the user left off.
* **Session with complete onboarding** — skips straight to OAuth consent.
After the user grants access, you receive an authorization code that you exchange for access and refresh tokens using the [token endpoint](#3-exchange-token).
# Exchange authorization code or refresh token for tokens
Source: https://cal.com/docs/api-reference/v2/oauth2/exchange-authorization-code-or-refresh-token-for-tokens
/api-reference/v2/openapi.json post /v2/auth/oauth2/token
RFC 6749-compliant token endpoint. Pass client_id in the request body (Section 2.3.1). Use grant_type 'authorization_code' to exchange an auth code for tokens, or 'refresh_token' to refresh an access token. Accepts both application/x-www-form-urlencoded (standard per RFC 6749 Section 4.1.3) and application/json content types.
# Get OAuth2 client
Source: https://cal.com/docs/api-reference/v2/oauth2/get-oauth2-client
/api-reference/v2/openapi.json get /v2/auth/oauth2/clients/{clientId}
Returns the OAuth2 client information for the given client ID
# Add custom organization team booking fields
Source: https://cal.com/docs/api-reference/v2/organization-event-types/add-custom-organization-team-booking-fields
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/teams/{teamId}/event-types/{eventTypeId}/booking-fields
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Adds custom booking fields. Default and workflow-added fields cannot be added through this endpoint. Required membership role: `team admin`. PBAC permission: `eventType.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_WRITE` scope is required.
# Create a phone call for an organization team event type
Source: https://cal.com/docs/api-reference/v2/organization-event-types/create-a-phone-call-for-an-organization-team-event-type
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/teams/{teamId}/event-types/{eventTypeId}/create-phone-call
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Required membership role: `team admin`. PBAC permission: `eventType.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_WRITE` scope is required.
# Create an organization team event type
Source: https://cal.com/docs/api-reference/v2/organization-event-types/create-an-organization-team-event-type
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/teams/{teamId}/event-types
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
If `bookingFields` is omitted, the event type is created with its default booking fields. At least one contact field—`email` or `attendeePhoneNumber`—must remain visible and required. Other default fields, such as additional notes, can be hidden. Provide `bookingFields` to customize the default fields or add custom booking fields. Required membership role: `team admin`. PBAC permission: `eventType.create`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_WRITE` scope is required.
# Delete a custom organization team booking field
Source: https://cal.com/docs/api-reference/v2/organization-event-types/delete-a-custom-organization-team-booking-field
/api-reference/v2/openapi.json delete /v2/organizations/{orgId}/teams/{teamId}/event-types/{eventTypeId}/booking-fields/{slug}
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Deletes the custom booking field identified by `slug`. Default fields cannot be deleted, set hidden instead. Required membership role: `team admin`. PBAC permission: `eventType.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_WRITE` scope is required.
# Delete an organization team event type
Source: https://cal.com/docs/api-reference/v2/organization-event-types/delete-an-organization-team-event-type
/api-reference/v2/openapi.json delete /v2/organizations/{orgId}/teams/{teamId}/event-types/{eventTypeId}
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Required membership role: `team admin`. PBAC permission: `eventType.delete`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_WRITE` scope is required.
# Get all team event types
Source: https://cal.com/docs/api-reference/v2/organization-event-types/get-all-team-event-types
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/event-types
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Use the optional `sortCreatedAt` query parameter to order results by creation date (by ID). Accepts "asc" (oldest first) or "desc" (newest first). When not provided, no explicit ordering is applied. Required membership role: `org admin`. PBAC permission: `eventType.readOrgEventTypes`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_EVENT_TYPE_READ` scope is required.
# Get an event type
Source: https://cal.com/docs/api-reference/v2/organization-event-types/get-an-event-type
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/event-types/{eventTypeId}
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Returns hidden booking fields only to callers who are allowed to view them. Required membership role: `team admin`. PBAC permission: `eventType.readTeamEventTypes`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_READ` scope is required.
# Get organization team booking fields
Source: https://cal.com/docs/api-reference/v2/organization-event-types/get-organization-team-booking-fields
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/event-types/{eventTypeId}/booking-fields
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Returns default and custom booking fields, plus any read-only workflow-added fields. Required membership role: `team admin`. PBAC permission: `eventType.readTeamEventTypes`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_READ` scope is required.
# List organization team event types
Source: https://cal.com/docs/api-reference/v2/organization-event-types/list-organization-team-event-types
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/event-types
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Hidden event types and hidden booking fields are returned only to team administrators or owners, and parent-organization administrators or owners. Other callers see public event types and visible booking fields. Authentication is optional.
# Replace organization team booking fields
Source: https://cal.com/docs/api-reference/v2/organization-event-types/replace-organization-team-booking-fields
/api-reference/v2/openapi.json put /v2/organizations/{orgId}/teams/{teamId}/event-types/{eventTypeId}/booking-fields
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Replaces all booking fields. Omitted custom fields are deleted, while omitted default fields are restored with their default settings. Use `PATCH` to update specific fields. Workflow-added fields are read-only and ignored. Required membership role: `team admin`. PBAC permission: `eventType.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_WRITE` scope is required.
# Update an organization team event type
Source: https://cal.com/docs/api-reference/v2/organization-event-types/update-an-organization-team-event-type
/api-reference/v2/openapi.json patch /v2/organizations/{orgId}/teams/{teamId}/event-types/{eventTypeId}
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Omit `bookingFields` to keep the existing booking fields. If provided, the array replaces them, and any omitted default fields are restored with their default settings. Required membership role: `team admin`. PBAC permission: `eventType.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_WRITE` scope is required.
# Update organization team booking fields
Source: https://cal.com/docs/api-reference/v2/organization-event-types/update-organization-team-booking-fields
/api-reference/v2/openapi.json patch /v2/organizations/{orgId}/teams/{teamId}/event-types/{eventTypeId}/booking-fields
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Updates the booking fields identified by `slug`. Omitted properties keep their current values. Workflow-added fields are read-only. Required membership role: `team admin`. PBAC permission: `eventType.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_WRITE` scope is required.
# Get list of verified emails of an org team
Source: https://cal.com/docs/api-reference/v2/organization-team-verified-resources/get-list-of-verified-emails-of-an-org-team
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/verified-resources/emails
Required membership role: `team admin`. PBAC permission: `team.readVerifiedResources`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_VERIFIED_RESOURCES_READ` scope is required.
# Get list of verified phone numbers of an org team
Source: https://cal.com/docs/api-reference/v2/organization-team-verified-resources/get-list-of-verified-phone-numbers-of-an-org-team
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/verified-resources/phones
Required membership role: `team admin`. PBAC permission: `team.readVerifiedResources`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_VERIFIED_RESOURCES_READ` scope is required.
# Get verified email of an org team by id
Source: https://cal.com/docs/api-reference/v2/organization-team-verified-resources/get-verified-email-of-an-org-team-by-id
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/verified-resources/emails/{id}
Required membership role: `team admin`. PBAC permission: `team.readVerifiedResources`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_VERIFIED_RESOURCES_READ` scope is required.
# Get verified phone number of an org team by id
Source: https://cal.com/docs/api-reference/v2/organization-team-verified-resources/get-verified-phone-number-of-an-org-team-by-id
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/verified-resources/phones/{id}
Required membership role: `team admin`. PBAC permission: `team.readVerifiedResources`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_VERIFIED_RESOURCES_READ` scope is required.
# Request email verification code
Source: https://cal.com/docs/api-reference/v2/organization-team-verified-resources/request-email-verification-code
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/teams/{teamId}/verified-resources/emails/verification-code/request
Sends a verification code to the email. Required membership role: `team admin`. PBAC permission: `team.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_VERIFIED_RESOURCES_WRITE` scope is required.
# Request phone number verification code
Source: https://cal.com/docs/api-reference/v2/organization-team-verified-resources/request-phone-number-verification-code
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/teams/{teamId}/verified-resources/phones/verification-code/request
Sends a verification code to the phone number. Required membership role: `team admin`. PBAC permission: `team.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_VERIFIED_RESOURCES_WRITE` scope is required.
# Verify a phone number for an org team
Source: https://cal.com/docs/api-reference/v2/organization-team-verified-resources/verify-a-phone-number-for-an-org-team
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/teams/{teamId}/verified-resources/phones/verification-code/verify
Use code to verify a phone number. Required membership role: `team admin`. PBAC permission: `team.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_VERIFIED_RESOURCES_WRITE` scope is required.
# Verify an email for an org team
Source: https://cal.com/docs/api-reference/v2/organization-team-verified-resources/verify-an-email-for-an-org-team
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/teams/{teamId}/verified-resources/emails/verification-code/verify
Use code to verify an email. Required membership role: `team admin`. PBAC permission: `team.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_VERIFIED_RESOURCES_WRITE` scope is required.
# Assign an attribute to a user
Source: https://cal.com/docs/api-reference/v2/orgs-attributes-options/assign-an-attribute-to-a-user
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/attributes/options/{userId}
Required membership role: `org admin`. PBAC permission: `organization.attributes.editUsers`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Create an attribute option
Source: https://cal.com/docs/api-reference/v2/orgs-attributes-options/create-an-attribute-option
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/attributes/{attributeId}/options
Required membership role: `org admin`. PBAC permission: `organization.attributes.create`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Delete an attribute option
Source: https://cal.com/docs/api-reference/v2/orgs-attributes-options/delete-an-attribute-option
/api-reference/v2/openapi.json delete /v2/organizations/{orgId}/attributes/{attributeId}/options/{optionId}
Required membership role: `org admin`. PBAC permission: `organization.attributes.delete`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Get all assigned attribute options by attribute ID
Source: https://cal.com/docs/api-reference/v2/orgs-attributes-options/get-all-assigned-attribute-options-by-attribute-id
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/attributes/{attributeId}/options/assigned
Required membership role: `org member`. PBAC permission: `organization.attributes.read`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Get all assigned attribute options by attribute slug
Source: https://cal.com/docs/api-reference/v2/orgs-attributes-options/get-all-assigned-attribute-options-by-attribute-slug
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/attributes/slugs/{attributeSlug}/options/assigned
Required membership role: `org member`. PBAC permission: `organization.attributes.read`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Get all attribute options
Source: https://cal.com/docs/api-reference/v2/orgs-attributes-options/get-all-attribute-options
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/attributes/{attributeId}/options
Required membership role: `org member`. PBAC permission: `organization.attributes.read`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Get all attribute options for a user
Source: https://cal.com/docs/api-reference/v2/orgs-attributes-options/get-all-attribute-options-for-a-user
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/attributes/options/{userId}
Required membership role: `org member`. PBAC permission: `organization.attributes.read`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Get attribute assignment history for a user
Source: https://cal.com/docs/api-reference/v2/orgs-attributes-options/get-attribute-assignment-history-for-a-user
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/attributes/assignments/{userId}/history
Required membership role: `org admin`. PBAC permission: `organization.attributes.readAuditLogs`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Unassign an attribute from a user
Source: https://cal.com/docs/api-reference/v2/orgs-attributes-options/unassign-an-attribute-from-a-user
/api-reference/v2/openapi.json delete /v2/organizations/{orgId}/attributes/options/{userId}/{attributeOptionId}
Required membership role: `org admin`. PBAC permission: `organization.attributes.editUsers`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Update an attribute assignment for a user
Source: https://cal.com/docs/api-reference/v2/orgs-attributes-options/update-an-attribute-assignment-for-a-user
/api-reference/v2/openapi.json patch /v2/organizations/{orgId}/attributes/options/{userId}/{attributeOptionId}
Required membership role: `org admin`. PBAC permission: `organization.attributes.editUsers`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Update an attribute option
Source: https://cal.com/docs/api-reference/v2/orgs-attributes-options/update-an-attribute-option
/api-reference/v2/openapi.json patch /v2/organizations/{orgId}/attributes/{attributeId}/options/{optionId}
Required membership role: `org admin`. PBAC permission: `organization.attributes.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Get attribute sync history
Source: https://cal.com/docs/api-reference/v2/orgs-attributes-sync-history/get-attribute-sync-history
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/attributes/sync-history
Returns a paginated list of integration sync runs for the organization, including rule evaluations, applied field mappings, and team membership changes per run. Supports cursor-based pagination.
# Get sync history for a specific user
Source: https://cal.com/docs/api-reference/v2/orgs-attributes-sync-history/get-sync-history-for-a-specific-user
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/attributes/sync-history/user/{userId}
Returns a paginated list of integration sync runs filtered by user within the organization, including rule evaluations, applied field mappings, and team membership changes per run. Supports cursor-based pagination.
# Create an attribute
Source: https://cal.com/docs/api-reference/v2/orgs-attributes/create-an-attribute
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/attributes
Required membership role: `org admin`. PBAC permission: `organization.attributes.create`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Delete an attribute
Source: https://cal.com/docs/api-reference/v2/orgs-attributes/delete-an-attribute
/api-reference/v2/openapi.json delete /v2/organizations/{orgId}/attributes/{attributeId}
Required membership role: `org admin`. PBAC permission: `organization.attributes.delete`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Get all attributes
Source: https://cal.com/docs/api-reference/v2/orgs-attributes/get-all-attributes
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/attributes
Required membership role: `org member`. PBAC permission: `organization.attributes.read`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Get an attribute
Source: https://cal.com/docs/api-reference/v2/orgs-attributes/get-an-attribute
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/attributes/{attributeId}
Required membership role: `org member`. PBAC permission: `organization.attributes.read`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Update an attribute
Source: https://cal.com/docs/api-reference/v2/orgs-attributes/update-an-attribute
/api-reference/v2/openapi.json patch /v2/organizations/{orgId}/attributes/{attributeId}
Required membership role: `org admin`. PBAC permission: `organization.attributes.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Block an organization booking attendee
Source: https://cal.com/docs/api-reference/v2/orgs-bookings/block-an-organization-booking-attendee
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/bookings/block
Add the email or domain of a booking attendee to the organization blocklist. All matching upcoming bookings in the organization are silently cancelled. If accessed using an OAuth access token, the `ORG_BOOKING_WRITE` scope is required.
# Get organization bookings (cursor pagination)
Source: https://cal.com/docs/api-reference/v2/orgs-bookings/get-organization-bookings-cursor-pagination
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/bookings
Cursor-paginated org bookings listing. Pass the `pagination.nextCursor` from the previous response as the `cursor` query parameter to fetch the next page. Omit `cursor` to fetch the first page. `pagination.hasMore` is `false` and `pagination.nextCursor` is `null` when you've reached the last page.
Must pass `cal-api-version: 2026-05-01` header.
**`status` filters to a single status.** Pass one (e.g. `?status=upcoming`) or omit to walk all statuses. To list bookings across multiple statuses, issue parallel requests — one per status — and merge client-side.
Required membership role: `org admin`. PBAC permission: `booking.readOrgBookings`. If accessed using an OAuth access token, the `ORG_BOOKING_READ` scope is required.
For managed organizations, bookings where the managed organization owner is the organizer or an attendee are excluded from the result.
# Report an organization booking
Source: https://cal.com/docs/api-reference/v2/orgs-bookings/report-an-organization-booking
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/bookings/report
Report a booking within the organization. A booking report is created and the reported booking along with other matching upcoming bookings are silently cancelled. If accessed using an OAuth access token, the `ORG_BOOKING_WRITE` scope is required.
# Delete delegation credentials of your organization
Source: https://cal.com/docs/api-reference/v2/orgs-delegation-credentials/delete-delegation-credentials-of-your-organization
/api-reference/v2/openapi.json delete /v2/organizations/{orgId}/delegation-credentials/{credentialId}
Required membership role: `org admin`. PBAC permission: `organization.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Get a delegation credential of your organization
Source: https://cal.com/docs/api-reference/v2/orgs-delegation-credentials/get-a-delegation-credential-of-your-organization
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/delegation-credentials/{credentialId}
Required membership role: `org admin`. PBAC permission: `organization.readDelegationCredentials`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Get all delegation credentials of your organization
Source: https://cal.com/docs/api-reference/v2/orgs-delegation-credentials/get-all-delegation-credentials-of-your-organization
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/delegation-credentials
Optionally filter by `domain` to look up the delegation credential configured for a specific domain. Required membership role: `org admin`. PBAC permission: `organization.readDelegationCredentials`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Reset a blocked delegation credential's secret rotation state
Source: https://cal.com/docs/api-reference/v2/orgs-delegation-credentials/reset-a-blocked-delegation-credentials-secret-rotation-state
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/delegation-credentials/{credentialId}/reset-rotation
Clears the terminal secret-rotation failure state so managed secret rotation resumes normally. Use this after fixing whatever external configuration issues caused rotation to reach a terminal state. Required membership role: `org admin`. PBAC permission: `organization.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Save delegation credentials for your organization
Source: https://cal.com/docs/api-reference/v2/orgs-delegation-credentials/save-delegation-credentials-for-your-organization
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/delegation-credentials
Required membership role: `org admin`. PBAC permission: `organization.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Update delegation credentials of your organization
Source: https://cal.com/docs/api-reference/v2/orgs-delegation-credentials/update-delegation-credentials-of-your-organization
/api-reference/v2/openapi.json patch /v2/organizations/{orgId}/delegation-credentials/{credentialId}
Required membership role: `org admin`. PBAC permission: `organization.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Create a membership
Source: https://cal.com/docs/api-reference/v2/orgs-memberships/create-a-membership
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/memberships
Required membership role: `org admin`. PBAC permission: `organization.invite`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_MEMBERSHIP_WRITE` scope is required.
Two identifier modes are accepted in the request body:
1. **`userId`** — attach an existing user (by id) directly. Use when the caller already knows the Cal.com user id and just needs the membership row created/updated. No notification email is sent.
2. **`email`** — invite an existing Cal.com user (by email). Runs the same invite flow the dashboard uses; the user must already exist (if not, the request returns `404` — create them first via `POST /v2/organizations/{orgId}/users`). What happens next depends on the organization's settings:
- **Auto-attach path.** When the organization is verified by Cal.com and the invitee's email domain matches the organization's configured auto-accept domain (both set by Cal.com during organization verification — not editable by the organization's own admins), the user is added as an accepted member: their organization profile is created, the membership is created with `accepted: true`, and they receive a "you've been added to the organization" notification email.
- **Pending-invite path.** If either condition is not met, the user receives an "accept your invite" notification email and the membership is created with `accepted: false` until they accept it via the dashboard. No organization profile is created until acceptance.
Either way the response's `accepted` value reflects the outcome, seat addition is recorded, and the new seat count is pushed to the billing provider.
# Delete a membership
Source: https://cal.com/docs/api-reference/v2/orgs-memberships/delete-a-membership
/api-reference/v2/openapi.json delete /v2/organizations/{orgId}/memberships/{membershipId}
Required membership role: `org admin`. PBAC permission: `organization.remove`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_MEMBERSHIP_WRITE` scope is required.
# Get a membership
Source: https://cal.com/docs/api-reference/v2/orgs-memberships/get-a-membership
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/memberships/{membershipId}
Required membership role: `org admin`. PBAC permission: `organization.readMemberships`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_MEMBERSHIP_READ` scope is required.
# Get all memberships
Source: https://cal.com/docs/api-reference/v2/orgs-memberships/get-all-memberships
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/memberships
Required membership role: `org admin`. PBAC permission: `organization.readMemberships`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_MEMBERSHIP_READ` scope is required.
# Update a membership
Source: https://cal.com/docs/api-reference/v2/orgs-memberships/update-a-membership
/api-reference/v2/openapi.json patch /v2/organizations/{orgId}/memberships/{membershipId}
Required membership role: `org admin`. PBAC permission: `organization.changeMemberRole`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_MEMBERSHIP_WRITE` scope is required.
# Add permissions to an organization role (single or batch)
Source: https://cal.com/docs/api-reference/v2/orgs-roles-permissions/add-permissions-to-an-organization-role-single-or-batch
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/roles/{roleId}/permissions
Required membership role: `org admin`. PBAC permission: `role.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# List permissions for an organization role
Source: https://cal.com/docs/api-reference/v2/orgs-roles-permissions/list-permissions-for-an-organization-role
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/roles/{roleId}/permissions
Required membership role: `org admin`. PBAC permission: `role.readOrgRoles`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Remove a permission from an organization role
Source: https://cal.com/docs/api-reference/v2/orgs-roles-permissions/remove-a-permission-from-an-organization-role
/api-reference/v2/openapi.json delete /v2/organizations/{orgId}/roles/{roleId}/permissions/{permission}
Required membership role: `org admin`. PBAC permission: `role.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Remove multiple permissions from an organization role
Source: https://cal.com/docs/api-reference/v2/orgs-roles-permissions/remove-multiple-permissions-from-an-organization-role
/api-reference/v2/openapi.json delete /v2/organizations/{orgId}/roles/{roleId}/permissions
Required membership role: `org admin`. PBAC permission: `role.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Replace all permissions for an organization role
Source: https://cal.com/docs/api-reference/v2/orgs-roles-permissions/replace-all-permissions-for-an-organization-role
/api-reference/v2/openapi.json put /v2/organizations/{orgId}/roles/{roleId}/permissions
Required membership role: `org admin`. PBAC permission: `role.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Create a new organization role
Source: https://cal.com/docs/api-reference/v2/orgs-roles/create-a-new-organization-role
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/roles
Required membership role: `org admin`. PBAC permission: `role.create`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Delete an organization role
Source: https://cal.com/docs/api-reference/v2/orgs-roles/delete-an-organization-role
/api-reference/v2/openapi.json delete /v2/organizations/{orgId}/roles/{roleId}
Required membership role: `org admin`. PBAC permission: `role.delete`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Get a specific organization role
Source: https://cal.com/docs/api-reference/v2/orgs-roles/get-a-specific-organization-role
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/roles/{roleId}
Required membership role: `org admin`. PBAC permission: `role.readOrgRoles`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Get all organization roles
Source: https://cal.com/docs/api-reference/v2/orgs-roles/get-all-organization-roles
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/roles
Required membership role: `org admin`. PBAC permission: `role.readOrgRoles`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Update an organization role
Source: https://cal.com/docs/api-reference/v2/orgs-roles/update-an-organization-role
/api-reference/v2/openapi.json patch /v2/organizations/{orgId}/roles/{roleId}
Required membership role: `org admin`. PBAC permission: `role.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Create routing form response and get available slots
Source: https://cal.com/docs/api-reference/v2/orgs-routing-forms/create-routing-form-response-and-get-available-slots
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/routing-forms/{routingFormId}/responses
Required membership role: `org admin`. PBAC permission: `routingForm.create`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_ROUTING_FORM_WRITE` scope is required.
# Get organization routing forms
Source: https://cal.com/docs/api-reference/v2/orgs-routing-forms/get-organization-routing-forms
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/routing-forms
Required membership role: `org admin`. PBAC permission: `routingForm.readOrgRoutingForms`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_ROUTING_FORM_READ` scope is required.
# Get routing form responses
Source: https://cal.com/docs/api-reference/v2/orgs-routing-forms/get-routing-form-responses
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/routing-forms/{routingFormId}/responses
Required membership role: `org admin`. PBAC permission: `routingForm.readResponsePii`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_ROUTING_FORM_READ` scope is required.
# Update routing form response
Source: https://cal.com/docs/api-reference/v2/orgs-routing-forms/update-routing-form-response
/api-reference/v2/openapi.json patch /v2/organizations/{orgId}/routing-forms/{routingFormId}/responses/{responseId}
Required membership role: `org admin`. PBAC permission: `routingForm.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_ROUTING_FORM_WRITE` scope is required.
# Get all schedules
Source: https://cal.com/docs/api-reference/v2/orgs-schedules/get-all-schedules
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/schedules
Required membership role: `org admin`. PBAC permission: `availability.readOrgAvailability`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_SCHEDULE_READ` scope is required.
# Get booking references
Source: https://cal.com/docs/api-reference/v2/orgs-teams-bookings/get-booking-references
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/bookings/{bookingUid}/references
Required membership role: `team admin`. PBAC permission: `booking.readTeamBookings`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_BOOKING_READ` scope is required.
# Get organization team bookings
Source: https://cal.com/docs/api-reference/v2/orgs-teams-bookings/get-organization-team-bookings
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/bookings
Required membership role: `team admin`. PBAC permission: `booking.readTeamBookings`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_BOOKING_READ` scope is required.
# Connect your conferencing application to a team
Source: https://cal.com/docs/api-reference/v2/orgs-teams-conferencing/connect-your-conferencing-application-to-a-team
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/teams/{teamId}/conferencing/{app}/connect
Only `google-meet` can be connected this way, and only for a team that already has a valid Google Calendar connection. Zoom and Microsoft Teams require an OAuth handshake - start it with `GET /v2/organizations/{orgId}/teams/{teamId}/conferencing/{app}/oauth/auth-url` instead. Walkthrough: https://cal.com/docs/api-reference/v2/conferencing-apps. Required membership role: `team admin`. PBAC permission: `team.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_APPS_WRITE` scope is required.
# Disconnect team conferencing application
Source: https://cal.com/docs/api-reference/v2/orgs-teams-conferencing/disconnect-team-conferencing-application
/api-reference/v2/openapi.json delete /v2/organizations/{orgId}/teams/{teamId}/conferencing/{app}/disconnect
Required membership role: `team admin`. PBAC permission: `team.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_APPS_WRITE` scope is required.
# Get OAuth conferencing app's auth URL for a team
Source: https://cal.com/docs/api-reference/v2/orgs-teams-conferencing/get-oauth-conferencing-apps-auth-url-for-a-team
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/conferencing/{app}/oauth/auth-url
Returns the URL to send a team admin to so they can authorize Zoom or Microsoft Teams for the team. Redirect their browser to it - the provider then returns them to a callback already registered by Cal.com, which stores the credential against the team rather than against the admin who authorized it. `returnTo` and `onErrorReturnTo` must each be a relative path or an absolute http(s) URL. The returned URL expires 10 minutes after this call, so redirect immediately rather than caching it. Walkthrough: https://cal.com/docs/api-reference/v2/conferencing-apps. Required membership role: `team admin`. PBAC permission: `team.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_APPS_WRITE` scope is required.
# Get team default conferencing application
Source: https://cal.com/docs/api-reference/v2/orgs-teams-conferencing/get-team-default-conferencing-application
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/conferencing/default
Required membership role: `team admin`. PBAC permission: `team.readConferencing`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_APPS_READ` scope is required.
# List team conferencing applications
Source: https://cal.com/docs/api-reference/v2/orgs-teams-conferencing/list-team-conferencing-applications
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/conferencing
Required membership role: `team admin`. PBAC permission: `team.readConferencing`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_APPS_READ` scope is required.
# Save conferencing app OAuth credentials
Source: https://cal.com/docs/api-reference/v2/orgs-teams-conferencing/save-conferencing-app-oauth-credentials
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/conferencing/{app}/oauth/callback
Required membership role: `team admin`. PBAC permission: `team.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_APPS_WRITE` scope is required.
# Set team default conferencing application
Source: https://cal.com/docs/api-reference/v2/orgs-teams-conferencing/set-team-default-conferencing-application
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/teams/{teamId}/conferencing/{app}/default
Required membership role: `team admin`. PBAC permission: `team.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_APPS_WRITE` scope is required.
# Create a private link for a team event type
Source: https://cal.com/docs/api-reference/v2/orgs-teams-event-types-private-links/create-a-private-link-for-a-team-event-type
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/teams/{teamId}/event-types/{eventTypeId}/private-links
If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_WRITE` scope is required.
# Delete a private link for a team event type
Source: https://cal.com/docs/api-reference/v2/orgs-teams-event-types-private-links/delete-a-private-link-for-a-team-event-type
/api-reference/v2/openapi.json delete /v2/organizations/{orgId}/teams/{teamId}/event-types/{eventTypeId}/private-links/{linkId}
If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_WRITE` scope is required.
# Get all private links for a team event type
Source: https://cal.com/docs/api-reference/v2/orgs-teams-event-types-private-links/get-all-private-links-for-a-team-event-type
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/event-types/{eventTypeId}/private-links
If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_READ` scope is required.
# Update a private link for a team event type
Source: https://cal.com/docs/api-reference/v2/orgs-teams-event-types-private-links/update-a-private-link-for-a-team-event-type
/api-reference/v2/openapi.json patch /v2/organizations/{orgId}/teams/{teamId}/event-types/{eventTypeId}/private-links/{linkId}
If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_WRITE` scope is required.
# Create team invite link
Source: https://cal.com/docs/api-reference/v2/orgs-teams-invite/create-team-invite-link
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/teams/{teamId}/invite
Required membership role: `team admin`. PBAC permission: `team.invite`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_MEMBERSHIP_WRITE` scope is required.
# Create a membership
Source: https://cal.com/docs/api-reference/v2/orgs-teams-memberships/create-a-membership
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/teams/{teamId}/memberships
Required membership role: `team admin`. PBAC permission: `team.invite`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_MEMBERSHIP_WRITE` scope is required.
# Delete a membership
Source: https://cal.com/docs/api-reference/v2/orgs-teams-memberships/delete-a-membership
/api-reference/v2/openapi.json delete /v2/organizations/{orgId}/teams/{teamId}/memberships/{membershipId}
Required membership role: `team admin`. PBAC permission: `team.remove`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_MEMBERSHIP_WRITE` scope is required.
# Get a membership
Source: https://cal.com/docs/api-reference/v2/orgs-teams-memberships/get-a-membership
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/memberships/{membershipId}
Required membership role: `team admin`. PBAC permission: `team.readMemberships`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_MEMBERSHIP_READ` scope is required.
# Get all memberships
Source: https://cal.com/docs/api-reference/v2/orgs-teams-memberships/get-all-memberships
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/memberships
Required membership role: `team admin`. PBAC permission: `team.readMemberships`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_MEMBERSHIP_READ` scope is required.
# Update a membership
Source: https://cal.com/docs/api-reference/v2/orgs-teams-memberships/update-a-membership
/api-reference/v2/openapi.json patch /v2/organizations/{orgId}/teams/{teamId}/memberships/{membershipId}
Required membership role: `team admin`. PBAC permission: `team.changeMemberRole`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_MEMBERSHIP_WRITE` scope is required.
# Add permissions to an organization team role (single or batch)
Source: https://cal.com/docs/api-reference/v2/orgs-teams-roles-permissions/add-permissions-to-an-organization-team-role-single-or-batch
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/teams/{teamId}/roles/{roleId}/permissions
# List permissions for an organization team role
Source: https://cal.com/docs/api-reference/v2/orgs-teams-roles-permissions/list-permissions-for-an-organization-team-role
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/roles/{roleId}/permissions
Required membership role: `org admin`. PBAC permission: `role.readTeamRoles`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Remove a permission from an organization team role
Source: https://cal.com/docs/api-reference/v2/orgs-teams-roles-permissions/remove-a-permission-from-an-organization-team-role
/api-reference/v2/openapi.json delete /v2/organizations/{orgId}/teams/{teamId}/roles/{roleId}/permissions/{permission}
# Remove multiple permissions from an organization team role
Source: https://cal.com/docs/api-reference/v2/orgs-teams-roles-permissions/remove-multiple-permissions-from-an-organization-team-role
/api-reference/v2/openapi.json delete /v2/organizations/{orgId}/teams/{teamId}/roles/{roleId}/permissions
# Replace all permissions for an organization team role
Source: https://cal.com/docs/api-reference/v2/orgs-teams-roles-permissions/replace-all-permissions-for-an-organization-team-role
/api-reference/v2/openapi.json put /v2/organizations/{orgId}/teams/{teamId}/roles/{roleId}/permissions
# Create a new organization team role
Source: https://cal.com/docs/api-reference/v2/orgs-teams-roles/create-a-new-organization-team-role
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/teams/{teamId}/roles
# Delete an organization team role
Source: https://cal.com/docs/api-reference/v2/orgs-teams-roles/delete-an-organization-team-role
/api-reference/v2/openapi.json delete /v2/organizations/{orgId}/teams/{teamId}/roles/{roleId}
# Get a specific organization team role
Source: https://cal.com/docs/api-reference/v2/orgs-teams-roles/get-a-specific-organization-team-role
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/roles/{roleId}
Required membership role: `org admin`. PBAC permission: `role.readTeamRoles`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Get all organization team roles
Source: https://cal.com/docs/api-reference/v2/orgs-teams-roles/get-all-organization-team-roles
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/roles
Required membership role: `org admin`. PBAC permission: `role.readTeamRoles`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Update an organization team role
Source: https://cal.com/docs/api-reference/v2/orgs-teams-roles/update-an-organization-team-role
/api-reference/v2/openapi.json patch /v2/organizations/{orgId}/teams/{teamId}/roles/{roleId}
# Create routing form response and get available slots
Source: https://cal.com/docs/api-reference/v2/orgs-teams-routing-forms-responses/create-routing-form-response-and-get-available-slots
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/teams/{teamId}/routing-forms/{routingFormId}/responses
Required membership role: `team member`. PBAC permission: `routingForm.create`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_ROUTING_FORM_WRITE` scope is required.
# Get organization team routing form responses
Source: https://cal.com/docs/api-reference/v2/orgs-teams-routing-forms-responses/get-organization-team-routing-form-responses
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/routing-forms/{routingFormId}/responses
Required membership role: `team admin`. PBAC permission: `routingForm.readResponsePii`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_ROUTING_FORM_READ` scope is required.
# Update routing form response
Source: https://cal.com/docs/api-reference/v2/orgs-teams-routing-forms-responses/update-routing-form-response
/api-reference/v2/openapi.json patch /v2/organizations/{orgId}/teams/{teamId}/routing-forms/{routingFormId}/responses/{responseId}
Required membership role: `team admin`. PBAC permission: `routingForm.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_ROUTING_FORM_WRITE` scope is required.
# Get team routing forms
Source: https://cal.com/docs/api-reference/v2/orgs-teams-routing-forms/get-team-routing-forms
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/routing-forms
Required membership role: `team admin`. PBAC permission: `routingForm.readTeamRoutingForms`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_ROUTING_FORM_READ` scope is required.
# Get all team member schedules
Source: https://cal.com/docs/api-reference/v2/orgs-teams-schedules/get-all-team-member-schedules
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/schedules
Required membership role: `team admin`. PBAC permission: `availability.readTeamAvailability`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_SCHEDULE_READ` scope is required.
# Check team Stripe connection
Source: https://cal.com/docs/api-reference/v2/orgs-teams-stripe/check-team-stripe-connection
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/stripe/check
Required membership role: `team admin`. PBAC permission: `organization.manageBilling`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Get Stripe connect URL for a team
Source: https://cal.com/docs/api-reference/v2/orgs-teams-stripe/get-stripe-connect-url-for-a-team
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/stripe/connect
Required membership role: `team admin`. PBAC permission: `organization.manageBilling`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Save Stripe credentials
Source: https://cal.com/docs/api-reference/v2/orgs-teams-stripe/save-stripe-credentials
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/stripe/save
Required membership role: `team admin`. PBAC permission: `organization.manageBilling`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control
# Get schedules of a team member
Source: https://cal.com/docs/api-reference/v2/orgs-teams-users-schedules/get-schedules-of-a-team-member
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/users/{userId}/schedules
Required membership role: `team admin`. PBAC permission: `availability.readTeamAvailability`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_SCHEDULE_READ` scope is required.
# Create organization team workflow for event-types
Source: https://cal.com/docs/api-reference/v2/orgs-teams-workflows/create-organization-team-workflow-for-event-types
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/teams/{teamId}/workflows
Required membership role: `team admin`. PBAC permission: `workflow.create`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_WORKFLOW_WRITE` scope is required.
# Create organization team workflow for routing-forms
Source: https://cal.com/docs/api-reference/v2/orgs-teams-workflows/create-organization-team-workflow-for-routing-forms
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/teams/{teamId}/workflows/routing-form
Required membership role: `team admin`. PBAC permission: `workflow.create`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_WORKFLOW_WRITE` scope is required.
# Delete organization team routing-form workflow
Source: https://cal.com/docs/api-reference/v2/orgs-teams-workflows/delete-organization-team-routing-form-workflow
/api-reference/v2/openapi.json delete /v2/organizations/{orgId}/teams/{teamId}/workflows/{workflowId}/routing-form
Required membership role: `team admin`. PBAC permission: `workflow.delete`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_WORKFLOW_WRITE` scope is required.
# Delete organization team workflow
Source: https://cal.com/docs/api-reference/v2/orgs-teams-workflows/delete-organization-team-workflow
/api-reference/v2/openapi.json delete /v2/organizations/{orgId}/teams/{teamId}/workflows/{workflowId}
Required membership role: `team admin`. PBAC permission: `workflow.delete`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_WORKFLOW_WRITE` scope is required.
# Get organization team routing form workflow
Source: https://cal.com/docs/api-reference/v2/orgs-teams-workflows/get-organization-team-routing-form-workflow
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/workflows/{workflowId}/routing-form
Required membership role: `team admin`. PBAC permission: `workflow.readTeamWorkflows`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_WORKFLOW_READ` scope is required.
# Get organization team routing form workflows
Source: https://cal.com/docs/api-reference/v2/orgs-teams-workflows/get-organization-team-routing-form-workflows
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/workflows/routing-form
Required membership role: `team admin`. PBAC permission: `workflow.readTeamWorkflows`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_WORKFLOW_READ` scope is required.
# Get organization team workflow
Source: https://cal.com/docs/api-reference/v2/orgs-teams-workflows/get-organization-team-workflow
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/workflows/{workflowId}
Required membership role: `team admin`. PBAC permission: `workflow.readTeamWorkflows`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_WORKFLOW_READ` scope is required.
# Get organization team workflows
Source: https://cal.com/docs/api-reference/v2/orgs-teams-workflows/get-organization-team-workflows
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/workflows
Required membership role: `team admin`. PBAC permission: `workflow.readTeamWorkflows`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_WORKFLOW_READ` scope is required.
# Update organization routing form team workflow
Source: https://cal.com/docs/api-reference/v2/orgs-teams-workflows/update-organization-routing-form-team-workflow
/api-reference/v2/openapi.json patch /v2/organizations/{orgId}/teams/{teamId}/workflows/{workflowId}/routing-form
Required membership role: `team admin`. PBAC permission: `workflow.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_WORKFLOW_WRITE` scope is required.
# Update organization team workflow
Source: https://cal.com/docs/api-reference/v2/orgs-teams-workflows/update-organization-team-workflow
/api-reference/v2/openapi.json patch /v2/organizations/{orgId}/teams/{teamId}/workflows/{workflowId}
Required membership role: `team admin`. PBAC permission: `workflow.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_WORKFLOW_WRITE` scope is required.
# Create a team
Source: https://cal.com/docs/api-reference/v2/orgs-teams/create-a-team
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/teams
Required membership role: `org admin`. PBAC permission: `team.create`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_PROFILE_WRITE` scope is required.
# Delete a team
Source: https://cal.com/docs/api-reference/v2/orgs-teams/delete-a-team
/api-reference/v2/openapi.json delete /v2/organizations/{orgId}/teams/{teamId}
Required membership role: `org admin`. PBAC permission: `team.delete`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_PROFILE_WRITE` scope is required.
# Get a team
Source: https://cal.com/docs/api-reference/v2/orgs-teams/get-a-team
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}
Required membership role: `team admin`. PBAC permission: `team.readTeamSettings`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_PROFILE_READ` scope is required.
# Get all teams
Source: https://cal.com/docs/api-reference/v2/orgs-teams/get-all-teams
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams
Required membership role: `org admin`. PBAC permission: `team.readOrgTeams`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_PROFILE_READ` scope is required.
# Get teams membership for user
Source: https://cal.com/docs/api-reference/v2/orgs-teams/get-teams-membership-for-user
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/me
Required membership role: `org member`. PBAC permission: `team.read`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_MEMBERSHIP_READ` scope is required.
# Update a team
Source: https://cal.com/docs/api-reference/v2/orgs-teams/update-a-team
/api-reference/v2/openapi.json patch /v2/organizations/{orgId}/teams/{teamId}
Required membership role: `org admin`. PBAC permission: `team.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_PROFILE_WRITE` scope is required.
# Get all bookings for an organization user
Source: https://cal.com/docs/api-reference/v2/orgs-users-bookings/get-all-bookings-for-an-organization-user
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/users/{userId}/bookings
If accessed using an OAuth access token, the `ORG_BOOKING_READ` scope is required.
# List conferencing apps of an organization user
Source: https://cal.com/docs/api-reference/v2/orgs-users-conferencing/list-conferencing-apps-of-an-organization-user
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/users/{userId}/conferencing
Lists all connected conferencing apps for a specific user within the organization, including which one is set as default. If accessed using an OAuth access token, the `APPS_READ` scope is required.
# Set the default conferencing app for an organization user
Source: https://cal.com/docs/api-reference/v2/orgs-users-conferencing/set-the-default-conferencing-app-for-an-organization-user
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/users/{userId}/conferencing/default
Sets the default conferencing app for a specific user within the organization. If accessed using an OAuth access token, the `APPS_WRITE` scope is required.
# Create an out-of-office entry for a user
Source: https://cal.com/docs/api-reference/v2/orgs-users-ooo/create-an-out-of-office-entry-for-a-user
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/users/{userId}/ooo
If accessed using an OAuth access token, the `ORG_SCHEDULE_WRITE` scope is required.
# Delete an out-of-office entry for a user
Source: https://cal.com/docs/api-reference/v2/orgs-users-ooo/delete-an-out-of-office-entry-for-a-user
/api-reference/v2/openapi.json delete /v2/organizations/{orgId}/users/{userId}/ooo/{oooId}
If accessed using an OAuth access token, the `ORG_SCHEDULE_WRITE` scope is required.
# Get all out-of-office entries for a user
Source: https://cal.com/docs/api-reference/v2/orgs-users-ooo/get-all-out-of-office-entries-for-a-user
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/users/{userId}/ooo
Required membership role: `org admin`. PBAC permission: `ooo.readOrgOoo`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_SCHEDULE_READ` scope is required.
# Get all out-of-office entries for organization users
Source: https://cal.com/docs/api-reference/v2/orgs-users-ooo/get-all-out-of-office-entries-for-organization-users
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/ooo
Required membership role: `org admin`. PBAC permission: `ooo.readOrgOoo`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_SCHEDULE_READ` scope is required.
# Update an out-of-office entry for a user
Source: https://cal.com/docs/api-reference/v2/orgs-users-ooo/update-an-out-of-office-entry-for-a-user
/api-reference/v2/openapi.json patch /v2/organizations/{orgId}/users/{userId}/ooo/{oooId}
If accessed using an OAuth access token, the `ORG_SCHEDULE_WRITE` scope is required.
# Create a schedule
Source: https://cal.com/docs/api-reference/v2/orgs-users-schedules/create-a-schedule
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/users/{userId}/schedules
Required membership role: `org admin`. PBAC permission: `availability.create`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_SCHEDULE_WRITE` scope is required.
# Delete a schedule
Source: https://cal.com/docs/api-reference/v2/orgs-users-schedules/delete-a-schedule
/api-reference/v2/openapi.json delete /v2/organizations/{orgId}/users/{userId}/schedules/{scheduleId}
Required membership role: `org admin`. PBAC permission: `availability.delete`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_SCHEDULE_WRITE` scope is required.
# Get a schedule
Source: https://cal.com/docs/api-reference/v2/orgs-users-schedules/get-a-schedule
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/users/{userId}/schedules/{scheduleId}
Required membership role: `org admin`. PBAC permission: `availability.readOrgAvailability`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_SCHEDULE_READ` scope is required.
# Get all schedules
Source: https://cal.com/docs/api-reference/v2/orgs-users-schedules/get-all-schedules
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/users/{userId}/schedules
Required membership role: `org admin`. PBAC permission: `availability.readOrgAvailability`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_SCHEDULE_READ` scope is required.
# Update a schedule
Source: https://cal.com/docs/api-reference/v2/orgs-users-schedules/update-a-schedule
/api-reference/v2/openapi.json patch /v2/organizations/{orgId}/users/{userId}/schedules/{scheduleId}
Required membership role: `org admin`. PBAC permission: `availability.updateOrgAvailability`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_SCHEDULE_WRITE` scope is required.
# Create a user
Source: https://cal.com/docs/api-reference/v2/orgs-users/create-a-user
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/users
Required membership role: `org admin`. PBAC permission: `organization.invite`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_MEMBERSHIP_WRITE` scope is required.
This endpoint creates a brand-new Cal.com user account and attaches them to the organization. If the email already belongs to an existing Cal.com user, the request fails with `409 Conflict`. When the user is already a member of this organization the message is `A user already exists with that email and is already a member of this organization`. When the user exists but is not a member, the message is `A user already exists with this email. Use POST /v2/organizations/{orgId}/memberships to add them to this organization.`. A common case is re-adding someone who was previously removed from the organization via the dashboard: the membership is gone, but the underlying Cal.com user account still exists.
To attach an existing Cal.com user to the organization, use `POST /v2/organizations/{orgId}/memberships` with the `email` field instead.
# Delete a user
Source: https://cal.com/docs/api-reference/v2/orgs-users/delete-a-user
/api-reference/v2/openapi.json delete /v2/organizations/{orgId}/users/{userId}
Required membership role: `org admin`. PBAC permission: `organization.remove`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_MEMBERSHIP_WRITE` scope is required.
# Get all users
Source: https://cal.com/docs/api-reference/v2/orgs-users/get-all-users
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/users
Required membership role: `org admin`. PBAC permission: `organization.readMemberships`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_MEMBERSHIP_READ` scope is required.
# Update a user
Source: https://cal.com/docs/api-reference/v2/orgs-users/update-a-user
/api-reference/v2/openapi.json patch /v2/organizations/{orgId}/users/{userId}
Required membership role: `org admin`. PBAC permission: `organization.editUsers`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_MEMBERSHIP_WRITE` scope is required.
# Create a webhook
Source: https://cal.com/docs/api-reference/v2/orgs-webhooks/create-a-webhook
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/webhooks
Required membership role: `org admin`. PBAC permission: `webhook.create`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_WEBHOOK_WRITE` scope is required.
# Delete a webhook
Source: https://cal.com/docs/api-reference/v2/orgs-webhooks/delete-a-webhook
/api-reference/v2/openapi.json delete /v2/organizations/{orgId}/webhooks/{webhookId}
Required membership role: `org admin`. PBAC permission: `webhook.delete`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_WEBHOOK_WRITE` scope is required.
# Get a webhook
Source: https://cal.com/docs/api-reference/v2/orgs-webhooks/get-a-webhook
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/webhooks/{webhookId}
Required membership role: `org admin`. PBAC permission: `webhook.readOrgWebhooks`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_WEBHOOK_READ` scope is required.
# Get all webhooks
Source: https://cal.com/docs/api-reference/v2/orgs-webhooks/get-all-webhooks
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/webhooks
Required membership role: `org admin`. PBAC permission: `webhook.readOrgWebhooks`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_WEBHOOK_READ` scope is required.
# Update a webhook
Source: https://cal.com/docs/api-reference/v2/orgs-webhooks/update-a-webhook
/api-reference/v2/openapi.json patch /v2/organizations/{orgId}/webhooks/{webhookId}
Required membership role: `org admin`. PBAC permission: `webhook.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `ORG_WEBHOOK_WRITE` scope is required.
# Create an out-of-office entry for the authenticated user
Source: https://cal.com/docs/api-reference/v2/out-of-office/create-an-out-of-office-entry-for-the-authenticated-user
/api-reference/v2/openapi.json post /v2/me/ooo
If accessed using an OAuth access token, the `SCHEDULE_WRITE` scope is required.
# Delete an out-of-office entry for the authenticated user
Source: https://cal.com/docs/api-reference/v2/out-of-office/delete-an-out-of-office-entry-for-the-authenticated-user
/api-reference/v2/openapi.json delete /v2/me/ooo/{oooId}
If accessed using an OAuth access token, the `SCHEDULE_WRITE` scope is required.
# Get all out-of-office entries for the authenticated user
Source: https://cal.com/docs/api-reference/v2/out-of-office/get-all-out-of-office-entries-for-the-authenticated-user
/api-reference/v2/openapi.json get /v2/me/ooo
If accessed using an OAuth access token, the `SCHEDULE_READ` scope is required.
# Update an out-of-office entry for the authenticated user
Source: https://cal.com/docs/api-reference/v2/out-of-office/update-an-out-of-office-entry-for-the-authenticated-user
/api-reference/v2/openapi.json patch /v2/me/ooo/{oooId}
If accessed using an OAuth access token, the `SCHEDULE_WRITE` scope is required.
# Calculate slots based on routing form response
Source: https://cal.com/docs/api-reference/v2/routing-forms/calculate-slots-based-on-routing-form-response
/api-reference/v2/openapi.json post /v2/routing-forms/{routingFormId}/calculate-slots
It will not actually save the response just return the routed event type and slots when it can be booked.
# Create a schedule
Source: https://cal.com/docs/api-reference/v2/schedules/create-a-schedule
/api-reference/v2/openapi.json post /v2/schedules
Create a schedule for the authenticated user.
The point of creating schedules is for event types to be available at specific times.
The first goal of schedules is to have a default schedule. If you are platform customer and created managed users, then it is important to note that each managed user should have a default schedule.
1. If you passed `timeZone` when creating managed user, then the default schedule from Monday to Friday from 9AM to 5PM will be created with that timezone. The managed user can then change the default schedule via the `AvailabilitySettings` atom.
2. If you did not, then we assume you want the user to have this specific schedule right away. You should create a default schedule by specifying
`"isDefault": true` in the request body. Until the user has a default schedule the user can't be booked nor manage their schedule via the AvailabilitySettings atom.
The second goal of schedules is to create another schedule that event types can point to. This is useful for when an event is booked because availability is not checked against the default schedule but instead against that specific schedule.
After creating a non-default schedule, you can update an event type to point to that schedule via the PATCH `event-types/{eventTypeId}` endpoint.
When specifying start time and end time for each day use the 24 hour format e.g. 08:00, 15:00 etc.
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
If accessed using an OAuth access token, the `SCHEDULE_WRITE` scope is required.
# Delete a schedule
Source: https://cal.com/docs/api-reference/v2/schedules/delete-a-schedule
/api-reference/v2/openapi.json delete /v2/schedules/{scheduleId}
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
If accessed using an OAuth access token, the `SCHEDULE_WRITE` scope is required.
# Get a schedule
Source: https://cal.com/docs/api-reference/v2/schedules/get-a-schedule
/api-reference/v2/openapi.json get /v2/schedules/{scheduleId}
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
If accessed using an OAuth access token, the `SCHEDULE_READ` scope is required.
# Get all schedules
Source: https://cal.com/docs/api-reference/v2/schedules/get-all-schedules
/api-reference/v2/openapi.json get /v2/schedules
Get all schedules of the authenticated user.
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
If accessed using an OAuth access token, the `SCHEDULE_READ` scope is required.
# Get default schedule
Source: https://cal.com/docs/api-reference/v2/schedules/get-default-schedule
/api-reference/v2/openapi.json get /v2/schedules/default
Get the default schedule of the authenticated user.
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
If accessed using an OAuth access token, the `SCHEDULE_READ` scope is required.
# Update a schedule
Source: https://cal.com/docs/api-reference/v2/schedules/update-a-schedule
/api-reference/v2/openapi.json patch /v2/schedules/{scheduleId}
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
If accessed using an OAuth access token, the `SCHEDULE_WRITE` scope is required.
# Add a selected calendar
Source: https://cal.com/docs/api-reference/v2/selected-calendars/add-a-selected-calendar
/api-reference/v2/openapi.json post /v2/selected-calendars
If accessed using an OAuth access token, the `APPS_WRITE` scope is required.
# Delete a selected calendar
Source: https://cal.com/docs/api-reference/v2/selected-calendars/delete-a-selected-calendar
/api-reference/v2/openapi.json delete /v2/selected-calendars
If accessed using an OAuth access token, the `APPS_WRITE` scope is required.
# Delete a reserved slot
Source: https://cal.com/docs/api-reference/v2/slots/delete-a-reserved-slot
/api-reference/v2/openapi.json delete /v2/slots/reservations/{uid}
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
# Get available time slots for an event type
Source: https://cal.com/docs/api-reference/v2/slots/get-available-time-slots-for-an-event-type
/api-reference/v2/openapi.json get /v2/slots
There are 4 ways to get available slots for event type of an individual user:
1. By event type id. Example '/v2/slots?eventTypeId=10&start=2033-09-05&end=2033-09-06&timeZone=Europe/Rome'
2. By event type slug + username. Example '/v2/slots?eventTypeSlug=intro&username=bob&start=2033-09-05&end=2033-09-06'
3. By event type slug + username + organization slug when searching within an organization. Example '/v2/slots?organizationSlug=org-slug&eventTypeSlug=intro&username=bob&start=2033-09-05&end=2033-09-06'
4. By usernames only (used for dynamic event type - there is no specific event but you want to know when 2 or more people are available). Example '/v2/slots?usernames=alice,bob&username=bob&organizationSlug=org-slug&start=2033-09-05&end=2033-09-06'. As you see you also need to provide the slug of the organization to which each user in the 'usernames' array belongs.
And 3 ways to get available slots for team event type:
1. By team event type id. Example '/v2/slots?eventTypeId=10&start=2033-09-05&end=2033-09-06&timeZone=Europe/Rome'.
**Note for managed event types**: Managed event types are templates that create individual child event types for each team member. You cannot fetch slots for the parent managed event type directly. Instead, you must:
- Find the child event type IDs (the ones assigned to specific users)
- Use those child event type IDs to fetch slots as individual user event types using as described in the individual user section above.
2. By team event type slug + team slug. Example '/v2/slots?eventTypeSlug=intro&teamSlug=team-slug&start=2033-09-05&end=2033-09-06'
3. By team event type slug + team slug + organization slug when searching within an organization. Example '/v2/slots?organizationSlug=org-slug&eventTypeSlug=intro&teamSlug=team-slug&start=2033-09-05&end=2033-09-06'
All of them require "start" and "end" query parameters which define the time range for which available slots should be checked.
Optional parameters are:
- timeZone: Time zone in which the available slots should be returned. Defaults to UTC.
- duration: Only use for event types that allow multiple durations or for dynamic event types. If not passed for multiple duration event types defaults to default duration. For dynamic event types defaults to 30 aka each returned slot is 30 minutes long. So duration=60 means that returned slots will be each 60 minutes long.
- format: Format of the slots. By default return is an object where each key is date and value is array of slots as string. If you want to get start and end of each slot use "range" as value.
- bookingUidToReschedule: When rescheduling an existing booking, provide the booking's unique identifier to exclude its time slot from busy time calculations. This ensures the original booking time appears as available for rescheduling.
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
# Get reserved slot
Source: https://cal.com/docs/api-reference/v2/slots/get-reserved-slot
/api-reference/v2/openapi.json get /v2/slots/reservations/{uid}
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
# Reserve a slot
Source: https://cal.com/docs/api-reference/v2/slots/reserve-a-slot
/api-reference/v2/openapi.json post /v2/slots/reservations
Make a slot not available for others to book for a certain period of time. If you authenticate using oAuth credentials, api key or access token
then you can also specify custom duration for how long the slot should be reserved for (defaults to 5 minutes).
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
# Update a reserved slot
Source: https://cal.com/docs/api-reference/v2/slots/update-a-reserved-slot
/api-reference/v2/openapi.json patch /v2/slots/reservations/{uid}
If you specify a custom reservation duration, you must authenticate using oAuth credentials, api key or access token.
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
# Check Stripe connection
Source: https://cal.com/docs/api-reference/v2/stripe/check-stripe-connection
/api-reference/v2/openapi.json get /v2/stripe/check
# Get Stripe connect URL
Source: https://cal.com/docs/api-reference/v2/stripe/get-stripe-connect-url
/api-reference/v2/openapi.json get /v2/stripe/connect
# Save Stripe credentials
Source: https://cal.com/docs/api-reference/v2/stripe/save-stripe-credentials
/api-reference/v2/openapi.json get /v2/stripe/save
# Add custom team booking fields
Source: https://cal.com/docs/api-reference/v2/team-event-types/add-custom-team-booking-fields
/api-reference/v2/openapi.json post /v2/teams/{teamId}/event-types/{eventTypeId}/booking-fields
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Adds custom booking fields. Default and workflow-added fields cannot be added through this endpoint. Required membership role: `team admin`. PBAC permission: `eventType.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_WRITE` scope is required.
# Create a phone call for a team event type
Source: https://cal.com/docs/api-reference/v2/team-event-types/create-a-phone-call-for-a-team-event-type
/api-reference/v2/openapi.json post /v2/teams/{teamId}/event-types/{eventTypeId}/create-phone-call
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Required membership role: `team admin`. PBAC permission: `eventType.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_WRITE` scope is required.
# Create a team event type
Source: https://cal.com/docs/api-reference/v2/team-event-types/create-a-team-event-type
/api-reference/v2/openapi.json post /v2/teams/{teamId}/event-types
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
If `bookingFields` is omitted, the event type is created with its default booking fields. At least one contact field—`email` or `attendeePhoneNumber`—must remain visible and required. Other default fields, such as additional notes, can be hidden. Provide `bookingFields` to customize the default fields or add custom booking fields. Required membership role: `team admin`. PBAC permission: `eventType.create`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_WRITE` scope is required.
# Delete a custom team booking field
Source: https://cal.com/docs/api-reference/v2/team-event-types/delete-a-custom-team-booking-field
/api-reference/v2/openapi.json delete /v2/teams/{teamId}/event-types/{eventTypeId}/booking-fields/{slug}
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Deletes the custom booking field identified by `slug`. Default fields cannot be deleted, set hidden instead. Required membership role: `team admin`. PBAC permission: `eventType.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_WRITE` scope is required.
# Delete a team event type
Source: https://cal.com/docs/api-reference/v2/team-event-types/delete-a-team-event-type
/api-reference/v2/openapi.json delete /v2/teams/{teamId}/event-types/{eventTypeId}
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Required membership role: `team admin`. PBAC permission: `eventType.delete`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_WRITE` scope is required.
# Get a team event type
Source: https://cal.com/docs/api-reference/v2/team-event-types/get-a-team-event-type
/api-reference/v2/openapi.json get /v2/teams/{teamId}/event-types/{eventTypeId}
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Returns hidden booking fields only to callers who are allowed to view them. Required membership role: `team admin`. PBAC permission: `eventType.readTeamEventTypes`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_READ` scope is required.
# Get team booking fields
Source: https://cal.com/docs/api-reference/v2/team-event-types/get-team-booking-fields
/api-reference/v2/openapi.json get /v2/teams/{teamId}/event-types/{eventTypeId}/booking-fields
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Returns default and custom booking fields, plus any read-only workflow-added fields. Required membership role: `team admin`. PBAC permission: `eventType.readTeamEventTypes`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_READ` scope is required.
# List team event types
Source: https://cal.com/docs/api-reference/v2/team-event-types/list-team-event-types
/api-reference/v2/openapi.json get /v2/teams/{teamId}/event-types
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Hidden event types and hidden booking fields are returned only to team administrators or owners, and parent-organization administrators or owners. Other callers see public event types and visible booking fields. Authentication is optional.
# Replace team booking fields
Source: https://cal.com/docs/api-reference/v2/team-event-types/replace-team-booking-fields
/api-reference/v2/openapi.json put /v2/teams/{teamId}/event-types/{eventTypeId}/booking-fields
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Replaces all booking fields. Omitted custom fields are deleted, while omitted default fields are restored with their default settings. Use `PATCH` to update specific fields. Workflow-added fields are read-only and ignored. Required membership role: `team admin`. PBAC permission: `eventType.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_WRITE` scope is required.
# Update a team event type
Source: https://cal.com/docs/api-reference/v2/team-event-types/update-a-team-event-type
/api-reference/v2/openapi.json patch /v2/teams/{teamId}/event-types/{eventTypeId}
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Omit `bookingFields` to keep the existing booking fields. If provided, the array replaces them, and any omitted default fields are restored with their default settings. Required membership role: `team admin`. PBAC permission: `eventType.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_WRITE` scope is required.
# Update team booking fields
Source: https://cal.com/docs/api-reference/v2/team-event-types/update-team-booking-fields
/api-reference/v2/openapi.json patch /v2/teams/{teamId}/event-types/{eventTypeId}/booking-fields
Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.
Updates the booking fields identified by `slug`. Omitted properties keep their current values. Workflow-added fields are read-only. Required membership role: `team admin`. PBAC permission: `eventType.update`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_WRITE` scope is required.
# Get team bookings
Source: https://cal.com/docs/api-reference/v2/teams-bookings/get-team-bookings
/api-reference/v2/openapi.json get /v2/teams/{teamId}/bookings
If accessed using an OAuth access token, the `TEAM_BOOKING_READ` scope is required.
# Create a webhook for a team event type
Source: https://cal.com/docs/api-reference/v2/teams-event-types-webhooks/create-a-webhook-for-a-team-event-type
/api-reference/v2/openapi.json post /v2/teams/{teamId}/event-types/{eventTypeId}/webhooks
If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_WRITE` scope is required.
# Delete a webhook for a team event type
Source: https://cal.com/docs/api-reference/v2/teams-event-types-webhooks/delete-a-webhook-for-a-team-event-type
/api-reference/v2/openapi.json delete /v2/teams/{teamId}/event-types/{eventTypeId}/webhooks/{webhookId}
If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_WRITE` scope is required.
# Delete all webhooks for a team event type
Source: https://cal.com/docs/api-reference/v2/teams-event-types-webhooks/delete-all-webhooks-for-a-team-event-type
/api-reference/v2/openapi.json delete /v2/teams/{teamId}/event-types/{eventTypeId}/webhooks
If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_WRITE` scope is required.
# Get a webhook for a team event type
Source: https://cal.com/docs/api-reference/v2/teams-event-types-webhooks/get-a-webhook-for-a-team-event-type
/api-reference/v2/openapi.json get /v2/teams/{teamId}/event-types/{eventTypeId}/webhooks/{webhookId}
If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_READ` scope is required.
# Get all webhooks for a team event type
Source: https://cal.com/docs/api-reference/v2/teams-event-types-webhooks/get-all-webhooks-for-a-team-event-type
/api-reference/v2/openapi.json get /v2/teams/{teamId}/event-types/{eventTypeId}/webhooks
If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_READ` scope is required.
# Update a webhook for a team event type
Source: https://cal.com/docs/api-reference/v2/teams-event-types-webhooks/update-a-webhook-for-a-team-event-type
/api-reference/v2/openapi.json patch /v2/teams/{teamId}/event-types/{eventTypeId}/webhooks/{webhookId}
If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_WRITE` scope is required.
# Create team invite link
Source: https://cal.com/docs/api-reference/v2/teams-invite/create-team-invite-link
/api-reference/v2/openapi.json post /v2/teams/{teamId}/invite
If accessed using an OAuth access token, the `TEAM_MEMBERSHIP_WRITE` scope is required.
# Create a membership
Source: https://cal.com/docs/api-reference/v2/teams-memberships/create-a-membership
/api-reference/v2/openapi.json post /v2/teams/{teamId}/memberships
If accessed using an OAuth access token, the `TEAM_MEMBERSHIP_WRITE` scope is required.
# Delete a membership
Source: https://cal.com/docs/api-reference/v2/teams-memberships/delete-a-membership
/api-reference/v2/openapi.json delete /v2/teams/{teamId}/memberships/{membershipId}
Required membership role: `team admin`. PBAC permission: `team.remove`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_MEMBERSHIP_WRITE` scope is required.
# Get a membership
Source: https://cal.com/docs/api-reference/v2/teams-memberships/get-a-membership
/api-reference/v2/openapi.json get /v2/teams/{teamId}/memberships/{membershipId}
If accessed using an OAuth access token, the `TEAM_MEMBERSHIP_READ` scope is required.
# Get all memberships
Source: https://cal.com/docs/api-reference/v2/teams-memberships/get-all-memberships
/api-reference/v2/openapi.json get /v2/teams/{teamId}/memberships
Retrieve team memberships with optional filtering by email addresses. Supports pagination. If accessed using an OAuth access token, the `TEAM_MEMBERSHIP_READ` scope is required.
# Update membership
Source: https://cal.com/docs/api-reference/v2/teams-memberships/update-membership
/api-reference/v2/openapi.json patch /v2/teams/{teamId}/memberships/{membershipId}
If accessed using an OAuth access token, the `TEAM_MEMBERSHIP_WRITE` scope is required.
# Create routing form response and get available slots
Source: https://cal.com/docs/api-reference/v2/teams-routing-forms-responses/create-routing-form-response-and-get-available-slots
/api-reference/v2/openapi.json post /v2/teams/{teamId}/routing-forms/{routingFormId}/responses
Only available for teams that are not part of an organization - for a team within an organization use the organization team endpoint instead. Required membership role: `team member`. PBAC permission: `routingForm.create`. Learn more about API access control at https://cal.com/docs/api-reference/v2/access-control. If accessed using an OAuth access token, the `TEAM_ROUTING_FORM_WRITE` scope is required.
# Get all team member schedules
Source: https://cal.com/docs/api-reference/v2/teams-schedules/get-all-team-member-schedules
/api-reference/v2/openapi.json get /v2/teams/{teamId}/schedules
If accessed using an OAuth access token, the `TEAM_SCHEDULE_READ` scope is required.
# Create an out-of-office entry for a team member
Source: https://cal.com/docs/api-reference/v2/teams-users-ooo/create-an-out-of-office-entry-for-a-team-member
/api-reference/v2/openapi.json post /v2/teams/{teamId}/users/{userId}/ooo
If accessed using an OAuth access token, the `TEAM_SCHEDULE_WRITE` scope is required.
# Delete an out-of-office entry for a team member
Source: https://cal.com/docs/api-reference/v2/teams-users-ooo/delete-an-out-of-office-entry-for-a-team-member
/api-reference/v2/openapi.json delete /v2/teams/{teamId}/users/{userId}/ooo/{oooId}
If accessed using an OAuth access token, the `TEAM_SCHEDULE_WRITE` scope is required.
# Get all out-of-office entries for a team member
Source: https://cal.com/docs/api-reference/v2/teams-users-ooo/get-all-out-of-office-entries-for-a-team-member
/api-reference/v2/openapi.json get /v2/teams/{teamId}/users/{userId}/ooo
If accessed using an OAuth access token, the `TEAM_SCHEDULE_READ` scope is required.
# Update an out-of-office entry for a team member
Source: https://cal.com/docs/api-reference/v2/teams-users-ooo/update-an-out-of-office-entry-for-a-team-member
/api-reference/v2/openapi.json patch /v2/teams/{teamId}/users/{userId}/ooo/{oooId}
If accessed using an OAuth access token, the `TEAM_SCHEDULE_WRITE` scope is required.
# Get list of verified emails of a team
Source: https://cal.com/docs/api-reference/v2/teams-verified-resources/get-list-of-verified-emails-of-a-team
/api-reference/v2/openapi.json get /v2/teams/{teamId}/verified-resources/emails
If accessed using an OAuth access token, the `TEAM_VERIFIED_RESOURCES_READ` scope is required.
# Get list of verified phone numbers of a team
Source: https://cal.com/docs/api-reference/v2/teams-verified-resources/get-list-of-verified-phone-numbers-of-a-team
/api-reference/v2/openapi.json get /v2/teams/{teamId}/verified-resources/phones
If accessed using an OAuth access token, the `TEAM_VERIFIED_RESOURCES_READ` scope is required.
# Get verified email of a team by id
Source: https://cal.com/docs/api-reference/v2/teams-verified-resources/get-verified-email-of-a-team-by-id
/api-reference/v2/openapi.json get /v2/teams/{teamId}/verified-resources/emails/{id}
If accessed using an OAuth access token, the `TEAM_VERIFIED_RESOURCES_READ` scope is required.
# Get verified phone number of a team by id
Source: https://cal.com/docs/api-reference/v2/teams-verified-resources/get-verified-phone-number-of-a-team-by-id
/api-reference/v2/openapi.json get /v2/teams/{teamId}/verified-resources/phones/{id}
If accessed using an OAuth access token, the `TEAM_VERIFIED_RESOURCES_READ` scope is required.
# Request email verification code
Source: https://cal.com/docs/api-reference/v2/teams-verified-resources/request-email-verification-code
/api-reference/v2/openapi.json post /v2/teams/{teamId}/verified-resources/emails/verification-code/request
Sends a verification code to the Email. If accessed using an OAuth access token, the `TEAM_VERIFIED_RESOURCES_WRITE` scope is required.
# Request phone number verification code
Source: https://cal.com/docs/api-reference/v2/teams-verified-resources/request-phone-number-verification-code
/api-reference/v2/openapi.json post /v2/teams/{teamId}/verified-resources/phones/verification-code/request
Sends a verification code to the phone number. If accessed using an OAuth access token, the `TEAM_VERIFIED_RESOURCES_WRITE` scope is required.
# Verify a phone number for an org team
Source: https://cal.com/docs/api-reference/v2/teams-verified-resources/verify-a-phone-number-for-an-org-team
/api-reference/v2/openapi.json post /v2/teams/{teamId}/verified-resources/phones/verification-code/verify
Use code to verify a phone number. If accessed using an OAuth access token, the `TEAM_VERIFIED_RESOURCES_WRITE` scope is required.
# Verify an email for a team
Source: https://cal.com/docs/api-reference/v2/teams-verified-resources/verify-an-email-for-a-team
/api-reference/v2/openapi.json post /v2/teams/{teamId}/verified-resources/emails/verification-code/verify
Use code to verify an email. If accessed using an OAuth access token, the `TEAM_VERIFIED_RESOURCES_WRITE` scope is required.
# Create a team
Source: https://cal.com/docs/api-reference/v2/teams/create-a-team
/api-reference/v2/openapi.json post /v2/teams
If accessed using an OAuth access token, the `TEAM_PROFILE_WRITE` scope is required.
# Delete a team
Source: https://cal.com/docs/api-reference/v2/teams/delete-a-team
/api-reference/v2/openapi.json delete /v2/teams/{teamId}
If accessed using an OAuth access token, the `TEAM_PROFILE_WRITE` scope is required.
# Get a team
Source: https://cal.com/docs/api-reference/v2/teams/get-a-team
/api-reference/v2/openapi.json get /v2/teams/{teamId}
If accessed using an OAuth access token, the `TEAM_PROFILE_READ` scope is required.
# Get teams
Source: https://cal.com/docs/api-reference/v2/teams/get-teams
/api-reference/v2/openapi.json get /v2/teams
If accessed using an OAuth access token, the `TEAM_PROFILE_READ` scope is required.
# Update a team
Source: https://cal.com/docs/api-reference/v2/teams/update-a-team
/api-reference/v2/openapi.json patch /v2/teams/{teamId}
If accessed using an OAuth access token, the `TEAM_PROFILE_WRITE` scope is required.
# User booking limits
Source: https://cal.com/docs/api-reference/v2/user-booking-limits
Cap the total number of bookings a user can accept across all event types per day, week, month, or year.
User booking limits let you restrict how many bookings a user can accept across **all** their event types — both personal and team — within a given time window. This is different from [event-type booking limits](#user-limits-vs-event-type-limits), which only apply to a single event type.
The `/v2/me/booking-limits` endpoints are available to organization members only. Non-org accounts receive a `403` response.
## How it works
When a user has booking limits configured, Cal.com checks the total number of accepted bookings for that user before allowing a new one. If the user has already reached their limit for the current period, the time slots are marked as unavailable and new bookings are rejected.
Limits can be set for any combination of these intervals:
| Interval | Description |
| ---------- | ----------------------------------- |
| `perDay` | Maximum bookings per calendar day |
| `perWeek` | Maximum bookings per calendar week |
| `perMonth` | Maximum bookings per calendar month |
| `perYear` | Maximum bookings per calendar year |
You can set one or more intervals simultaneously. For example, you could allow up to 5 bookings per day but no more than 20 per week.
When multiple intervals are set, the stricter limit at any given moment takes precedence. A daily limit of 3 and a weekly limit of 10 means a user can never exceed 3 bookings on a single day, even if they haven't reached 10 for the week.
## Setting limits in the UI
You can configure user booking limits in **Settings > My Account > General**. Toggle the booking limits option, set your desired limits for each interval, and click **Update** to save.
## Setting limits via the API
The dedicated `/v2/me/booking-limits` endpoints let you read, update, and clear the authenticated user's global limits without round-tripping the full `/v2/me` payload. These endpoints are only available to organization members and return `403` for non-org accounts. OAuth clients need the `PROFILE_READ` scope to read and `PROFILE_WRITE` scope to update or clear.
### Read current limits
Use [`GET /v2/me/booking-limits`](https://cal.com/docs/api-reference/v2/me/get-my-booking-limits) to fetch the authenticated user's limits. Unset bounds are returned as `null`.
```bash theme={null}
curl https://api.cal.com/v2/me/booking-limits \
-H "Authorization: Bearer YOUR_API_KEY"
```
```json theme={null}
{
"status": "success",
"data": {
"perDay": 5,
"perWeek": 20,
"perMonth": null,
"perYear": null
}
}
```
User booking limits are also included in the `GET /v2/me` response under the `bookingLimits` field.
### Update limits
Use [`PATCH /v2/me/booking-limits`](https://cal.com/docs/api-reference/v2/me/update-my-booking-limits) to change one or more intervals. The endpoint has merge semantics: omit a field to leave it untouched, or set it to `null` to remove only that limit.
```bash theme={null}
curl -X PATCH https://api.cal.com/v2/me/booking-limits \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"perDay": 5,
"perWeek": 20
}'
```
To clear a single interval without affecting the others, pass `null` for just that field:
```bash theme={null}
curl -X PATCH https://api.cal.com/v2/me/booking-limits \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"perDay": null
}'
```
### Clear all limits
Use [`DELETE /v2/me/booking-limits`](https://cal.com/docs/api-reference/v2/me/clear-my-booking-limits) to remove every limit in a single call. The endpoint returns `204 No Content` on success.
```bash theme={null}
curl -X DELETE https://api.cal.com/v2/me/booking-limits \
-H "Authorization: Bearer YOUR_API_KEY"
```
## User limits vs event-type limits
Cal.com supports two levels of booking limits:
| Level | Scope | Where to configure |
| ----------------------------- | ----------------------------------------------- | -------------------------------- |
| **User booking limits** | All bookings for a user across every event type | Settings > My Account > General |
| **Event-type booking limits** | Bookings for a single event type only | Event type settings > Limits tab |
Both levels are enforced independently. If a user has a daily limit of 5 and an event type has a daily limit of 3, the event type can only receive 3 bookings per day — but the user can still accept 2 more bookings on other event types before hitting their personal cap.
## Effect on availability
When a user reaches a booking limit for a given period, all time slots within that period are marked as busy. This means:
* Slots no longer appear as available on booking pages.
* Attempts to book during that period return an error.
* The limit resets at the start of the next period (next day, week, month, or year) based on the user's timezone.
# Migrating from API v1 to v2
Source: https://cal.com/docs/api-reference/v2/v1-v2-differences
Complete guide for migrating your Cal.com integration from v1 to v2
**API v1 was shut down on April 8, 2026.** All v1 endpoints have been removed. Use this guide to migrate any remaining v1 integrations to API v2.
## Why migrate to v2?
The v2 API includes numerous enhancements and new features that are not available in v1:
* **Performance improvements**: Optimized for better performance and scalability
* **User-friendly response objects**: Improved response structure for better developer experience
* **Enhanced security**: Improved security measures to protect your data
* **New features**: Access to new Cal.com features only available in v2
* **Better error handling**: More descriptive error messages and status codes
## Authentication changes
In v1, you authenticated using an API key passed as a query parameter:
```bash theme={null}
curl https://api.cal.com/v1/bookings?apiKey=cal_test_xxxxxx
```
In v2, you authenticate using an API key in the `Authorization` header:
```bash theme={null}
curl https://api.cal.com/v2/bookings \
-H "Authorization: Bearer cal_test_xxxxxx" \
-H "cal-api-version: 2024-08-13"
```
## Endpoint-by-endpoint migration guide
### Bookings
#### Create a booking
**v1 endpoint:**
```
POST /v1/bookings
```
**v1 request body:**
```json theme={null}
{
"eventTypeId": 123,
"start": "2023-05-24T13:00:00.000Z",
"end": "2023-05-24T13:30:00.000Z",
"responses": {
"name": "John Doe",
"email": "[email protected]",
"location": {
"value": "userPhone",
"optionValue": ""
}
},
"timeZone": "Europe/London",
"language": "en",
"metadata": {}
}
```
**v2 endpoint:**
```
POST /v2/bookings
```
**v2 request body:**
```json theme={null}
{
"eventTypeId": 123,
"start": "2024-08-13T09:00:00Z",
"attendee": {
"name": "John Doe",
"email": "[email protected]",
"timeZone": "America/New_York",
"language": "en"
},
"location": {
"type": "phone"
},
"metadata": {}
}
```
**Key differences:**
* `responses` object replaced with `attendee` object
* `end` time is no longer required (calculated from event type duration)
* `location` structure changed from `{value, optionValue}` to `{type}`
* Must include `cal-api-version: 2024-08-13` header
* Response structure is more detailed with `status` and `data` wrapper
**v1 response:**
```json theme={null}
{
"booking": {
"id": 91,
"uid": "bFJeNb2uX8ANpT3JL5EfXw",
"startTime": "2023-05-25T09:30:00.000Z",
"endTime": "2023-05-25T10:30:00.000Z",
"attendees": [...],
"status": "ACCEPTED"
}
}
```
**v2 response:**
```json theme={null}
{
"status": "success",
"data": {
"id": 123,
"uid": "booking_uid_123",
"start": "2024-08-13T15:30:00Z",
"end": "2024-08-13T16:30:00Z",
"duration": 60,
"status": "accepted",
"hosts": [...],
"attendees": [...],
"eventType": {
"id": 1,
"slug": "some-event"
}
}
}
```
#### Get all bookings
**v1:** Not available as a dedicated endpoint
**v2 endpoint:**
```
GET /v2/bookings
```
**v2 query parameters:**
* `status`: Filter by booking status (accepted, pending, cancelled, rejected)
* `attendeeEmail`: Filter by attendee email
* `eventTypeId`: Filter by event type
* `afterStart`, `beforeEnd`: Filter by date range
* `take`, `skip`: Pagination
* `sortStart`, `sortEnd`, `sortCreated`: Sorting options
**v2 response includes pagination:**
```json theme={null}
{
"status": "success",
"data": [...],
"pagination": {
"totalItems": 123,
"currentPage": 2,
"totalPages": 13,
"hasNextPage": true
}
}
```
#### Cancel a booking
**v1 endpoint:**
```
DELETE /v1/bookings?id={id}&allRemainingBookings=false&cancellationReason=reason
```
**v2 endpoint:**
```
POST /v2/bookings/{uid}/cancel
```
**v2 request body:**
```json theme={null}
{
"cancellationReason": "User requested cancellation"
}
```
**Key differences:**
* Changed from DELETE to POST method
* Uses booking `uid` in path instead of `id` query parameter
* Cancellation reason in request body instead of query parameter
#### Reschedule a booking
**v1:** Required creating a new booking with `rescheduleUid`
**v2 endpoint:**
```
POST /v2/bookings/{uid}/reschedule
```
**v2 request body:**
```json theme={null}
{
"start": "2024-08-14T10:00:00Z",
"reschedulingReason": "Conflict with another meeting"
}
```
**Key differences:**
* Dedicated reschedule endpoint in v2
* Simpler process - just provide new start time
* Automatically handles the relationship between old and new bookings
### Event types
#### Get all event types
**v1 endpoint:**
```
GET /v1/event-types
```
**v2 endpoint:**
```
GET /v2/event-types
```
**Key differences:**
* v2 response includes more detailed information about each event type
* v2 includes pagination support
* v2 response wrapped in `{status, data}` structure
#### Create an event type
**v1 endpoint:**
```
POST /v1/event-types
```
**v1 request body:**
```json theme={null}
{
"title": "30 Min Meeting",
"slug": "30min",
"length": 30,
"locations": [{"type": "integrations:zoom"}]
}
```
**v2 endpoint:**
```
POST /v2/event-types
```
**v2 request body:**
```json theme={null}
{
"title": "30 Min Meeting",
"slug": "30min",
"lengthInMinutes": 30,
"locations": [{"type": "zoom"}]
}
```
**Key differences:**
* `length` renamed to `lengthInMinutes` for clarity
* Location types simplified (no `integrations:` prefix)
* More configuration options available in v2
#### Update an event type
**v1 endpoint:**
```
PATCH /v1/event-types/{id}
```
**v2 endpoint:**
```
PATCH /v2/event-types/{id}
```
**Key differences:**
* Same HTTP method and path structure
* Request/response body structure differences match create endpoint
* v2 provides more granular control over event type settings
* v2 treats the `teamId` and `parentId` of an event type as immutable. Update requests that attempt to change either field are rejected. Delete and recreate the event type to move it to another team or change its managed parent.
### Schedules
#### Get all schedules
**v1 endpoint:**
```
GET /v1/schedules
```
**v2 endpoint:**
```
GET /v2/schedules
```
**Key differences:**
* v2 includes default schedule indicator
* v2 response includes more detailed availability information
* v2 supports filtering and pagination
#### Create a schedule
**v1 endpoint:**
```
POST /v1/schedules
```
**v1 request body:**
```json theme={null}
{
"name": "Working Hours",
"timeZone": "America/New_York",
"availability": [...]
}
```
**v2 endpoint:**
```
POST /v2/schedules
```
**v2 request body:**
```json theme={null}
{
"name": "Working Hours",
"timeZone": "America/New_York",
"isDefault": false,
"schedule": [...]
}
```
**Key differences:**
* `availability` renamed to `schedule` in v2
* `isDefault` flag added to set default schedule
* More flexible schedule configuration options
### Webhooks
#### Get all webhooks
**v1 endpoint:**
```
GET /v1/webhooks
```
**v2 endpoint:**
```
GET /v2/webhooks
```
#### Create a webhook
**v1 endpoint:**
```
POST /v1/webhooks
```
**v1 request body:**
```json theme={null}
{
"subscriberUrl": "https://example.com/webhook",
"eventTriggers": ["BOOKING_CREATED"],
"active": true
}
```
**v2 endpoint:**
```
POST /v2/webhooks
```
**v2 request body:**
```json theme={null}
{
"payloadTemplate": null,
"triggers": ["BOOKING_CREATED"],
"subscriberUrl": "https://example.com/webhook",
"active": true
}
```
**Key differences:**
* `eventTriggers` renamed to `triggers`
* Added `payloadTemplate` for custom webhook payloads
* More webhook event types available in v2
### Slots
#### Get available slots
**v1 endpoint:**
```
GET /v1/slots/available?eventTypeId=123&startTime=2024-01-01&endTime=2024-01-31
```
**v2 endpoint:**
```
GET /v2/slots/available?eventTypeId=123&startTime=2024-01-01T00:00:00Z&endTime=2024-01-31T23:59:59Z
```
**Key differences:**
* v2 requires full ISO 8601 timestamps
* v2 response includes more metadata about slot availability
* v2 supports additional filtering options (username, teamSlug, etc.)
### Teams
#### Get all teams
**v1 endpoint:**
```
GET /v1/teams
```
**v2 endpoint:**
```
GET /v2/teams
```
**Key differences:**
* v2 includes organization context if team is part of an org
* v2 response includes more team metadata
* v2 supports pagination
### Users
#### Get user profile
**v1 endpoint:**
```
GET /v1/users/{id}
```
**v2 endpoint:**
```
GET /v2/me
```
**Key differences:**
* v2 uses `/me` endpoint for current user
* v2 returns more detailed profile information
* v2 includes organization and team memberships
## Response structure changes
### v1 response format
```json theme={null}
{
"booking": {...}
}
```
### v2 response format
```json theme={null}
{
"status": "success",
"data": {...}
}
```
All v2 responses follow this consistent structure with:
* `status`: Either "success" or "error"
* `data`: The actual response data
* `error`: Error details (only present when status is "error")
## Error handling
### v1 errors
```json theme={null}
{
"message": "Event type not found"
}
```
### v2 errors
```json theme={null}
{
"status": "error",
"error": {
"code": "NOT_FOUND",
"message": "Event type not found"
}
}
```
v2 provides more structured error responses with error codes for better error handling.
## Migration checklist
* [ ] Update authentication to use `Authorization` header instead of query parameter
* [ ] Add `cal-api-version: 2024-08-13` header to all requests
* [ ] Update base URL from `/v1/` to `/v2/`
* [ ] Update request body structures (especially `responses` → `attendee` for bookings)
* [ ] Update response parsing to handle `{status, data}` wrapper
* [ ] Update location object structures
* [ ] Update field names (`length` → `lengthInMinutes`, `eventTriggers` → `triggers`, etc.)
* [ ] Implement pagination handling for list endpoints
* [ ] Update error handling to parse new error structure
* [ ] Test all endpoints in your integration
* [ ] Update any stored booking/event type IDs if needed
## Need help?
If you have questions or need assistance with migrating to v2, please contact our support team.
# Get list of verified emails
Source: https://cal.com/docs/api-reference/v2/verified-resources/get-list-of-verified-emails
/api-reference/v2/openapi.json get /v2/verified-resources/emails
If accessed using an OAuth access token, the `VERIFIED_RESOURCES_READ` scope is required.
# Get list of verified phone numbers
Source: https://cal.com/docs/api-reference/v2/verified-resources/get-list-of-verified-phone-numbers
/api-reference/v2/openapi.json get /v2/verified-resources/phones
If accessed using an OAuth access token, the `VERIFIED_RESOURCES_READ` scope is required.
# Get verified email by id
Source: https://cal.com/docs/api-reference/v2/verified-resources/get-verified-email-by-id
/api-reference/v2/openapi.json get /v2/verified-resources/emails/{id}
If accessed using an OAuth access token, the `VERIFIED_RESOURCES_READ` scope is required.
# Get verified phone number by id
Source: https://cal.com/docs/api-reference/v2/verified-resources/get-verified-phone-number-by-id
/api-reference/v2/openapi.json get /v2/verified-resources/phones/{id}
If accessed using an OAuth access token, the `VERIFIED_RESOURCES_READ` scope is required.
# Request email verification code
Source: https://cal.com/docs/api-reference/v2/verified-resources/request-email-verification-code
/api-reference/v2/openapi.json post /v2/verified-resources/emails/verification-code/request
Sends a verification code to the email. If accessed using an OAuth access token, the `VERIFIED_RESOURCES_WRITE` scope is required.
# Request phone number verification code
Source: https://cal.com/docs/api-reference/v2/verified-resources/request-phone-number-verification-code
/api-reference/v2/openapi.json post /v2/verified-resources/phones/verification-code/request
Sends a verification code to the phone number. If accessed using an OAuth access token, the `VERIFIED_RESOURCES_WRITE` scope is required.
# Verify a phone number
Source: https://cal.com/docs/api-reference/v2/verified-resources/verify-a-phone-number
/api-reference/v2/openapi.json post /v2/verified-resources/phones/verification-code/verify
Use code to verify a phone number. If accessed using an OAuth access token, the `VERIFIED_RESOURCES_WRITE` scope is required.
# Verify an email
Source: https://cal.com/docs/api-reference/v2/verified-resources/verify-an-email
/api-reference/v2/openapi.json post /v2/verified-resources/emails/verification-code/verify
Use code to verify an email. If accessed using an OAuth access token, the `VERIFIED_RESOURCES_WRITE` scope is required.
# Create a webhook
Source: https://cal.com/docs/api-reference/v2/webhooks/create-a-webhook
/api-reference/v2/openapi.json post /v2/webhooks
If accessed using an OAuth access token, the `WEBHOOK_WRITE` scope is required.
# Delete a webhook
Source: https://cal.com/docs/api-reference/v2/webhooks/delete-a-webhook
/api-reference/v2/openapi.json delete /v2/webhooks/{webhookId}
If accessed using an OAuth access token, the `WEBHOOK_WRITE` scope is required.
# Get a webhook
Source: https://cal.com/docs/api-reference/v2/webhooks/get-a-webhook
/api-reference/v2/openapi.json get /v2/webhooks/{webhookId}
If accessed using an OAuth access token, the `WEBHOOK_READ` scope is required.
# Get all webhooks
Source: https://cal.com/docs/api-reference/v2/webhooks/get-all-webhooks
/api-reference/v2/openapi.json get /v2/webhooks
Gets a paginated list of webhooks for the authenticated user. If accessed using an OAuth access token, the `WEBHOOK_READ` scope is required.
# Update a webhook
Source: https://cal.com/docs/api-reference/v2/webhooks/update-a-webhook
/api-reference/v2/openapi.json patch /v2/webhooks/{webhookId}
If accessed using an OAuth access token, the `WEBHOOK_WRITE` scope is required.
# Create a workflow
Source: https://cal.com/docs/api-reference/v2/workflows/create-a-workflow
/api-reference/v2/openapi.json post /v2/workflows
Create an event-type workflow owned by the authenticated user. Not available to third-party OAuth access tokens.
# Delete your workflow
Source: https://cal.com/docs/api-reference/v2/workflows/delete-your-workflow
/api-reference/v2/openapi.json delete /v2/workflows/{workflowId}
Delete an event-type workflow owned by the authenticated user. Not available to third-party OAuth access tokens.
# Get your workflow
Source: https://cal.com/docs/api-reference/v2/workflows/get-your-workflow
/api-reference/v2/openapi.json get /v2/workflows/{workflowId}
Get an event-type workflow owned by the authenticated user. Routing form workflows are not addressable through this endpoint. Not available to third-party OAuth access tokens.
# Get your workflows
Source: https://cal.com/docs/api-reference/v2/workflows/get-your-workflows
/api-reference/v2/openapi.json get /v2/workflows
Get the event-type workflows owned by the authenticated user. Routing form workflows are not returned. Not available to third-party OAuth access tokens.
# Update your workflow
Source: https://cal.com/docs/api-reference/v2/workflows/update-your-workflow
/api-reference/v2/openapi.json patch /v2/workflows/{workflowId}
Update an event-type workflow owned by the authenticated user. Not available to third-party OAuth access tokens.
# Apple calendar connect
Source: https://cal.com/docs/atoms/apple-calendar-connect
The Apple calendar connect button is used to sync user's apple calendar - whenever an event is created in cal.com it will show up in the apple calendar.
Below code snippet can be used to render the Apple calendar connect button
```js theme={null}
import { Connect } from "@calcom/atoms";
export default function ConnectCalendar() {
return (
<>
>
);
}
```
For a demonstration of the Apple calendar connect integration, please refer to the video below.
The apple calendar atom works a bit differently than outlook and google calendar. We don’t get redirected to an OAuth consent page, instead a modal appears which prompts us to create an app specific password to use with Cal.com
Similar to Outlook and Google calendar, the Apple calendar connect supports integration for both single and multiple users. The above video demonstration showcases the integration for a single user. To enable integration for multiple users, simply pass the prop `isMultiCalendar` as `true`.
Below code snippet can be used to render the Apple calendar connect button for multiple users
```js theme={null}
import { Connect } from "@calcom/atoms";
export default function ConnectCalendar() {
return (
<>
>
);
}
```
For a demonstration of the Apple calendar connect integration for multiple users, please refer to the video below.
We offer all kinds of customizations to the Outlook calendar connect via props. Below is a list of props that can be passed to the Google calendar connect.
| Name | Required | Description |
| :-------------------- | :------- | :------------------------------------------------------------------------ |
| className | No | To pass in custom classnames from outside for styling the atom |
| label | No | The label for the connect button |
| alreadyConnectedLabel | No | Label to display when atom is in already connected state |
| loadingLabel | No | Label to display when atom is in loading state |
| onCheckError | No | A callback function to handle errors when checking the connection status |
| initialData | No | Initial data to be passed |
| isMultiCalendar | No | Specifies if the button supports integration for multiple users |
| tooltip | No | In case user wants to pass external tooltip component |
| tooltipSide | No | Specifies what direction the tooltip appears |
| isClickable | No | Boolean to disable button or not |
| onSuccess | No | A callback function to handle success when checking the connection status |
If you try to put your Apple Id password in the password field, it will throw an error. It only accepts an
app specific password generated from [https://appleid.apple.com/account/manage](https://appleid.apple.com/account/manage).
# Availability settings
Source: https://cal.com/docs/atoms/availability-settings
The availability settings atom enables a user to set their available time slots. This atom allows users to define specific time slots when they are available for meetings or events, helping them manage their schedules effectively and avoid double bookings.
First, for the AvailabilitySettings toggle animation to work set the reading direction on the `` element:
```html theme={null}
...
```
Below code snippet can be used to render the availability settings atom
```js theme={null}
import { AvailabilitySettings } from "@calcom/atoms";
export default function Availability() {
return (
<>
{
console.log("Updated schedule successfully");
}}
onDeleteSuccess={() => {
console.log("Deleted schedule successfully");
}}
onFormStateChange={(formState) => {
console.log("Form state updated");
// Access form data: formState.name, formState.schedule, formState.timeZone, etc.
}}
/>
>
)
}
```
For a demonstration of the availability settings atom, please refer to the video below.
If a user wishes to add further adjustments to their availability for special occasions or events, they can use the date overrides feature. Date overrides enables users to pick any date that they're currently available and set specific hours for availability on that day or mark themselves entirely unavailable. Once that day is passed, the date override is automatically deleted.
Below code snippet can be used to render date overrides into the availability settings atom
```js theme={null}
import { AvailabilitySettings } from "@calcom/atoms";
export default function Availability() {
return (
<>
{
console.log("Updated schedule successfully");
}}
onDeleteSuccess={() => {
console.log("Deleted schedule successfully");
}}
onFormStateChange={(formState) => {
console.log("Form state changed:", formState);
// Access form data including dateOverrides: formState.dateOverrides
}}
/>
>
)
}
```
For a demonstration of the availability settings atom with date overrides, please refer to the video below.
We offer all kinds of customizations to the availability settings atom via props and customClassNames.
Below is a list of props that can be passed to the availability settings atom.
| Name | Required | Description |
| :--------------------- | :------- | :--------------------------------------------------------------------------------------------------------------- |
| id | No | The ID of the schedule which fetches a user's availability |
| labels | No | Helpful if you want to pass in custom labels for i18n |
| customClassNames | No | To pass in custom classnames from outside for styling the atom |
| onUpdateSuccess | No | A callback function to handle updating user availability successfully |
| onBeforeUpdate | No | Validates schedule before it is sent to the server; if true, the schedule is sent, else it is not |
| onUpdateError | No | A callback function that gets triggered when the user availability fails to update |
| onDeleteSuccess | No | A callback function that gets triggered when the user availability is deleted successfully |
| onDeleteError | No | A callback function that gets triggered when the user availability fails to delete |
| onFormStateChange | No | A callback function that gets triggered whenever the form state changes, providing real-time access to form data |
| enableOverrides | No | Allows user to enable or disable date overrides display in the atom; defaults to disabled |
| disableEditableHeading | No | Prevents users from editing the heading |
| allowDelete | No | When set to false, this prop hides the delete button |
| allowSetToDefault | No | When set to false, this prop hides the set to default toggle |
| disableToasts | No | Allows users to enable or disable toast notifications, with the default setting being disabled. |
Along with the props, Availability settings atom accepts custom styles via the **customClassNames** prop. Below is a list of props that fall under this **customClassNames** prop.
| Name | Description |
| :----------------------- | :------------------------------------------------------------------------------------ |
| containerClassName | Adds styling to the whole availability settings component |
| ctaClassName | Adds stylings only to certain call-to-action buttons |
| editableHeadingClassName | Editable heading or title can be styled |
| formClassName | Form which contains the days and toggles |
| timezoneSelectClassName | Adds styling to the timezone select component |
| subtitlesClassName | Styles the subtitle |
| scheduleClassNames | An object for granular styling of schedule components (see detailed table below) |
| dateOverrideClassNames | An object for granular styling of date override components (see detailed table below) |
### scheduleClassNames Object Structure
The `scheduleClassNames` prop accepts an object with the following structure for granular styling of schedule components:
| Property Path | Description |
| :------------------------ | :----------------------------------------------------------------------------------------- |
| `schedule` | Styles the entire schedule component |
| `scheduleContainer` | Styles the schedule container |
| `scheduleDay` | Adds styling to just the day of a particular schedule |
| `dayRanges` | Adds styling to day ranges |
| `timeRangeField` | Styles the time range input fields |
| `labelAndSwitchContainer` | Adds styling to label and switches |
| `timePicker` | An object for granular styling of time picker dropdown components (see nested table below) |
#### timePicker Object Structure (nested within scheduleClassNames)
The `timePicker` prop accepts an object with the following structure for granular styling of time picker dropdown components. This applies to the time selection dropdowns (e.g., \[ 9:00 ]) that users can click to open a dropdown with available times or click to manually enter a time:
| Property Path | Description |
| :--------------- | :------------------------------------------------- |
| `container` | Styles the main time picker container |
| `valueContainer` | Styles the container that holds the selected value |
| `value` | Styles the displayed selected time value |
| `input` | Styles the input field for manual time entry |
| `dropdown` | Styles the dropdown menu with time options |
### dateOverrideClassNames Object Structure
The `dateOverrideClassNames` prop accepts an object with the following structure for granular styling of date override components:
| Property Path | Description |
| :------------ | :------------------------------------- |
| `container` | Styles the main container |
| `title` | Styles the title |
| `description` | Styles the description |
| `button` | Styles the button to add date override |
Please ensure all custom classnames are valid [Tailwind CSS](https://tailwindcss.com/) classnames.
## Programmatic Form Validation and Submission
The AvailabilitySettings component supports programmatic form validation and submission through a ref-based API. This allows you to validate availability data and submit forms programmatically without user interaction.
```js theme={null}
import { useRef } from 'react';
import { AvailabilitySettings } from "@calcom/atoms";
import type { AvailabilitySettingsFormRef } from "@calcom/atoms";
export function AvailabilityWithValidation() {
const availabilityRef = useRef(null);
const handleValidate = async () => {
const result = await availabilityRef.current?.validateForm();
if (result?.isValid) {
console.log("Availability form is valid");
} else {
console.log("Validation errors:", result?.errors);
}
};
const handleSubmit = () => {
availabilityRef.current?.handleFormSubmit({
onSuccess: () => {
// Additional success handling logic here
console.log('Availability updated successfully');
},
onError: (error) => {
// Additional error handling logic here
console.error('Error updating availability:', error);
}
});
};
return (
<>
{
console.log("Updated schedule successfully");
}}
onFormStateChange={(formState) => {
console.log("Form state changed:", formState);
}}
/>
>
);
}
```
### Ref Methods
| Method | Description |
| :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| validateForm | Validates the current availability form state and returns a promise with validation results. |
| handleFormSubmit | Programmatically submits the availability form, triggering the same validation and submission flow as clicking the save button. Unlike `validateForm`, this method will check required fields and prevent submission unless all required fields are set |
### Callbacks
The `handleFormSubmit` method accepts an optional callbacks object with the following properties:
```typescript theme={null}
type AvailabilitySettingsFormCallbacks = {
onSuccess?: () => void;
onError?: (error: Error) => void;
};
```
* **onSuccess**: Called when the form submission is successful. This allows you to execute additional logic after a successful update.
* **onError**: Called when an error occurs during form submission. The error parameter contains details about what went wrong, allowing you to handle specific error cases or display custom error messages.
The `validateForm` method returns an `AvailabilityFormValidationResult` object with:
* `isValid`: Boolean indicating if the form passed validation
* `errors`: Object containing any validation errors found
**Note:** If a required field is not filled in, the `validateForm` method will not return any error. The validation focuses on the format and consistency of provided data rather than enforcing required field completion.
# Booker
Source: https://cal.com/docs/atoms/booker
The booker atom is a dynamic component that facilitates the booking process for users. It allows individuals to easily select available time slots and confirm their participation in various events, streamlining the scheduling experience. It is one of the most important atoms and a critical piece of our scheduling system.
## Basic usage
Below code snippet can be used to render the booker atom:
```js theme={null}
import { Booker } from "@calcom/atoms";
export default function Booker( props : BookerProps ) {
return (
<>
{
console.log("booking created successfully");
}}
/>
>
)
}
```
## Team events
For team events, you must set `isTeamEvent` to `true` and provide a `teamId`:
```js theme={null}
import { Booker } from "@calcom/atoms";
export default function TeamBooker( props : BookerProps ) {
return (
{
console.log("Team booking created:", data);
}}
/>
)
}
```
## Dynamic bookings
Dynamic bookings allow multiple users to be booked simultaneously. Pass multiple usernames as an array or a plus-separated string:
```js theme={null}
import { Booker } from "@calcom/atoms";
export default function DynamicBooker() {
return (
{
console.log("Dynamic booking created:", data);
}}
/>
)
}
```
For a demonstration of the booker atom, please refer to the video below.
It is also possible to change the booker layout into a week or column view, you just need to pass in the view prop the layout you want to use. The layouts are as follows: **MONTH\_VIEW,** **WEEK\_VIEW** and **COLUMN\_VIEW**. Both the week and column layouts come with an Overlay Calendar feature, which allows users to overlay multiple calendars on top of their primary calendar.
Below code snippet can be used to render the booker atom with week view
```js theme={null}
import { Booker } from "@calcom/atoms";
export default function Booker( props : BookerProps ) {
return (
<>
{
console.log("booking created successfully");
}}
/>
>
)
}
```
For a demonstration of the booker atom along with calendar overlay, please refer to the video below.
## Advanced usage patterns
### Prefilling booking form data
You can prefill booking form fields using the `defaultFormValues` prop:
```js theme={null}
```
### Rescheduling bookings
To enable rescheduling, pass the `rescheduleUid` or `bookingUid`:
```js theme={null}
{
console.log("Booking rescheduled:", data);
}}
/>
```
### Custom start time
Control the first available date shown to users with the `startTime` prop:
```js theme={null}
```
### Handling booking state changes
Monitor the booker's internal state with `onBookerStateChange`:
```js theme={null}
{
console.log("Current state:", state.state); // "loading" | "selecting_date" | "selecting_time" | "booking"
console.log("Selected date:", state.selectedDate);
console.log("Selected timeslot:", state.selectedTimeslot);
console.log("Form values:", state.formValues);
}}
/>
```
### Custom metadata
Pass custom metadata to track booking sources or additional context:
```js theme={null}
```
### Timezone restrictions
Limit available timezones for the booker:
```js theme={null}
```
### Setting the default timezone
By default the booker remembers the last timezone the user selected (persisted in local storage). Pass the `timezone` prop to control which timezone the booker opens with instead. It takes precedence over the persisted value, and the user can still change it via the timezone selector.
```js theme={null}
```
### Handling slot reservation
Monitor slot reservation events:
```js theme={null}
{
console.log("Slot reserved:", data);
}}
onReserveSlotError={(error) => {
console.error("Failed to reserve slot:", error);
}}
onDeleteSlotSuccess={() => {
console.log("Slot released");
}}
onDeleteSlotError={(error) => {
console.error("Failed to release slot:", error);
}}
/>
```
### Custom location URL
Override the default meeting link with a custom URL:
```js theme={null}
```
### Instant meetings
Enable instant meeting mode:
```js theme={null}
{
console.log("Instant meeting created:", data);
}}
/>
```
### Dry run mode
Test booking flows without creating actual bookings:
```js theme={null}
{
console.log("Dry run completed successfully");
}}
/>
```
### Handling timeslot loading
Execute logic when available timeslots are loaded:
```js theme={null}
{
console.log("Available slots:", slots);
// slots is a Record where keys are dates
}}
/>
```
### CRM integration
Integrate with CRM systems for routing and tracking:
```js theme={null}
```
### Routing form integration
Pass routing form parameters for advanced routing:
```js theme={null}
```
### Controlling URL parameters
By default, the booker doesn't update URL parameters in platform mode. Enable this behavior:
```js theme={null}
```
### Disabling the confirm button
Conditionally disable the booking confirmation button:
```js theme={null}
const [isProcessing, setIsProcessing] = useState(false);
```
### Hiding event metadata
Hide the left sidebar containing event details:
```js theme={null}
```
When event metadata is hidden, the timezone selector that lives inside the sidebar is also hidden. To keep the timezone selector visible above the booker, pass `showTimezoneWhenEventDetailsHidden`:
```js theme={null}
```
If the event type has a locked timezone, that timezone is applied automatically and the selector is omitted.
### Round robin configuration
For round robin team events, hide organization and team information:
```js theme={null}
```
### Handling calendar failures silently
Display slots even when third-party calendar credentials are invalid:
```js theme={null}
```
When `silentlyHandleCalendarFailures` is enabled, the booker may show stale availability if calendar credentials are expired or invalid.
### Custom event meta children
Inject custom React components into the event metadata section:
```js theme={null}
Custom information here
}
/>
```
### Complete example with all callbacks
```js theme={null}
import { Booker } from "@calcom/atoms";
import { useState } from "react";
export default function CompleteBookerExample() {
const [bookingData, setBookingData] = useState(null);
return (
{
console.log("Booking created:", data);
setBookingData(data);
}}
onCreateBookingError={(error) => {
console.error("Booking failed:", error);
}}
onReserveSlotSuccess={(data) => {
console.log("Slot reserved:", data);
}}
onBookerStateChange={(state) => {
console.log("Booker state changed:", state);
}}
onTimeslotsLoaded={(slots) => {
console.log("Timeslots loaded:", Object.keys(slots).length, "days");
}}
customClassNames={{
bookerContainer: "rounded-lg shadow-lg",
datePickerCustomClassNames: {
datePickerDatesActive: "bg-blue-500"
}
}}
/>
)
}
```
We offer all kinds of customizations to the booker atom via props and customClassNames.
## State management
The Booker atom uses Zustand for internal state management. You can monitor state changes using the `onBookerStateChange` callback:
### Booker states
The booker progresses through several states during the booking flow:
* `loading` - Initial state while event data is being fetched
* `selecting_date` - User is viewing the calendar and selecting a date
* `selecting_time` - User has selected a date and is choosing a time slot
* `booking` - User is filling out the booking form
* `success` - Booking has been successfully created
* `error` - An error occurred during the booking process
### Available state values
When using `onBookerStateChange`, you receive an object containing:
```typescript theme={null}
{
state: BookerState; // Current state of the booker
username: string | null; // Username being booked
eventSlug: string | null; // Event type slug
eventId: number | null; // Event type ID
month: string | null; // Current month (YYYY-MM)
selectedDate: string | null; // Selected date (YYYY-MM-DD)
selectedTimeslot: string | null; // Selected timeslot (ISO string)
selectedDuration: number | null; // Selected duration in minutes
formValues: Record; // Current form field values
layout: BookerLayout; // Current layout (month_view, week_view, column_view)
timezone: string | null; // Current timezone
recurringEventCount: number | null; // Number of recurring events
// ... and more
}
```
### Example: Tracking booking progress
```js theme={null}
import { Booker } from "@calcom/atoms";
import { useState } from "react";
export default function TrackedBooker() {
const [progress, setProgress] = useState({
hasSelectedDate: false,
hasSelectedTime: false,
hasFilledForm: false
});
return (
{
setProgress({
hasSelectedDate: !!state.selectedDate,
hasSelectedTime: !!state.selectedTimeslot,
hasFilledForm: Object.keys(state.formValues).length > 0
});
}}
/>
)
}
```
## Props reference
Below is a comprehensive list of props that can be passed to the booker atom.
| Name | Required | Description |
| :--------------------------------- | :---------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| username | Yes | Username of the person whose schedule is to be displayed |
| eventSlug | Yes | Unique slug created for a particular event |
| orgBannerUrl | No | URL of the user's current organization |
| customClassNames | No | To pass in custom classnames from outside for styling the atom |
| month | No | The exact month for displaying a user's availability; defaults to the current month |
| selectedDate | No | Default selected date for which the slot picker opens |
| startTime | No | Custom start time for the Booker that allows users to decide the first available date. Accepts JavaScript Date object or date string in format `YYYY-MM-DD` (e.g., `"2025-08-20"` or `new Date("2025-08-20")`) |
| hideBranding | No | For hiding any branding on the booker |
| isAway | No | Sets the booker component to the away state |
| allowsDynamicBooking | No | Boolean indicating if the booking is a dynamic booking |
| bookingData | No | Data for rescheduling a booking passed in via this prop |
| defaultFormValues | No | Prefilled values for booking form fields like name, email, guests, notes, reschedule reason, etc. |
| isTeamEvent | No | Boolean indicating if it is a team event |
| duration | No | Refers to a multiple-duration event type; selects default if not passed |
| durationConfig | No | Configures selectable options for a multi-duration event type |
| hashedLink | No | Refers to the private link from event types page |
| isInstantMeeting | No | Boolean indicating if the booking is an instant meeting |
| bookingUid | No | Unique ID generated during booking creation |
| rescheduleUid | No | Unique ID generated during booking creation, same as bookingUid |
| locationUrl | No | Custom meeting link URL instead of a Cal.com link |
| firstName | No | First name of the attendee |
| lastName | No | Last name of the attendee |
| guests | No | Invite a guest to join a meeting |
| name | No | Host name |
| onCreateBookingSuccess | No | Callback function for successful booking creation |
| onCreateBookingError | No | Callback function triggered on booking creation failure |
| onCreateRecurringBookingSuccess | No | Callback function for successful recurring booking creation |
| onCreateRecurringBookingError | No | Callback function triggered on recurring booking creation failure |
| onCreateInstantBookingSuccess | No | Callback function for successful instant booking creation |
| onCreateInstantBookingError | No | Callback function triggered on instant booking creation failure |
| onReserveSlotSuccess | No | Callback function for successful slot reservation |
| onReserveSlotError | No | Callback function triggered on slot reservation failure |
| onDeleteSlotSuccess | No | Callback function for successful slot deletion |
| onDeleteSlotError | No | Callback function triggered on slot deletion failure |
| view | No | Specifies the layout of the booker atom into column, week, or month view |
| metadata | No | Used to pass custom metadata values into the booker. Metadata should be an object eg: `{ bookingSource: "website", userRole: "admin" }` |
| bannerUrl | No | Adds custom banner to the booker atom |
| onBookerStateChange | No | Callback function that is triggered when the state of the booker atom changes. |
| allowUpdatingUrlParams | No | Boolean indicating if the URL parameters should be updated, defaults to false. |
| confirmButtonDisabled | No | Boolean indicating if the submit button should be disabled, defaults to false. |
| timeZones | No | Array of valid IANA timezones to be used in the booker. Eg. \["Asia/Kolkata", "Europe/London"] |
| timezone | No | IANA timezone the booker opens with (e.g. `"America/New_York"`). Takes precedence over the value persisted in local storage from a previous visit; the booker user can still change it via the timezone selector. |
| onTimeslotsLoaded | No | Callback function triggered once the available timeslots have been fetched. |
| roundRobinHideOrgAndTeam | No | Boolean indicating if the organization and team should be hidden in the booker atom sidebar for round robin scheduling type, defaults to false. |
| showNoAvailabilityDialog | No | Boolean indicating if the no availability dialog should be shown, defaults to true. |
| silentlyHandleCalendarFailures | No | Boolean when true the booker still displays slots when the third party calendars credentials are invalid or expired, Booker may show stale availability when enabled |
| hideEventMetadata | No | Boolean that controls the visibility of the event metadata sidebar. When `true`, hides the left sidebar containing event details like title, description, duration, and host information. Defaults to `false`. |
| showTimezoneWhenEventDetailsHidden | No | Boolean that surfaces a timezone selector row above the booker when `hideEventMetadata` is `true`. Lets attendees change their timezone even when the event details sidebar is hidden. Has no effect when `hideEventMetadata` is `false` or when the event type has a locked timezone. Defaults to `false`. |
| teamId | Conditional | Required when `isTeamEvent` is `true`. The numeric ID of the team whose event is being booked. |
| routingFormSearchParams | No | Search parameters from routing forms to pass context for advanced routing logic. Format: `"?field1=value1&field2=value2"`. |
| teamMemberEmail | No | Email of a specific team member to route the booking to. Used in CRM integrations and team routing. |
| crmAppSlug | No | Slug of the CRM app being used (e.g., `"salesforce"`, `"hubspot"`). Used for CRM-based routing. |
| crmOwnerRecordType | No | Type of CRM record being referenced (e.g., `"Lead"`, `"Contact"`). Used with CRM integrations. |
| crmRecordId | No | Unique identifier of the CRM record. Used to associate bookings with CRM records. |
| preventEventTypeRedirect | No | Boolean to prevent automatic redirect to event type's success redirect URL. Defaults to `false`. |
| handleCreateBooking | No | Custom function to handle booking creation. Overrides the default booking creation logic. |
| onDryRunSuccess | No | Callback function triggered when a dry run booking completes successfully. |
| handleSlotReservation | No | Custom function to handle slot reservation logic. Receives the timeslot string as parameter. |
| entity | No | Entity configuration object containing `orgSlug`, `teamSlug`, `name`, and `considerUnpublished` properties. |
| eventMetaChildren | No | React node to inject custom content into the event metadata section. |
| defaultPhoneCountry | No | Sets the default country code for phone number inputs in the booking form. Accepts ISO 3166-1 alpha-2 country codes (e.g., `"us"`, `"gb"`, `"in"`, `"ee"`). When set, phone inputs will default to the specified country's dialing code. |
## Styling
Booker atom accepts custom styles via the `customClassNames` prop. This prop is an object that contains root level styles and nested objects for styling different parts of the booker component. Each nested object groups related styles for a specific section of the booker (e.g., eventMeta, datePicker, availableTimeSlots, etc).
Here is an example booker with root level style `bookerContainer` and nested object `datePickerCustomClassNames` with `datePickerDatesActive` style:
```js theme={null}
import { Booker } from "@calcom/atoms";
export default function Booker( props : BookerProps ) {
return (
<>
>
)
}
```
Below is a list of **customClassNames** properties grouped by their parent objects:
### Root Level Styles
| Property | Description |
| :-------------- | :------------------------------------------- |
| bookerContainer | Adds styling to the whole of the booker atom |
### Event Meta Styles (`eventMetaCustomClassNames`)
| Property | Description |
| :---------------------- | :---------------------------------------------------------------- |
| eventMetaContainer | Styles the event meta component containing details about an event |
| eventMetaTitle | Adds styles to the event meta title |
| eventMetaTimezoneSelect | Adds styles to the event meta timezone selector |
### Date Picker Styles (`datePickerCustomClassNames`)
| Property | Description |
| :-------------------- | :------------------------------------------- |
| datePickerContainer | Adds styling to the date picker |
| datePickerTitle | Styles the date picker title |
| datePickerDays | Adds styling to all days in the date picker |
| datePickerDate | Adds styling to all dates in the date picker |
| datePickerDatesActive | Styles only the dates with available slots |
| datePickerToggle | Styles the left and right toggle buttons |
### Available Time Slots Styles (`availableTimeSlotsCustomClassNames`)
| Property | Description |
| :--------------------------------- | :------------------------------------------------- |
| availableTimeSlotsContainer | Adds styling to the available time slots component |
| availableTimeSlotsHeaderContainer | Styles only the header container |
| availableTimeSlotsTitle | Adds styles to the title |
| availableTimeSlotsTimeFormatToggle | Adds styles to the format toggle buttons |
| availableTimes | Styles all the available times container |
### Confirmation Step Styles (`confirmStep`)
| Property | Description |
| :------------ | :-------------------------------------------- |
| confirmButton | Styles the confirm button in the booking form |
| backButton | Styles the back button in the booking form |
Here is an example with more custom styles:
```js theme={null}
import { Booker } from "@calcom/atoms";
export default function Booker( props : BookerProps ) {
return (
<>
>
)
}
```
## Rescheduling a booking
The booker atom also supports rescheduling a booking. To reschedule a booking, you need to pass in the `rescheduleUid` prop to the booker atom along with the other necessary props. This will allow the booker to display the reschedule form and handle the rescheduling process. The `rescheduleUid` is the booking uid you get when you create a booking.
### Host busy slot indicators
When a host reschedules their own booking while logged in, the booker displays all available time slots including ones that overlap with existing calendar events. To help the host make an informed decision, each slot shows a color-coded indicator:
* **Green dot** — the slot is free on the host's calendar
* **Red dot** — the slot conflicts with an existing event on the host's calendar
This allows hosts to intentionally double-book when needed, such as when rearranging a low-priority meeting. Guests rescheduling the same booking only see genuinely available slots and do not have the ability to override busy times.
For team events, the host can still see their own busy slot indicators, but other team members' availability constraints remain enforced. This means the host can override only their own calendar conflicts, not those of other assigned hosts.
Here is an example of how to use the booker atom for rescheduling an individual event:
```js theme={null}
import { Booker } from "@calcom/atoms";
export default function Booker( props : BookerProps ) {
return (
<>
{
console.log("Booking rescheduled successfully!");
}}
/>
>
)
}
```
For team events, you need to pass isTeamEvent and teamId prop to the booker along with the other necessary props. This will allow the booker to display the reschedule form and handle the rescheduling process. The `teamId` is the team id you get when you create a team event.
Here is an example of how to use the booker atom for rescheduling a team event:
```js theme={null}
import { Booker } from "@calcom/atoms";
export default function Booker( props : BookerProps ) {
return (
<>
{
console.log("Booking rescheduled successfully!");
}}
/>
>
)
}
```
# Booker Embed
Source: https://cal.com/docs/atoms/booker-embed
The Booker Embed atom lets you integrate your Cal.com booking page on your website. It's perfect for those who want to add their booking link to their own site, blog, or app. Attendees would directly see your booking link inside your own webpage like it's part of your website. You can also heavily customize the embed styling.
Using the embed, the entire booking would happen on your website, making it a seamless end-user experience.
Below code snippet can be used to render the Booker Embed atom
```js theme={null}
import { BookerEmbed } from "@calcom/atoms";
export default function BookerEmbedPage(props: BookerProps) {
return (
<>
{
console.log("Booking created successfully!");
}}
defaultFormValues={{ name: " ", email: " " }}
/>
>
);
}
```
For a demonstration of the Booker Embed atom, please refer to the video below.
It is also possible to change the booker layout into a week or column view, you just need to pass in the view prop the layout you want to use. The layouts are as follows: **MONTH\_VIEW,** **WEEK\_VIEW** and **COLUMN\_VIEW**.
Below code snippet can be used to render the Booker Embed atom with week view
```js theme={null}
import { BookerEmbed } from "@calcom/atoms";
export default function BookerEmbed(props: BookerProps) {
return (
<>
{
console.log("Booking created successfully!");
}}
defaultFormValues={{ name: " ", email: " " }}
/>
>
);
}
```
For a demonstration of the Booker Embed atom along with week view, please refer to the video below.
For team events, you need to pass `isTeamEvent` and `teamId` prop to the booker along with the other necessary props. The `teamId` is the ID of the team that owns the event type.
Here is an example of how to use the Booker Embed atom for a team event:
```js theme={null}
import { BookerEmbed } from "@calcom/atoms";
export default function BookerEmbed( props : BookerEmbedProps ) {
return (
<>
{
console.log("Booking created successfully!");
}}
/>
>
)
}
```
We offer all kinds of customizations to the Booker Embed atom via props and customClassNames.
Below is a list of props that can be passed to the Booker Embed atom.
| Name | Required | Description |
| :--------------------------------- | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| username | Yes | Username of the person whose schedule is to be displayed |
| eventSlug | Yes | Unique slug created for a particular event |
| orgBannerUrl | No | URL of the user's current organization |
| customClassNames | No | To pass in custom classnames from outside for styling the atom |
| month | No | The exact month for displaying a user's availability; defaults to the current month |
| selectedDate | No | Default selected date for which the slot picker opens |
| startTime | No | Custom start time for the Booker that allows users to decide the first available date. Accepts JavaScript Date object or date string in format `YYYY-MM-DD` (e.g., `"2025-08-20"` or `new Date("2025-08-20")`) |
| hideBranding | No | For hiding any branding on the booker |
| isAway | No | Sets the booker component to the away state |
| allowsDynamicBooking | No | Boolean indicating if the booking is a dynamic booking |
| bookingData | No | Data for rescheduling a booking passed in via this prop |
| defaultFormValues | No | Prefilled values for booking form fields like name, email, guests, notes, reschedule reason, etc. |
| isTeamEvent | No | Boolean indicating if it is a team event |
| duration | No | Refers to a multiple-duration event type; selects default if not passed |
| durationConfig | No | Configures selectable options for a multi-duration event type |
| hashedLink | No | Refers to the private link from event types page |
| isInstantMeeting | No | Boolean indicating if the booking is an instant meeting |
| bookingUid | No | Unique ID generated during booking creation |
| rescheduleUid | No | Unique ID generated during booking creation, same as bookingUid |
| locationUrl | No | Custom meeting link URL instead of a Cal.com link |
| firstName | No | First name of the attendee |
| lastName | No | Last name of the attendee |
| guests | No | Invite a guest to join a meeting |
| name | No | Host name |
| onCreateBookingSuccess | No | Callback function for successful booking creation |
| onCreateBookingError | No | Callback function triggered on booking creation failure |
| onCreateRecurringBookingSuccess | No | Callback function for successful recurring booking creation |
| onCreateRecurringBookingError | No | Callback function triggered on recurring booking creation failure |
| onCreateInstantBookingSuccess | No | Callback function for successful instant booking creation |
| onCreateInstantBookingError | No | Callback function triggered on instant booking creation failure |
| onReserveSlotSuccess | No | Callback function for successful slot reservation |
| onReserveSlotError | No | Callback function triggered on slot reservation failure |
| onDeleteSlotSuccess | No | Callback function for successful slot deletion |
| onDeleteSlotError | No | Callback function triggered on slot deletion failure |
| view | No | Specifies the layout of the booker atom into column, week, or month view |
| metadata | No | Used to pass custom metadata values into the booker. Metadata should be an object eg: `{ bookingSource: "website", userRole: "admin" }` |
| bannerUrl | No | Adds custom banner to the booker atom |
| onBookerStateChange | No | Callback function that is triggered when the state of the booker atom changes. |
| allowUpdatingUrlParams | No | Boolean indicating if the URL parameters should be updated, defaults to false. |
| confirmButtonDisabled | No | Boolean indicating if the submit button should be disabled, defaults to false. |
| timeZones | No | Array of valid IANA timezones to be used in the booker. Eg. \["Asia/Kolkata", "Europe/London"] |
| onTimeslotsLoaded | No | Callback function triggered once the available timeslots have been fetched. |
| roundRobinHideOrgAndTeam | No | Boolean indicating if the organization and team should be hidden in the booker atom sidebar for round robin scheduling type, defaults to false. |
| showNoAvailabilityDialog | No | Boolean indicating if the no availability dialog should be shown, defaults to true. |
| silentlyHandleCalendarFailures | No | Boolean when true the booker still displays slots when the third party calendars credentials are invalid or expired, Booker may show stale availability when enabled |
| hideEventMetadata | No | Boolean that controls the visibility of the event metadata sidebar. When `true`, hides the left sidebar containing event details like title, description, duration, and host information. Defaults to `false`. |
| showTimezoneWhenEventDetailsHidden | No | Boolean that surfaces a timezone selector row above the booker when `hideEventMetadata` is `true`. Lets attendees change their timezone even when the event details sidebar is hidden. Has no effect when `hideEventMetadata` is `false` or when the event type has a locked timezone. Defaults to `false`. |
| defaultPhoneCountry | No | Sets the default country code for phone number inputs in the booking form. Accepts ISO 3166-1 alpha-2 country codes (e.g., `"us"`, `"gb"`, `"in"`, `"ee"`). When set, phone inputs will default to the specified country's dialing code. |
Required props vary by usage:
1. Individual booking: `username` + `eventSlug`
2. Team booking: `teamId` + `isTeamEvent` + `eventSlug`
## Styling
Booker Embed atom accepts custom styles via the `customClassNames` prop. This prop is an object that contains root level styles and nested objects for styling different parts of the booker component. Each nested object groups related styles for a specific section of the booker (e.g., eventMeta, datePicker, availableTimeSlots, etc).
Here is an example booker with root level style `bookerContainer` and nested object `datePickerCustomClassNames` with `datePickerDatesActive` style:
```js theme={null}
import { BookerEmbed } from "@calcom/atoms";
export default function BookerEmbed( props : BookerEmbedProps ) {
return (
<>
>
)
}
```
Below is a list of **customClassNames** properties grouped by their parent objects:
### Root Level Styles
| Property | Description |
| :-------------- | :------------------------------------------- |
| bookerContainer | Adds styling to the whole of the booker atom |
### Event Meta Styles (`eventMetaCustomClassNames`)
| Property | Description |
| :---------------------- | :---------------------------------------------------------------- |
| eventMetaContainer | Styles the event meta component containing details about an event |
| eventMetaTitle | Adds styles to the event meta title |
| eventMetaTimezoneSelect | Adds styles to the event meta timezone selector |
### Date Picker Styles (`datePickerCustomClassNames`)
| Property | Description |
| :-------------------- | :------------------------------------------- |
| datePickerContainer | Adds styling to the date picker |
| datePickerTitle | Styles the date picker title |
| datePickerDays | Adds styling to all days in the date picker |
| datePickerDate | Adds styling to all dates in the date picker |
| datePickerDatesActive | Styles only the dates with available slots |
| datePickerToggle | Styles the left and right toggle buttons |
### Available Time Slots Styles (`availableTimeSlotsCustomClassNames`)
| Property | Description |
| :--------------------------------- | :------------------------------------------------- |
| availableTimeSlotsContainer | Adds styling to the available time slots component |
| availableTimeSlotsHeaderContainer | Styles only the header container |
| availableTimeSlotsTitle | Adds styles to the title |
| availableTimeSlotsTimeFormatToggle | Adds styles to the format toggle buttons |
| availableTimes | Styles all the available times container |
### Confirmation Step Styles (`confirmStep`)
| Property | Description |
| :------------ | :-------------------------------------------- |
| confirmButton | Styles the confirm button in the booking form |
| backButton | Styles the back button in the booking form |
Here is an example with more custom styles:
```js theme={null}
import { BookerEmbed } from "@calcom/atoms";
export default function BookerEmbed( props : BookerEmbedProps ) {
return (
<>
>
)
}
```
## Rescheduling a booking
The Booker Embed atom also supports rescheduling a booking. To reschedule a booking, you need to pass in the `rescheduleUid` prop to the booker atom along with the other necessary props. This will allow the booker to display the reschedule form and handle the rescheduling process. The `rescheduleUid` is the booking uid you get when you create a booking.
### Host busy slot indicators
When an authenticated host reschedules their own booking, all time slots are displayed — including those that conflict with existing calendar events. Each slot shows a color-coded dot indicator: green for free and red for busy. This lets hosts intentionally double-book if needed. Guests only see genuinely available slots.
For team events, the host sees their own busy slot indicators but other team members' availability constraints remain enforced.
Here is an example of how to use the Booker Embed atom for rescheduling an individual event:
```js theme={null}
import { BookerEmbed } from "@calcom/atoms";
export default function BookerEmbed( props : BookerProps ) {
return (
<>
{
console.log("Booking rescheduled successfully!");
}}
/>
>
)
}
```
For team events, you need to pass isTeamEvent and teamId prop to the booker along with the other necessary props. This will allow the booker to display the reschedule form and handle the rescheduling process. The `teamId` is the team ID you get when you create a team event.
Here is an example of how to use the Booker Embed atom for rescheduling a team event:
```js theme={null}
import { BookerEmbed } from "@calcom/atoms";
export default function BookerEmbed( props : BookerEmbedProps ) {
return (
<>
{
console.log("Booking rescheduled successfully!");
}}
/>
>
)
}
```
# Cal OAuth Provider
Source: https://cal.com/docs/atoms/cal-oauth-provider
Cal OAuth Provider is used to setup Cal Atoms within your app after your users have authenticated using your Cal OAuth 2.0 client.
Your users will have authenticated with your Cal OAuth client and you will have access to their access tokens, which
the Cal Atoms will use to manage their event type settings, availability, etc. within your app.
It is used in the root of your app, be it \_app.js or \_app.tsx in case of
Next.js or App.js or App.ts in case of React. Here is an example:
```js theme={null}
import "@calcom/atoms/globals.min.css";
import { CalOAuthProvider } from '@calcom/atoms';
function MyApp({ Component, pageProps }) {
const accessToken = "user-oauth-access-token";
return (
);
}
export default MyApp;
```
Below is a list of props that can be passed to the Cal OAuth Provider.
| Name | Required | Description |
| :----------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| clientId | Yes | Your OAuth2 client ID |
| options | Yes | Configuration options - `apiUrl` (should be [https://api.cal.com/v2](https://api.cal.com/v2)) and `refreshUrl` (URL of endpoint you have to build that to which atoms will send expired access tokens and receive new one in return. `readingDirection` (defaults to "ltr" but can also pass "rtl" which will change direction of UI components) |
| accessToken | Yes | The access token of your user for whom cal handles scheduling. |
| autoUpdateTimezone | No | Whether to automatically update managed user timezone (default: true) |
| language | No | Language code (default: "en") - available languages: "en", "de", "fr", "it", "nl", "pt-BR", "es" |
| organizationId | No | ID of your organization |
# Calendar settings
Source: https://cal.com/docs/atoms/calendar-settings
The calendar settings atom can be used to configure how your event types interact with your calendars. This atom gives you the ability to select where to add events when you're booked. Additionally, you can also select which calendars you want to check for conflicts to prevent double bookings.
Below code snippet can be used to render the calendar settings button
```js theme={null}
import { CalendarSettings } from "@calcom/atoms";
export default function CalendarSettingsComponent() {
return (
<>
>
)
}
```
For a demonstration of the calendar settings atom, please refer to the video below.
Customizations can be done to the calendar settings atom via props and classnames. Below is a list of props that can be passed to the calendar settings atom
| Name | Required | Description |
| :--------- | :------- | :------------------------------------------------------------- |
| classNames | No | To pass in custom classnames from outside for styling the atom |
Along with the props, calendar settings atom accepts custom styles via the **classNames** prop. Below is a list of props that fall under this **classNames** prop.
| Name | Description |
| :------------------------------------------ | :------------------------------------------------------------------------------------------ |
| calendarSettingsCustomClassnames | Adds styling to the entire calendar settings atom |
| destinationCalendarSettingsCustomClassnames | Adds styling only to the destination calendar container |
| selectedCalendarSettingsCustomClassnames | Adds styling only to the selected calendar container |
| selectedCalendarSettingsClassNames | An object for granular styling of selected calendars component (see detailed table below) |
| destinationCalendarSettingsClassNames | An object for granular styling of destination calendar component (see detailed table below) |
### selectedCalendarSettingsClassNames Object Structure
The `selectedCalendarSettingsClassNames` prop accepts an object with the following nested structure for granular styling of the selected calendars component:
| Property Path | Description |
| :-------------------------------------------------------------------- | :---------------------------------------------------------- |
| `container` | Styles the main container of the selected calendars section |
| `header.container` | Styles the header container |
| `header.title` | Styles the header title |
| `header.description` | Styles the header description |
| `selectedCalendarsListClassNames.container` | Styles the container that holds all selected calendar items |
| `selectedCalendarsListClassNames.selectedCalendar.container` | Styles each individual calendar item container |
| `selectedCalendarsListClassNames.selectedCalendar.header.container` | Styles the header section of each calendar item |
| `selectedCalendarsListClassNames.selectedCalendar.header.title` | Styles the title of each calendar item |
| `selectedCalendarsListClassNames.selectedCalendar.header.description` | Styles the description of each calendar item |
| `selectedCalendarsListClassNames.selectedCalendar.body.container` | Styles the body section of each calendar item |
| `selectedCalendarsListClassNames.selectedCalendar.body.description` | Styles the body description of each calendar item |
| `noSelectedCalendarsMessage` | Styles the message shown when no calendars are connected |
### destinationCalendarSettingsClassNames Object Structure
The `destinationCalendarSettingsClassNames` prop accepts an object with the following nested structure for granular styling of the destination calendar component:
| Property Path | Description |
| :------------------- | :------------------------------------------------------------ |
| `container` | Styles the main container of the destination calendar section |
| `header.title` | Styles the header title text |
| `header.description` | Styles the header description text |
Additionally, if you wish to select either the Destination Calendar or the Selected Calendar, we also provide atoms specifically designed for this purpose.
**1. Destination calendar settings atom**
The destination calendar settings atom lets you can select which calendar you want to add events to when you're booked. Below code snippet can be used to render the destination calendar settings atom.
```js theme={null}
import { DestinationCalendarSettings } from "@calcom/atoms";
export default function DestinationCalendars() {
const loadingStatus = <>Loading...>
return (
<>
>
)
}
```
This is how the destination calendar settings atom looks:
Customizations can be done to the destination calendar settings atom via props. Below is a list of props that can be passed to the calendar settings atom
| Name | Required | Description |
| :----------- | :------- | :------------------------------------------------------------- |
| statusLoader | No | To pass in a custom component for the loading state |
| classNames | No | To pass in custom classnames from outside for styling the atom |
**2. Selected calendar settings atom**
The selected calendar settings atom lets you select which calendars you want to check for conflicts to prevent double bookings. Below code snippet can be used to render the selected calendar settings atom.
```js theme={null}
import { SelectedCalendarsSettings } from "@calcom/atoms";
export default function SelectedCalendars( props : SelectedCalendarsProps ) {
return (
<>
>
)
}
```
This is how the selected calendar settings atom looks:
Customizations can be done to the destination calendar settings atom via props. Below is a list of props that can be passed to the calendar settings atom
| Name | Required | Description |
| :--------- | :------- | :------------------------------------------------------------- |
| classNames | No | To pass in custom classnames from outside for styling the atom |
# Calendar view
Source: https://cal.com/docs/atoms/calendar-view
The calendar view atom is a weekly calendar where users see the entire week at a glance, with available and unavailable time slots clearly marked. It has two modes: an **event type view** that shows the bookable slots of one event type, and a **users view** that overlays the busy times and shared availability of a set of users so a meeting can be placed between them.
## Event type view
Pass `isEventTypeView` together with the event slug and either a username (individual event) or a team id (team event).
### Individual event type
```js theme={null}
import { CalendarView } from "@calcom/atoms";
export default function Calendar( props : CalendarProps ) {
return (
)
}
```
### Team event type
```js theme={null}
import { CalendarView } from "@calcom/atoms";
export default function Calendar( props : CalendarProps ) {
return (
)
}
```
For a demonstration of the event type view, please refer to the video below.
Props accepted in the event type view:
| Name | Required | Description |
| :-------------- | :------- | :-------------------------------------------------------------------------------------- |
| isEventTypeView | Yes | Must be `true` to render the event type view |
| eventSlug | Yes | Unique slug created for a particular event |
| username | Optional | Username of the person whose schedule is displayed, required only for individual events |
| teamId | Optional | Id of the team the event belongs to, required only for team events |
## Users view
Without `isEventTypeView`, the atom renders the busy times of the given users, colored by their order in `userIds`, and their intersected availability as clickable slots. Omit `userIds` to show the authenticated user's own calendar.
```js theme={null}
import { CalendarView } from "@calcom/atoms";
export default function InterviewCalendar( props : InterviewCalendarProps ) {
return (
)
}
```
Props accepted in the users view. All of them are optional.
| Name | Description |
| :----------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| userIds | Users whose busy times and availability are displayed. An empty array renders an unavailable calendar; omit it to show the authenticated user's own calendar |
| weekStart | First day of the displayed week. Defaults to Sunday |
| eventTypeId | Event type the meeting is booked with; its length and slot interval drive the grid |
| duration | Slot duration in minutes for the hover preview, click grid and availability slots. Defaults to 30 |
| slotInterval | Minutes between consecutive slot starts. Defaults to the event type's interval, then to the duration. Passing it makes the slot interval picker controlled |
| bookingUid | Booking to keep visible and highlighted on the grid, whoever its hosts are |
| initialDate | Week to open on. Defaults to the current week, or to the week of `bookingUid` when that booking is loaded. Past weeks can be navigated with the header arrows |
| timezone | Timezone the grid is drawn in. Passing it makes the calendar controlled; leave it out to start from the authenticated user's timezone |
| showTimezoneControls | Shows an editable meeting timezone above the grid, next to every displayed user's timezone |
| onTimezoneChange | Fires when a meeting timezone is picked, whether or not `timezone` is controlled |
| showSlotIntervalControls | Shows a slot interval picker above the grid so the cadence can be changed on the spot |
| slotIntervalOptions | Intervals offered by the picker, in minutes. Defaults to 15, 30 and 60; the current interval is always included |
| onSlotIntervalChange | Fires when a slot interval is picked, whether or not `slotInterval` is controlled |
| onCellClick | Fires for clicks anywhere on the grid, inside or outside the users' availability, with the clicked `Date` |
| onEventClick | Fires when an existing event on the grid is clicked |
# Conferencing Apps
Source: https://cal.com/docs/atoms/conferencing-apps
The Conferencing Apps Atom allows users to seamlessly install applications such as Zoom, Google Meet, and Microsoft Teams, enabling them to set these as default or optional locations for their events.
Below code snippet can be used to render Conferencing Apps Atom
```js theme={null}
import { ConferencingAppsSettings } from "@calcom/atoms";
import { usePathname } from "next/navigation";
export default function ConferencingApps() {
const pathname = usePathname();
const callbackUri = `${window.location.origin}${pathname}`;
return (
<>
>
)
}
```
Below is a list of props that can be passed to the Conferencing Apps Atom
| Name | Required | Description |
| :-------------------------- | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| returnTo | No | The URL of the page to redirect to after a successful installation. |
| onErrorReturnTo | No | The URL of the page to redirect to in case an error occurs. |
| disableToasts | No | Boolean value to disable toast notifications in the atom. |
| apps | No | Array of conferencing app slugs to display in the dropdown. If provided, only these apps will be shown (if not already installed). Valid values are `'google-meet'`, `'zoom'`, and `'msteams'`. |
| disableBulkUpdateEventTypes | No | A Boolean flag that prevents the bulk update of event types modal from appearing when the default conferencing app is changed. Defaults to false |
## Google Meet
For a demonstration of installing Google Meet, setting it as the default conferencing app for all event types, and removing the app, please watch the video below.
Google meet requires Google Calendar to be installed first
## Zoom
For a demonstration of installing Zoom, setting it as the default conferencing app for all event types, and removing the app, please watch the video below.
## MS Teams Video
For a demonstration of installing MS Teams Video, setting it as the default conferencing app for all event types, and removing the app, please watch the video below.
Connecting with MS Teams requires a work/school Microsoft account.
If you continue with a personal account you will receive an error
# Create schedule
Source: https://cal.com/docs/atoms/create-schedule
The Create Schedule atom provides a simple dialog interface for users to create new availability
schedules. Fully customizable with callback support for handling successful schedule creation.
Below code snippet can be used to render the Create schedule atom
```js theme={null}
import { CreateSchedule } from "@calcom/atoms";
export default function CreateSchedule() {
return (
<>
>
)
}
```
For a demonstration of the Create schedule atom, please refer to the video below.
For developers who don't want the dialog-based interface, we provide a `CreateScheduleForm` atom to integrate the schedule
creation form directly into your own UI. This headless approach gives you full flexibility to
handle layout, styling, and user flow exactly how you need it.
Below code snippet can be used to render the Create schedule form atom
```js theme={null}
import { CreateScheduleForm } from "@calcom/atoms";
export default function CreateScheduleForm() {
return (
<>
>
)
}
```
For a demonstration of the Create schedule form, please refer to the video below.
We offer all kinds of customizations to the Create schedule atom via props. Below is a list of props that can be passed to the atom.
| Name | Required | Description |
| :--------------- | :------- | :-------------------------------------------------------------------- |
| name | No | The label for the create schedule button |
| customClassNames | No | To pass in custom classnames from outside for styling the atom |
| onSuccess | No | Callback function that handles successful creation of schedule |
| onError | No | Callback function to handles errors at the time of schedule creation |
| disableToasts | No | Boolean value that determines whether the toasts are displayed or not |
Along with the props, create schedule atom accepts custom styles via the **customClassNames** prop. Below is a list of props that fall under this **customClassNames** prop.
| Name | Description |
| :------------------- | :-------------------------------------------------------------------------------------------------------- |
| createScheduleButton | Adds styling to the create button |
| inputField | Adds styling to the container of the name input field |
| formWrapper | Adds styling to the whole form |
| actionsButtons | Object containing classnames for the submit, cancel buttons and container inside the create schedule atom |
Similar to the create schedule atom, the create schedule form also offer all kinds of customizations via props. Below is a list of props that can be passed to the atom.
\| customClassNames | No | To pass in custom classnames from outside for styling the atom |
\| onSuccess | No | Callback function that handles successful creation of schedule |
\| onError | No | Callback function to handles errors at the time of schedule creation |
\| disableToasts | No | Boolean value that determines whether the toasts are displayed or not |
Along with the props, create schedule form also accepts custom styles via the **customClassNames** prop. Below is a list of props that fall under this **customClassNames** prop.
| Name | Description |
| :------------- | :-------------------------------------------------------------------------------------------------------- |
| formWrapper | Adds styling to the whole form |
| inputField | Adds styling to the container of the name input field |
| actionsButtons | Object containing classnames for the submit, cancel buttons and container inside the create schedule atom |
Please ensure all custom classnames are valid Tailwind CSS classnames. Note that sometimes the custom
classnames may fail to override the styling with the classnames that you might have passed via props. That
is because the clsx utility function that we use to override classnames inside our components has some
limitations. A simple get around to solve this issue is to just prefix your classnames with ! property just
before passing in any classname.
# Event Type
Source: https://cal.com/docs/atoms/event-type
The event type atom enables a user to create events that others can use to book them. The events can be created for an individual or a team. However, a team event type can only be created by a team admin or owner.
## Individual event type
Below code snippet can be used to render the create event type atom, which is a form with all the required input fields needed to create an event type.
```js theme={null}
import { CreateEventType } from "@calcom/atoms";
export default function EventType() {
return (
<>
{
console.log("EventType created successfully", eventType);
}}
customClassNames={{
atomsWrapper: "border p-4 rounded-md",
buttons: { submit: "bg-red-500", cancel: "bg-gray-300" },
}}
/>
>
)
}
```
For a demonstration of the create event type atom, please refer to the video below.
## Team event type
For creating an event type for a team, you need to provide the team id of your particular team as a prop to the create event type atom. Importantly, a team event type can only be created by a managed user who has an accepted admin or owner role within the team. That means
you have to create a managed user and then add an accepted membership with an admin or owner role by making a request to the [memberships endpoint](https://cal.com/docs/api-reference/v2/orgs-teams-memberships/create-a-membership). Example body:
```js theme={null}
{
"userId": 1006,
"accepted": true,
"role": "OWNER"
}
```
then you have to pass the access token of this managed user to the [CalProvider](https://cal.com/docs/platform/atoms/cal-provider) to then finally be able to
use the `` component while passing `teamId={teamId}` to it to create a team event type:
```js theme={null}
import { CreateEventType } from "@calcom/atoms";
export default function TeamEventType(teamId: number) {
return (
<>
{
console.log("EventType created successfully", eventType);
}}
customClassNames={{
atomsWrapper: "border p-4 rounded-md",
buttons: { submit: "bg-red-500", cancel: "bg-gray-300" },
}}
/>
>
)
}
```
For a demonstration of the create event type atom, please refer to the video below.
Below is a list of props that can be passed to the create event type atom
| Name | Required | Description |
| :--------------- | :------- | :--------------------------------------------------------------------- |
| teamId | No | Unique identifier of the team |
| customClassNames | No | To pass in custom classnames from outside for styling the atom |
| onSuccess | No | Callback function that handles successful creation of event type |
| onError | No | Callback function to handles errors at the time of event type creation |
| onCancel | No | Callback function that handles cancellation of event type form |
Along with the props, create event type atom accepts custom styles via the **customClassNames** prop. Below is a list of props that fall under this **customClassNames** prop.
| Name | Description |
| :----------- | :----------------------------------------------------------------------------------------------- |
| atomsWrapper | Adds styling to the whole create event type atom |
| buttons | Object containing classnames for the submit and cancel buttons inside the create event type atom |
Individual and team event types take in the same props.
## Event type settings
The event type settings contains various tabs that can be used to configure or make modifications to the event type that has just been created.
Below code snippet can be used to render the event type settings atom.
```js theme={null}
import { EventTypeSettings } from "@calcom/atoms";
export function EventTypeTabs(eventTypeId: number) {
return (
<>
{
console.log("EventType settings updated successfully", eventType);
}}
onFormStateChange={(formState) => {
console.log("Form state changed:", formState);
// Access form data: formState.isDirty, formState.dirtyFields, formState.values
}}
customClassNames={{ atomsWrapper: "w-[70vw]! m-auto!" }}
/>
>
);
}
```
To show only specific tabs, remove the ones you don't want from the array:
```js theme={null}
// Example: Show only setup, limits, and availability tabs
{
console.log("EventType settings updated successfully", eventType);
}}
/>
```
If the `eventTypeId` is of a team event type id, then only the owner or admin of the team can update the event type settings. That means
you have to create a managed user and then add an accepted membership with an admin or owner role by making a request to the [memberships endpoint](https://cal.com/docs/api-reference/v2/orgs-teams-memberships/create-a-membership). Example body:
```js theme={null}
{
"userId": 1006,
"accepted": true,
"role": "OWNER"
}
```
then you have to pass the access token of this managed user to the [CalProvider](https://cal.com/docs/platform/atoms/cal-provider) to then finally be able to
use the `` component while passing id of a team event type enabling admin or owner to edit team event types.
For a demonstration of the event type settings atom, please refer to the video below.
Below is a list of props that can be passed to the event type settings atom.
| Name | Required | Description | |
| :---------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | - |
| id | Yes | The event type id obtained at the time of event type creation | |
| tabs | No | Array of tabs to display in the event type settings. Possible values: `["setup", "limits", "recurring", "advanced", "availability", "team", "payments"]`. Remove any tabs you don't want to show. | |
| onSuccess | No | Callback function that triggers when the event type is successfully updated | |
| onError | No | Callback function to handles errors at the time of event type update | |
| onFormStateChange | No | Callback function that triggers when the form state changes, providing access to isDirty, dirtyFields, and current form values | |
| onDeleteSuccess | No | Callback function that triggers when the event type is successfully deleted | |
| onDeleteError | No | Callback function that handles errors at the time of event type deletion | |
| allowDelete | No | Boolean value that determines whether the delete button is displayed or not | |
| disableToasts | No | Boolean value that determines whether the toasts are displayed or not | |
| customClassNames | No | To pass in custom classnames from outside for styling the atom | |
Along with the props, event type settings atom accepts custom styles via the **customClassNames** prop. Below is a list of props that fall under this **customClassNames** prop.
| Name | Description |
| :----------- | :------------------------------------------------- |
| atomsWrapper | Adds styling to the whole event type settings atom |
Please ensure all custom classnames are valid [Tailwind CSS](https://tailwindcss.com/) classnames.
## Programmatic Form Validation and Submission
The EventTypeSettings component supports programmatic form validation and submission through a ref-based API. This allows you to validate form data and submit forms programmatically without user interaction.
```js theme={null}
import { useRef } from 'react';
import { EventTypeSettings } from "@calcom/atoms";
import type { EventTypePlatformWrapperRef } from "@calcom/atoms";
export function EventTypeWithValidation(eventTypeId: number) {
const eventTypeRef = useRef(null);
const handleValidate = async () => {
const result = await eventTypeRef.current?.validateForm();
if (result?.isValid) {
console.log("Form is valid");
} else {
console.log("Validation errors:", result?.errors);
}
};
const handleSubmit = () => {
eventTypeRef.current?.handleFormSubmit({
onSuccess: () => {
// Additional success handling logic here
console.log('Event type updated successfully');
},
onError: (error) => {
// Additional error handling logic here
console.error('Error updating event type:', error);
}
});
};
return (
<>
{
console.log("EventType updated successfully", eventType);
}}
/>
>
);
}
```
### Ref Methods
| Method | Description |
| :--------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| validateForm | Validates the current event type form state and returns a promise with validation results. |
| handleFormSubmit | Programmatically submits the event type form, triggering the same validation and submission flow as clicking the save button. Unlike `validateForm`, this method will check required fields and prevent submission unless all required fields are set |
### Callbacks
The `handleFormSubmit` method accepts an optional callbacks object with the following properties:
```typescript theme={null}
type EventTypeFormCallbacks = {
onSuccess?: () => void;
onError?: (error: Error) => void;
};
```
* **onSuccess**: Called when the form submission is successful. This allows you to execute additional logic after a successful update.
* **onError**: Called when an error occurs during form submission. The error parameter contains details about what went wrong, allowing you to handle specific error cases or display custom error messages.
The `validateForm` method returns an `EventTypeFormValidationResult` object with:
* `isValid`: Boolean indicating if the form passed validation
* `errors`: Object containing any validation errors found
**Note:** If a required field is not filled in, the `validateForm` method will not return any error. The validation focuses on the format and consistency of provided data rather than enforcing required field completion.
Following are the tabs that are available in the event type settings atom:
**1. Event Setup**
The event setup tab allows users to configure the fundamental aspects of their events. In this tab, users can define or update essential details such as the event title, description, duration, slug, location as well as the event URL.
**2. Availability**
The availability tab allows users to manage their scheduling preferences effectively. Within this tab, users can edit their existing availability or set new availability options based on the preferences they have previously established.
For team event types, availability for a specific event can also be configured for the entire team based on a common schedule. Otherwise each host can be assigned their own specific availability based on their schedules.
**3. Assignment**
The assignment tab is only available for team event types. It lets you configure what kind of scheduling you want to enable for your team event, where you can choose from a collective, round robin or managed event. You can also choose who amongst your team can or cannot attend the event.
**4. Limits**
The limits tab lets you configure how often you can be booked. You can add a lot of configurations via this tab, such as a buffer time before or after the event and a minimum notice period.
Also if you want to select how many time a particular event can be booked, or how far in the future the event can be booked that is also possible. Or if you want to limit the event to a specific amount of time, that can also be done via the limits tab.
Below video shows a demonstration of the limits tab.
**5. Advanced**
The advanced tab lets you customize your event type with a lot more options. For example, you can set a custom name for the event type that will appear in your calendar, or add questions that will appear on your booking page. Another option would be to add an email verification for the person who is booking the event.
To protect registered Cal.com users from impersonation, email verification is always required when the booker enters an email address that belongs to an existing Cal.com account. This check runs even if email verification is not enabled on the event type.
Below video shows a demonstration of the all the options you get in the advanced tab.
**6. Recurring**
The recurring tab lets you set up a recurring event. You can set up recurring events such as weekly, monthly, yearly, etc, and choose how many times the event should repeat. You can schedule a recurring event for up to 32 occurrences.
Recurring events are currently experimental and causes some issues sometimes when checking for availability. We are working on fixing this.
**7. Payments**
The payments tab lets you accept payments for your events. At the moment we only support payments via Stripe.
Once you connect your Stripe account, you can set a custom price along with the currency and payment option; which will determine when to charge your customers.
# Google calendar connect
Source: https://cal.com/docs/atoms/google-calendar-connect
The Google calendar connect button is used to sync user's google calendar - whenever an event is created in cal.com it will show up in the google calendar.
Below code snippet can be used to render the Google calendar connect button
```js theme={null}
import { Connect } from "@calcom/atoms";
export default function Connect() {
return (
<>
>
)
}
```
For a demonstration of the Google calendar connect integration, please refer to the video below.
Google calendar connect supports integration for both single and multiple users. The above video demonstration showcases the integration for a single user. To enable integration for multiple users, simply pass the prop `isMultiCalendar` as `true`. This allows your application to handle multiple Google calendar accounts seamlessly, providing a more flexible experience for users who manage several calendars.
Below code snippet can be used to render the Google calendar connect button for multiple users
```js theme={null}
import { Connect } from "@calcom/atoms";
export default function Connect() {
return (
<>
>
)
}
```
For a demonstration of the Google calendar connect integration for multiple users, please refer to the video below.
We offer all kinds of customizations to the Google calendar connect via props. Below is a list of props that can be passed to the Google calendar connect.
| Name | Required | Description |
| :-------------------- | :------- | :---------------------------------------------------------------------------------------- |
| className | No | To pass in custom classnames from outside for styling the atom |
| label | No | The label for the connect button |
| alreadyConnectedLabel | No | Label to display when atom is in already connected state |
| loadingLabel | No | Label to display when atom is in loading state |
| onCheckError | No | A callback function to handle errors when checking the connection status |
| redir | No | A custom redirect URL link where the user gets redirected after successful authentication |
| initialData | No | Initial data to be passed |
| isMultiCalendar | No | Specifies if the button supports integration for multiple users |
| tooltip | No | In case user wants to pass external tooltip component |
| tooltipSide | No | Specifies what direction the tooltip appears |
| isClickable | No | Boolean to disable button or not |
| onSuccess | No | A callback function to handle success when checking the connection status |
Please ensure all custom classnames are valid Tailwind CSS classnames. Note that sometimes the custom
classnames may fail to override the styling with the classnames that you might have passed via props. That
is because the clsx utility function that we use to override classnames inside our components has some
limitations. A simple get around to solve this issue is to just prefix your classnames with ! property just
before passing in any classname.
# Managing booking fields
Source: https://cal.com/docs/atoms/guides/booking-fields
Prefilling and / or making them read-only.
## Prefilling default booking fields
If you want to pre-fill name and email fields you can pass them to the Booker atom's defaultFormValues prop:
```js theme={null}
```
which will look like:
## Prefilling custom booking fields
We created an event type for managed user with custom text and select fields. Name and email fields are created by default. Here is the request:
That looks in booker like:
Let's say you want that when someone is trying to book one of your managed users that the
booking fields should be pre-filled with some data. We see that one of the booking fields has slug `coding-language` and the other
`help-with`. To pre-fill them you pass their slugs and values to the Booker atom's defaultFormValues prop:
```js theme={null}
```
which now will populate the booking fields:
## Making pre-filled fields read-only
`disableOnPrefill` booking field property controls whether or not to make a booking field read only if we pass its value in the defaultFormValues prop. Here is an example request
where we create an event type with one booking field with "disableOnPrefill": true and the other with "disableOnPrefill": false:
If we pass their values to the Booker atom:
```js theme={null}
```
then here is how it will look like in the Booker. The "What language will we peer code in" text field is read only:
# Custom booking flow
Source: https://cal.com/docs/atoms/guides/custom-booking-flow
Learn how to intercept a booking to introduce your custom flow and then submit the booking.
If you want to have a custom payment flow or any other custom action before the booking happens, you can intercept the booking
once the user clicks book in the [Booker atom](/docs/atoms/booker), run whatever custom code you need and then finally
submit the booking with the intercepted booking data.
In your component that renders the Booker atom create a `interceptBooking` function that will be passed to the Booker atom `handleCreateBooking` hook. See [Booker atom](/docs/atoms/booker) docs on how to use it - in this tutorial
we focus on the `handleCreateBooking` hook.
```
import { Booker, UseCreateBookingInput } from "@calcom/atoms";
export function BookingPage(props: { calUsername: string; calEmail: string }) {
...
const interceptBooking = useCallback((data: UseCreateBookingInput) => {
console.log(data);
}, []);
return (
);
}
```
1. When user clicks "Book" in the Booker atom the booking will not happen. Instead, the `interceptBooking` function will be called and will receive all the booking data.
2. Then, you can hide the Booker component and instead render another component, show a modal, redirect to a new page or whatever you want to do.
3. After your custom action is finished, extract the necessary information from the booking data passed to `interceptBooking` and make a POST request to
[create booking](/docs/api-reference/v2/bookings/create-a-booking) endpoint to actually create the booking.
# Custom slot selection flow
Source: https://cal.com/docs/atoms/guides/custom-slot-select-flow
Learn how to use handleSlotReservation for custom slot selection flows.
## Using `handleSlotReservation` for custom slot selection flows
If you want to trigger a custom action immediately when a user selects a timeslot (before the booking form appears), you can use the `handleSlotReservation` prop. This prop allows you to intercept the slot selection event, enabling you to run custom logic—such as calling the reserve slot API.
**Example usage:**
```tsx theme={null}
{
// Your custom logic here
console.log("User selected timeslot:", timeslot);
// For example, call the reserve slot API's etc.
}}
// ...other props
/>
```
* When a user selects a slot, the `handleSlotReservation` function will be called with the selected timeslot as its argument.
* If you provide this prop, the default slot reservation flow will be interrupted until your custom logic is complete.
* This is useful for introducing custom slot reservation flows.
See the [Reserve a Slot API](/docs/api-reference/v2/slots/reserve-a-slot) documentation for more details on reserving slots programmatically.
# Custom toasts
Source: https://cal.com/docs/atoms/guides/replacing-toasts
Replace default cal.com toasts with your own
It is possible to disable default toasts for the `AvailabilitySettings` and `EventTypeSettings` atoms to then use hooks => callbacks
and listen for updates to then render your own toasts.
If we use the `AvailabilitySettings` atom, make changes and press "Save" a confirmation appears:
We can disable them using `disableToasts` prop:
```js theme={null}
```
Now when changes happen and Save is clicked the toast will not appear.
You can then create functions and pass them to hooks:
```js theme={null}
```
in case of success response is passed to your functions and in case of error and error.
# Bookings hooks
Source: https://cal.com/docs/atoms/hooks/bookings-hooks
Overview of all the hooks associated with bookings.
Optimize your development workflow using our custom hooks. Our custom hooks are designed to simplify the integration of the Cal.com API into your products. With these hooks, you can effortlessly manage state, handle side effects, and optimize performance, allowing you to focus on building exceptional user experiences without the hassle of complex API interactions.
Below is a list of hooks you can find that are associated with bookings.
### 1. get all bookings - `useBookings`
The useBookings hook returns an array of bookings that belong to the user whose access token is passed to [\](https://cal.com/docs/atoms/cal-oauth-provider). You can filter bookings by status, event type id, attendee email etc. - [see this class](https://github.com/calcom/cal.com/blob/307b2946a2cb6dcdb32dbee6c4f448e8a01c4285/packages/platform/types/bookings/2024-08-13/inputs/get-bookings.input.ts#L32) for filters you can pass to the hook.
Below code snippet shows how to use the useBookings hook to fetch bookings for a specific user using take, skip and status filters:
```js theme={null}
import { useBookings } from "@calcom/atoms";
export default function Bookings() {
const { isLoading: isLoadingUpcomingBookings, data: upcomingBookings } = useBookings({
take: 50,
skip: 0,
status: ["upcoming"],
});
return (
<>
);
})}
>
);
}
```
### 2. get specific booking - `useBooking`
The useBooking hook returns detailed information about a single booking. Simply provide the booking's unique identifier ***(bookingUid)*** as a parameter to fetch its associated data.
Below code snippet shows how to use the useBooking hook to fetch a specific booking for a user.
```js theme={null}
import { useBooking } from "@calcom/atoms";
export default function Booking() {
const { isLoading: isLoadingBooking, data: booking } = useBooking("nChHoxEm1GXVPzi7TNAuWc");
return (
<>
{isLoadingBooking &&
Loading...
}
{!isLoadingBooking && !booking &&
No booking found
}
{!isLoading &&
!!booking &&
return (
Title: {booking.title}
)
}
>
);
}
```
### 3. cancel booking - `useCancelBooking`
The useCancelBooking hook allows you to cancel a booking. This hook returns a mutation function that handles the cancellation process. To cancel a booking, you'll need to provide at least the booking's unique identifier ***(uid).*** While the uid is the only mandatory parameter, you can enhance the cancellation process by including optional parameters such as ***cancellationReason*** to specify why the booking is being canceled, and ***allRemainingBookings*** to specify whether to cancel all future recurring bookings.
Below code snippet shows how to use the useCancelBooking hook to cancel a booking for a user.
```js theme={null}
import { useCancelBooking } from "@calcom/atoms";
export default function CancelBooking() {
const { mutate: cancelBooking } = useCancelBooking({
onSuccess: () => {
console.log("Booking canceled successfully!")
},
});
return (
<>
>
);
}
```
### 4. get all bookings of a user as an organization admin - `useOrganizationUserBookings`
The useBookings hooks returns bookings of user associated with the access token passed to the [\](https://cal.com/docs/atoms/cal-oauth-provider), but this hook
allows organization admins to fetch bookings of any organization member.
The access token passed to the [\](https://cal.com/docs/atoms/cal-oauth-provider) must belong
to a user who is an organization admin. That means you have to create a user and then add an accepted membership with an admin or owner role by making a request to the [organizations memberships endpoint](https://cal.com/docs/api-reference/v2/orgs-memberships/create-a-membership). Example body:
```js theme={null}
{
"userId": 1006,
"accepted": true,
"role": "ADMIN"
}
```
then you have to pass the access token of this user to the [CalOAuthProvider](https://cal.com/docs/atoms/cal-oauth-provider) to then finally be able to
use the "useOrganizationUserBookings" by specifying "userId" which will return an array of that user's bookings. You can filter bookings by status, event type id, attendee email etc. - [see this class](https://github.com/calcom/cal.com/blob/307b2946a2cb6dcdb32dbee6c4f448e8a01c4285/packages/platform/types/bookings/2024-08-13/inputs/get-bookings.input.ts#L32) for filters you can pass to the hook.
Below code snippet shows how to use the "useOrganizationUserBookings" hook to fetch bookings for a specific user using take, skip and status filters:
```js theme={null}
import { useOrganizationUserBookings } from "@calcom/atoms";
export default function Bookings() {
const someUserId = 1218;
const { isLoading: isLoadingUpcomingBookings, data: upcomingBookings } = useOrganizationUserBookings(someUserId, {
take: 50,
skip: 0,
status: ["upcoming"],
});
return (
<>
);
})}
>
);
}
```
### 5. get all organization bookings as an organization admin - `useOrganizationBookings`
The useBookings hooks returns bookings of user associated with the access token passed to the [\](https://cal.com/docs/atoms/cal-oauth-provider), but this hook
allows organization admins to fetch bookings of the whole organization.
The access token passed to the [\](https://cal.com/docs/atoms/cal-oauth-provider) must belong
to a user who is an organization admin. That means you have to create a user and then add an accepted membership with an admin or owner role by making a request to the [organizations memberships endpoint](https://cal.com/docs/api-reference/v2/orgs-memberships/create-a-membership). Example body:
```js theme={null}
{
"userId": 1006,
"accepted": true,
"role": "ADMIN"
}
```
then you have to pass the access token of this user to the [CalOAuthProvider](https://cal.com/docs/atoms/cal-oauth-provider) to then finally be able to
use the "useOrganizationBookings" which will return an array of organization bookings. You can filter bookings by status, user ids, event type ids, attendee email etc. - [see this class](https://github.com/calcom/cal.com/blob/307b2946a2cb6dcdb32dbee6c4f448e8a01c4285/packages/platform/types/bookings/2024-08-13/inputs/get-bookings.input.ts#L32) for filters you can pass to the hook + you can use "userIds" filter like in example below.
Below code snippet shows how to use the "useOrganizationBookings" hook to fetch bookings for a specific users using take, skip and userIds filters:
```js theme={null}
import { useOrganizationBookings } from "@calcom/atoms";
export default function Bookings() {
const { isLoading: isLoadingUpcomingBookings, data: upcomingBookings } = useOrganizationBookings({
take: 50,
skip: 0,
userIds: [10, 11],
});
return (
<>
);
})}
>
);
}
```
# Calendars hooks
Source: https://cal.com/docs/atoms/hooks/calendar-hooks
Overview of all the hooks associated with calendars.
### 1. `useConnectedCalendars`
The useConnectedCalendars returns an object containing all connected calendars of a user, and the destination calendar. It is useful in managing multiple calendar integrations. Each connected calendar entry contains essential properties such as the **credentialId**, **externalId** and integration which should be an object, which can be used in the hooks to add and remove selected calendars.
Below code snippet shows how to use the useConnectedCalendars hook to fetch calendars for a user.
```js theme={null}
import { useConnectedCalendars } from "@calcom/atoms";
export default function ConnectedCalendars() {
const { isLoading: isLoadingConnectedCalendars, data: userCalendars } = useConnectedCalendars({});
return (
<>
{isLoadingConnectedCalendars &&
);
})}
>
);
}
```
### 2. `useAddSelectedCalendar`
The useAddSelectedCalendar lets you add a calendar to check for conflicts to prevent double bookings. This hook returns a mutation function that handles the addition process. The mutation function accepts an object with the following properties: ***credentialId*** which is the credential id of the calendar to add, ***integration*** which is the name of the integration, and ***externalId*** which is the external id of the calendar. In order to obtain those credentials, you can use the ***useConnectedCalendars*** hook.
Below code snippet shows how to use the useAddSelectedCalendar hook to add a specific calendar to check for conflicts.
```js theme={null}
import { useAddSelectedCalendar } from "@calcom/atoms";
export default function AddSelectedCalendar() {
const { mutate: addSelectedCalendar } = useAddSelectedCalendar({
onSuccess: () => {
console.log("Selected calendar added successfully!");
},
onError: () => {
console.log("Error adding selected calendar");
},
});
return (
<>
>
);
}
```
### 3. `useRemoveSelectedCalendar`
The useRemoveSelectedCalendar lets you remove a calendar to check for conflicts to prevent double bookings. This hook returns a mutation function that handles the addition process. The mutation function accepts an object with the following properties: ***credentialId*** which is the credential id of the calendar to add, ***integration*** which is the name of the integration, and ***externalId*** which is the external id of the calendar. In order to obtain those credentials, you can use the ***useConnectedCalendars*** hook.
Below code snippet shows how to use the useRemoveSelectedCalendar hook to remove a specific calendar to check for conflicts.
```js theme={null}
import { useRemoveSelectedCalendar } from "@calcom/atoms";
export default function RemoveSelectedCalendar() {
const { mutate: removeSelectedCalendar } = useRemoveSelectedCalendar({
onSuccess: () => {
console.log("Selected calendar removed successfully!");
},
onError: () => {
console.log("Error removing selected calendar");
},
});
return (
<>
>
);
}
```
### 4. `useDeleteCalendarCredentials`
The useDeleteCalendarCredentials hook allows you to delete a users calendar credentials. This hook returns a mutation function which handles the deletion process. The mutation function accepts an object with the following properties: ***id*** which is the credential id, and ***calendar*** is the name of the calendar which can be either google, office365 or apple.
Below code snippet shows how to use the useDeleteCalendarCredentials hook to manage deletion of calendar credentials.
```js theme={null}
import { useDeleteCalendarCredentials } from "@calcom/atoms";
export default function DeleteUserCalendar() {
const { mutate: deleteCalendarCredentials } = useDeleteCalendarCredentials({
onSuccess: () => {
console.log("Calendar credentials deleted successfully!");
},
onError: () => {
console.log("Error deleting calendar credentials");
},
});
return (
<>
>
);
}
```
# Event types hooks
Source: https://cal.com/docs/atoms/hooks/event-types-hooks
Overview of all the hooks associated with event types.
### 1. `useEventTypes`
The useEventTypes returns all event types associated with a user. Simply provide a username to fetch all their available event types. This hook is useful when you need to display or manage a user's entire collection of event configurations.
Below code snippet shows how to use the useEventTypes hook to fetch the event types of a specific user.
```js theme={null}
import { useEventTypes } from "@calcom/atoms";
export default function EventTypes({ username }: { username: string }) {
const { isLoading: isLoadingEvents, data: eventTypes, refetch } = useEventTypes(username);
return (
<>
{isLoadingEvents &&
);
})}
>
);
}
```
### 2. `useTeamEventTypes`
The useTeamEventTypes returns all event types associated with a team. Simply provide the team id to fetch all their available event types. This hook is useful when you need to display or manage a team's entire collection of event configurations.
This hook only works within an organization context. The team must belong to an organization, and the access token passed to `` must be from a managed user within that organization.
Below code snippet shows how to use the useTeamEventTypes hook to fetch the event types of a team.
```js theme={null}
import { useTeamEventTypes } from "@calcom/atoms";
export default function TeamEventTypes({ id }: { id: number }) {
const { isLoading: isLoadingTeamEvents, data: teamEventTypes, refetch: refetchTeamEvents } = useTeamEventTypes(id);
return (
<>
{isLoadingTeamEvents &&
);
})}
>
);
}
```
### 3. `useEventTypeById`
The useEventTypeById returns data for a specific event type, provided you pass in the correct event type id. This hook is useful when you need to display or manage a specific event type.
Below code snippet shows how to use the useEventTypeById hook to fetch a specific event type.
```js theme={null}
import { useEventTypeById } from "@calcom/atoms";
export default function EventType({ id }: { id: number }) {
const { isLoading: isLoadingEventType, data: eventType } = useEventTypeById(id);
return (
<>
{isLoadingEventType &&
Loading...
}
{!isLoadingEventType && !eventType &&
No event type found
}
{!isLoadingEventType &&
!!eventType &&
return (
Title: {eventType.slug}
)
}
>
);
}
```
### 4. `useCreateEventType`
The useCreateEventType hook allows you to create a new event type. This hook returns a mutation function that handles the event type creation process. The mutation function accepts an object with the following properties: ***lengthInMinutes*** which is the length of the event in minutes, ***title*** which is the title of the event, ***slug*** which is the slug of the event, and ***description*** which is the description of the event.
Below code snippet shows how to use the useCreateEventType hook to set up an event type.
```js theme={null}
import { useCreateEventType } from "@calcom/atoms";
export default function CreateEventType() {
const { mutate: createEventType, isPending } = useCreateEventType({
onSuccess: () => {
console.log("Event type created successfully!");
},
onError: () => {
console.log("Error creating event type");
},
});
return (
<>
>
);
}
```
### 5. `useCreateTeamEventType`
The useCreateTeamEventType hook allows you to create a new team event type. This hook returns a mutation function that handles the event type creation process.
This hook only works within an organization context. The team must belong to an organization, and the access token passed to `` must be from a managed user within that organization.
Below code snippet shows how to use the useCreateTeamEventType hook to set up a team event type.
```js theme={null}
import { useCreateTeamEventType } from "@calcom/atoms";
export default function CreateTeamEventType() {
const { mutate: createTeamEventType, isPending } = useCreateTeamEventType({
onSuccess: () => {
console.log("Event type created successfully!");
},
onError: () => {
console.log("Error creating event type");
},
});
return (
<>
>
);
}
```
# Team hooks
Source: https://cal.com/docs/atoms/hooks/team-hooks
Overview of all the hooks associated with teams.
### 1. `useTeams`
The useTeams returns all teams info. This hook is useful when you need to display or manage a user's entire collection of teams.
Below code snippet shows how to use the useTeams hook to fetch team details.
```js theme={null}
import { useTeams } from "@calcom/atoms";
export default function UserTeams() {
const { data: teams, isLoading: isLoadingTeams } = useTeams();
return (
<>
{isLoadingTeams &&
);
})}
>
);
}
```
# User hooks
Source: https://cal.com/docs/atoms/hooks/user-hooks
Overview of all the hooks associated with users.
### 1. `useMe`
The useMe returns the current user's info. This hook is useful when you need to display a user's details.
Below code snippet shows how to use the useMe hook to fetch user details.
```js theme={null}
import { useMe } from "@calcom/atoms";
export default function UserDetails() {
const { data: userData, isLoading: isLoadingUser } = useMe();
return (
<>
{isLoadingUser &&
Loading...
}
{!isLoadingUser && !userData &&
No user found
}
{!isLoadingUser &&
!!userData &&
return (
Username: {userData.username}
)
}
>
);
}
```
# Introduction to Cal.com Atoms
Source: https://cal.com/docs/atoms/introduction
Embed Cal.com scheduling components directly into your application
`@calcom/atoms` is in maintenance mode. Existing integrations keep receiving fixes and new versions on npm, but no new atoms are planned for this package: the next generation of Atoms will be distributed as copy-and-paste components built on coss ui and API v2. New integrations should start from [API v2](/docs/api-reference/v2/introduction).
Cal.com Atoms are customizable React components that let you integrate Cal.com scheduling functionality directly into your application. Using OAuth authentication, your users can connect their Cal.com accounts and manage their scheduling without leaving your app.
## What are atoms?
Atoms are pre-built UI components that handle:
* **Booking** - Let users book appointments with Cal.com users
* **Availability** - Display and manage availability schedules
* **Event Types** - Create and configure event types
* **Calendar Integration** - Connect Google, Outlook, and Apple calendars
* **Payments** - Accept payments via Stripe for bookings
## Supported frameworks
Cal.com Atoms currently support:
* **React 18** and **React 19**
* **Next.js 14** and **Next.js 15**
## Prerequisites
* An OAuth client created via [OAuth setup](/docs/api-reference/v2/oauth)
* User access tokens obtained through the OAuth flow
## Getting started
Ready to integrate atoms into your application? Follow our step-by-step setup guide to get started.
Complete installation and configuration instructions
## Authentication flow
Atoms with OAuth work with regular Cal.com user accounts:
1. User clicks "Connect with Cal.com" in your app
2. User authorizes your OAuth client on Cal.com
3. You receive access and refresh tokens
4. Pass the access token to `CalOAuthProvider`
5. Atoms use this token to make API calls on behalf of the user
See the [OAuth documentation](/docs/api-reference/v2/oauth) for details on implementing the authentication flow.
# List Event Types
Source: https://cal.com/docs/atoms/list-event-types
The List Event Types atom displays all of a user's event types with their title, description, and duration. Each item includes a dropdown menu with edit and delete actions.
## Quick start
Below is a code snippet to render the ListEventTypes atom. The `getEventTypeUrl` prop receives the event type ID and should return the URL where users will be redirected when clicking on an event type.
```tsx theme={null}
import { CalProvider, ListEventTypes } from "@calcom/atoms";
export default function EventTypesPage() {
return (
`/event-types/${eventTypeId}`} />
);
}
```
The `ListEventTypes` atom requires authentication. Make sure it's wrapped in a `CalProvider` with a valid
`accessToken`.
## Props
| Name | Required | Description |
| :-------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| getEventTypeUrl | No | Callback function that receives the event type ID and returns the URL to redirect when clicking on an event type. If not provided, the event type items will not be clickable. |
## Demo
## Combining with EventTypeSettings
The ListEventTypes atom can be combined with the [EventTypeSettings](/docs/atoms/event-type#event-type-settings) atom to provide a complete event type management experience. When a user clicks on an event type from the list, they are redirected to a settings page where they can edit the event type details.
Use the quick start example above to set up a page that displays all event types. The `getEventTypeUrl` prop should return the route where you'll render the EventTypeSettings atom (e.g., `/event-types/${eventTypeId}`).
Set up a dynamic route page (e.g., `/event-types/[eventTypeId]`) that extracts the event type ID from the URL and passes it to the EventTypeSettings atom:
```tsx theme={null}
import { useRouter } from "next/router";
import { CalProvider, EventTypeSettings } from "@calcom/atoms";
export default function EventTypeSettingsPage() {
const router = useRouter();
const eventTypeId = Number(router.query.eventTypeId);
if (!eventTypeId) return null;
return (
{
console.log("Event type updated successfully");
}}
/>
);
}
```
For a complete implementation example, check out the [example
app](https://github.com/calcom/cal.com/tree/main/packages/platform/examples/base).
# List schedules
Source: https://cal.com/docs/atoms/list-schedules
The List schedules atom displays all of a user's availability schedules and provides management actions such as delete, duplicate, and edit capabilities for each schedule.
Below code snippet can be used to render the List schedules atom
```js theme={null}
import { ListSchedules } from "@calcom/atoms";
export default function ListSchedules() {
return (
<>
`/availability/${scheduleId}`} />
>
);
}
```
For a demonstration of the List schedules atom, please refer to the video below.
Below is a list of props that can be passed to the List schedules atom.
| Name | Required | Description |
| :------------- | :------- | :---------------------------------------------------------------------------------------------------------------- |
| getRedirectUrl | No | Callback function that determines where the user should be redirected when they click on a schedule from the atom |
## Combining list schedules atom with availability settings atom
The List schedules atom works best when combined with the Availability settings atom to provide a seamless experience. In order to combine both of atom, here are the steps we recommend:
Create a page for demostrating the list schedules atom.
Below code snippet can be used to setup the list schedules atom on a new page
```js theme={null}
import { ListSchedules } from "@calcom/atoms";
export default function ListSchedules() {
return (
<>
`/availability/${scheduleId}`} />
>
);
}
```
`getRedirectUrl` prop is a callback function that determines where the user should be redirected when they click on a schedule from the atom. Say if you want to display your availability settings atom on the page `/availability/:scheduleId`, you need to make sure you return the exact same url when you call the `getRedirectUrl` function.
Create a page for demonstrating the availability settings atom. This should be a dynamic route that accepts a schedule id as a query parameter, and then the schedule id gets passed to the availability settings atom.
Below code snippet can be used to obtain the `scheduleId` and display the availability settings atom
```js theme={null}
import { AvailabilitySettings } from "@calcom/atoms";
import { useRouter } from "next/router";
export default function Availability() {
const router = useRouter();
return (
<>
{
console.log("Updated schedule successfully");
}}
/>
>
)
}
```
-Below video shows a demonstration of how we combined the list schedules atom and availability settings atom in one of our example app to create a seamless availability management experience.
Link to the example app can be found
[here](https://github.com/calcom/cal.com/tree/main/packages/platform/examples/base)
# Onboarding Embed
Source: https://cal.com/docs/atoms/onboarding-embed
Embed Cal.com account creation, onboarding, and OAuth authorization directly in your app
The `OnboardingEmbed` atom lets you embed the full Cal.com signup, onboarding, and OAuth authorization flow directly inside your application. Users create a Cal.com account, complete onboarding, and grant your app OAuth access — all without leaving your site.
For a demonstration of the onboarding embed flow, please refer to the video below.
## When to use this
Use the Onboarding Embed when you want new users to connect their Cal.com account during your app's own signup or setup flow. Instead of redirecting users to Cal.com and back, the entire process happens in an embedded dialog.
## Prerequisites
* An OAuth client created via [OAuth setup](/docs/api-reference/v2/oauth)
* The `@calcom/atoms` package installed in your project
## Modes
The component supports two modes for receiving the authorization code:
* **Callback mode** — provide `onAuthorizationAllowed` to receive the authorization code via a callback. No page navigation occurs.
* **Redirect mode** — omit `onAuthorizationAllowed` and the browser navigates to your `redirectUri` with the code as a query parameter.
After a new user signs up through the embed, Cal.com sends them a verification email to confirm their email address.
### Callback mode
Provide `onAuthorizationAllowed` to receive the authorization code directly. The dialog closes and your callback fires after the user authorizes your OAuth client — no page reload.
```tsx theme={null}
import { OnboardingEmbed } from "@calcom/atoms";
import { useState } from "react";
function App() {
const [state] = useState(() => crypto.randomUUID());
return (
{
fetch("/api/cal/exchange", {
method: "POST",
body: JSON.stringify({ code, state }),
});
}}
onError={(error) => console.error(error.code, error.message)}
onClose={() => console.log("Dialog dismissed")}
/>
);
}
```
### Redirect mode
Omit `onAuthorizationAllowed` and the browser navigates to your `redirectUri` after the user completes onboarding and grants access:
```
https://your-app.com/cal/callback?code=AUTHORIZATION_CODE&state=YOUR_STATE
```
```tsx theme={null}
import { OnboardingEmbed } from "@calcom/atoms";
import { useState } from "react";
function App() {
const [state] = useState(() => crypto.randomUUID());
return (
console.error(error.code, error.message)}
/>
);
}
```
## Props
| Prop | Type | Required | Description |
| ------------------------ | ------------------------------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `oAuthClientId` | `string` | Yes | Your OAuth client ID. |
| `host` | `string` | No | Cal.com host URL. Defaults to `https://app.cal.com`. Used for local development to point to the Cal web app. |
| `theme` | `"light" \| "dark"` | No | Theme for the embedded onboarding UI. Defaults to `"light"`. |
| `user` | `{ email?: string, name?: string, username?: string }` | No | Prefill user details in signup and profile steps. |
| `authorization` | `AuthorizationProps` | Yes | OAuth authorization parameters (see below). |
| `onAuthorizationAllowed` | `(result: { code: string }) => void` | No | Called with the authorization code on completion. If provided, enables callback mode. If omitted, enables redirect mode. |
| `onError` | `(error: OnboardingError) => void` | No | Called on unrecoverable error. |
| `onAuthorizationDenied` | `() => void` | No | Called when the user declines OAuth authorization. If omitted, the browser navigates to `redirectUri?error=access_denied&state=YOUR_STATE`. |
| `onClose` | `() => void` | No | Called when the user dismisses the dialog before completing. |
| `trigger` | `ReactNode` | No | Custom trigger element. Defaults to a "Continue with Cal.com" button. |
### Authorization props
| Prop | Type | Required | Description |
| --------------- | ---------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `redirectUri` | `string` | Yes | One of the redirect URIs registered on your OAuth client. **Must share the same origin** as the page hosting ``, because the iframe uses `postMessage` for secure communication. |
| `scope` | `string[]` | Yes | OAuth scopes to request. Must be a subset of scopes registered on the OAuth client. See the [OAuth scopes documentation](/docs/api-reference/v2/oauth#available-scopes). |
| `state` | `string` | Yes | CSRF token. Generate a unique value per session and verify it when you receive the authorization code. |
| `codeChallenge` | `string` | For public clients | PKCE code challenge (S256 method). Required for public OAuth clients. |
If the user signs up via Google, the `user` prop values are ignored — name, email, and username are inferred from the Google account instead.
## Trigger and theme
The `theme` prop controls the appearance of the trigger button, the onboarding steps, and the authorization page. The default trigger renders a "Continue with Cal.com" button:
| Light theme (default) | Dark theme |
| --------------------- | -------------- |
| | |
You can pass a custom trigger element via the `trigger` prop:
```tsx theme={null}
Connect calendar}
// ...other props
/>
```
## User flow walkthrough
Here's what happens when a user clicks the trigger with `onAuthorizationAllowed` provided and the `user` prop set:
```tsx theme={null}
{
alert(`Success! Auth code: ${code}`);
}}
/>
```
**1. Trigger** — The component renders a "Continue with Cal.com" button. The user clicks it to open the onboarding dialog.
**2. Login or signup** — The dialog opens with the login form. Existing users can sign in with email or Google. The `user.email` prop prefills the email field.
New users click "Create account" to sign up with Google or email. When signing up with email, the `user.email` and `user.username` props are prefilled.
**3. Profile** — After signup, the user sets up their profile. The `user.name` prop prefills the name field.
**4. Connect calendar** — The user can connect a calendar or skip this step.
**5. Authorize** — The user reviews the requested permissions and clicks "Allow". The displayed permissions correspond to the `scope` passed to the component.
**6. Done** — `onAuthorizationAllowed` fires with the authorization code. Exchange it for tokens using the [token endpoint](/docs/api-reference/v2/oauth#3-exchange-token).
## Public clients (PKCE)
Public OAuth clients cannot safely store a client secret (e.g. browser-only apps). Use PKCE to secure the authorization code exchange instead. Generate a `code_verifier`, derive a `code_challenge` from it, and pass the challenge to `OnboardingEmbed`. When you receive the authorization code, exchange it with the `code_verifier` instead of a client secret.
```tsx theme={null}
import { OnboardingEmbed } from "@calcom/atoms";
import { useEffect, useMemo, useState } from "react";
async function generatePkce() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
const codeVerifier = btoa(String.fromCharCode(...array))
.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
const digest = await crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode(codeVerifier)
);
const codeChallenge = btoa(String.fromCharCode(...new Uint8Array(digest)))
.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
return { codeVerifier, codeChallenge };
}
export function MyApp() {
const state = useMemo(() => crypto.randomUUID(), []);
const [pkce, setPkce] = useState<{
codeVerifier: string;
codeChallenge: string;
} | null>(null);
useEffect(() => {
generatePkce().then(setPkce);
}, []);
if (!pkce) return null;
return (
{
const res = await fetch("https://api.cal.com/v2/auth/oauth2/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
client_id: "your_client_id",
code_verifier: pkce.codeVerifier,
grant_type: "authorization_code",
code,
redirect_uri: "https://your-app.com/cal/callback",
}),
});
const { access_token, refresh_token } = await res.json();
}}
/>
);
}
```
## Error handling
The `onError` callback receives an error object with the following shape:
```ts theme={null}
interface OnboardingError {
code:
| "INVALID_PROPS"
| "SIGNUP_FAILED"
| "ONBOARDING_FAILED"
| "AUTHORIZATION_FAILED"
| "STATE_MISMATCH"
| "UNKNOWN";
message: string;
}
```
| Code | Description |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `INVALID_PROPS` | Required props are missing or invalid (e.g. `oAuthClientId` does not exist, `redirectUri` does not match a registered URI, or required authorization fields are empty). |
| `SIGNUP_FAILED` | Account creation failed. |
| `ONBOARDING_FAILED` | An error occurred during the onboarding steps. |
| `AUTHORIZATION_FAILED` | The user denied access or OAuth consent failed. |
| `STATE_MISMATCH` | The `state` in the response did not match the `state` you provided. Possible CSRF attack. |
| `UNKNOWN` | An unexpected error occurred. |
## How it works
The component opens a dialog containing an iframe that loads Cal.com's onboarding flow. The iframe runs on Cal.com's domain with a first-party session, so no third-party cookies are needed.
The flow automatically detects the user's state:
* **No session** — starts at signup/login, then profile setup, calendar connection, and OAuth consent.
* **Session with incomplete onboarding** — resumes from where the user left off.
* **Session with complete onboarding** — skips straight to OAuth consent.
After the user grants access, you receive an authorization code that you exchange for access and refresh tokens using the [token endpoint](/docs/api-reference/v2/oauth#3-exchange-token).
# Outlook calendar connect
Source: https://cal.com/docs/atoms/outlook-calendar-connect
The Outlook calendar connect button is used to sync a user's outlook calendar - whenever an event is created in cal.com it will show up in the outlook calendar.
Below code snippet can be used to render the Google calendar connect button
```js theme={null}
import { Connect } from "@calcom/atoms";
export default function ConnectCalendar() {
return (
<>
>
)
}
```
For a demonstration of the Google calendar connect integration, please refer to the video below.
Outlook calendar connect supports integration for both single and multiple users. The above video demonstration showcases the integration for a single user. To enable integration for multiple users, simply pass the prop `isMultiCalendar` as `true`. This allows your application to handle multiple Outlook calendar accounts seamlessly, providing a more flexible experience for users who manage several calendars.
Below code snippet can be used to render the Outlook calendar connect button for multiple users
```js theme={null}
import { Connect } from "@calcom/atoms";
export default function ConnectCalendar() {
return (
<>
>
)
}
```
For a demonstration of the Outlook calendar connect integration for multiple users, please refer to the video below.
We offer all kinds of customizations to the Outlook calendar connect via props. Below is a list of props that can be passed to the Google calendar connect.
| Name | Required | Description |
| :-------------------- | :------- | :---------------------------------------------------------------------------------------- |
| className | No | To pass in custom classnames from outside for styling the atom |
| label | No | The label for the connect button |
| alreadyConnectedLabel | No | Label to display when atom is in already connected state |
| loadingLabel | No | Label to display when atom is in loading state |
| onCheckError | No | A callback function to handle errors when checking the connection status |
| redir | No | A custom redirect URL link where the user gets redirected after successful authentication |
| initialData | No | Initial data to be passed |
| isMultiCalendar | No | Specifies if the button supports integration for multiple users |
| tooltip | No | In case user wants to pass external tooltip component |
| tooltipSide | No | Specifies what direction the tooltip appears |
| isClickable | No | Boolean to disable button or not |
| onSuccess | No | A callback function to handle success when checking the connection status |
# Payment form
Source: https://cal.com/docs/atoms/payment-form
The Payment form atom is a streamlined interface that integrates with Stripe to facilitate secure payment processing for bookings. It allows users to easily enter their payment information, ensuring a smooth and efficient transaction experience at the time of booking.
Below code snippet can be used to render the Payment form atom
```js theme={null}
import { PaymentForm } from "@calcom/atoms";
export default function StripePaymentForm(uid: string) {
return (
<>
>
);
}
```
For a demonstration of the Payment form, please refer to the video below.
Below is a list of props that can be passed to the Payment form.
| Name | Required | Description |
| :---------------------------- | :------- | :----------------------------------------------------------------------------------- |
| paymentUid | Yes | The uid of the payment |
| onPaymentSuccess | No | Callback function to be executed upon successful payment processing |
| onPaymentCancellation | No | Callback function to be executed upon payment processing failure |
| onEventTypePaymentInfoSuccess | No | Callback function to be executed when payment information is successfully processed. |
| onEventTypePaymentInfoFailure | No | Callback function to be executed upon payment information processing failure. |
## Combining payment form with event types atom
The Payment form atom works best when combined with the event types atom to provide a seamless payment experience. In order to combine payment and event types atom, here are the steps we recommend:
The event types atom has a payments tab which you can use to connect your stripe account and set up payments info (price, currency, etc.)
Below code snippet can be used to setup the event types atom
```js theme={null}
import { EventTypeSettings } from "@calcom/atoms";
export default function EventType(id: number) {
return (
<>
>
);
}
```
With this you're all set to accept payments for your bookings.
The booker atom has a prop called `onCreateBookingSuccess` which is a callback function that gets called at the time of a successful booking creation. This function takes a parameter called `data` which contains the payment uid and another property called paymentRequired which can be used to detect if the booking requires payment or not. You can use the payment uid to access the payment form atom.
Either store the payment uid in your database, a local state variable or pass it into another page as query parameters.
Below code snippet can be used to obtain the payment uid from the booker atom
```js theme={null}
import { Booker } from "@calcom/atoms";
export default function Booker( props : BookerProps ) {
return (
<>
{
if (data.data.paymentRequired) {
// assuming you have set up a separate page for payment with the same path
// if using nextjs, better to use next/router
window.location.href = `/payment/${data.data.paymentUid}`;
}
}}
/>
>
)
}
```
We recommend setting up a separate page for the payment form for the sake of a smooth and streamlined flow, but you can also do it in the same page.
From the previous step, you get redirected to the payment page when the booking is created. You can now access the payment uid present in the query parameters and render the payment form atom.
Below code snippet can be used to obtain the payment uid and pass it to payment form atom.
```js theme={null}
import { PaymentForm } from "@calcom/atoms";
export default function StripePaymentForm() {
// if using nextjs, better to use the usePathname hook
const pathname = window.location.pathname;
const uid = pathname.split("/").pop();
return (
<>
>
);
}
```
Below video shows a demonstration of how we combined the event types atom and payment form in one of our of our example app to create a seamless payment experience.
Link to the example app can be found [here](https://github.com/calcom/cal.com/tree/main/packages/platform/examples/base)
# Setup
Source: https://cal.com/docs/atoms/setup
Complete setup guide for Cal.com Atoms with OAuth
`@calcom/atoms` is in maintenance mode. Existing integrations keep receiving fixes and new versions on npm, but no new atoms are planned for this package: the next generation of Atoms will be distributed as copy-and-paste components built on coss ui and API v2. New integrations should start from [API v2](/docs/api-reference/v2/introduction).
This guide walks you through setting up Cal.com Atoms in your application using OAuth authentication.
## 1. Create an OAuth client
First, create an OAuth client to enable user authentication:
1. Go to [https://app.cal.com/settings/developer/oauth](https://app.cal.com/settings/developer/oauth)
2. Create a new OAuth client with:
* **Name**: Your application name
* **Redirect URIs**: URLs where users are redirected after authorization (e.g., `https://yourapp.com/callback`)
Your OAuth client will be reviewed by Cal.com. Once approved, you can use it to authenticate users.
See the [OAuth documentation](/docs/api-reference/v2/oauth) for complete details.
## 2. Implement the OAuth flow
When a user wants to connect their Cal.com account:
1. **Redirect to authorization URL**
```js theme={null}
const authUrl = `https://app.cal.com/auth/oauth2/authorize?client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}&state=${STATE}`;
window.location.href = authUrl;
```
2. **Handle the callback**
After authorization, the user is redirected to your callback URL with a `code` parameter.
3. **Exchange code for tokens**
```js theme={null}
const response = await fetch("https://api.cal.com/v2/auth/oauth2/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
grant_type: "authorization_code",
code: authorizationCode,
redirect_uri: REDIRECT_URI,
}),
});
const { access_token, refresh_token, expires_in } = await response.json();
// Store these tokens securely
```
## 3. Set up a token refresh endpoint
Access tokens expire after 30 minutes. Create an endpoint that atoms can call to refresh tokens:
```js theme={null}
// pages/api/refresh.js (Next.js example)
export default async function handler(req, res) {
const expiredToken = req.headers.authorization?.replace("Bearer ", "");
// Look up the refresh token for this user in your database
const refreshToken = await getRefreshTokenForUser(expiredToken);
const response = await fetch("https://api.cal.com/v2/auth/oauth2/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
client_id: process.env.CAL_OAUTH_CLIENT_ID,
client_secret: process.env.CAL_OAUTH_CLIENT_SECRET,
grant_type: "refresh_token",
refresh_token: refreshToken,
}),
});
const tokens = await response.json();
// Store the new tokens in your database
await updateTokensForUser(expiredToken, tokens);
res.json({ accessToken: tokens.access_token });
}
```
## 4. Install the atoms package
```bash theme={null}
npm i @calcom/atoms
```
```bash theme={null}
yarn add @calcom/atoms
```
```bash theme={null}
pnpm add @calcom/atoms
```
## 5. Tailwind version compatibility
If your project utilizes Tailwind CSS, please ensure you import the CSS bundle that corresponds to your specific version. Atoms provides two distinct bundles to maintain compatibility across versions:
* Tailwind CSS v4:
```js theme={null}
import "@calcom/atoms/globals.min.css";
```
* Tailwind CSS v3:
```js theme={null}
import "@calcom/atoms/globals.tw3.min.css";
```
To prevent styling conflicts or unexpected behavior, please only include the import that matches your project's Tailwind version.
## 6. Configure the OAuth provider
For atoms to work, the root of your application **must** be wrapped with `CalOAuthProvider`. This provider handles authentication and token management for all atoms in your app.
See the [CalOAuthProvider documentation](/docs/atoms/cal-oauth-provider) for all available props and advanced configuration.
```js theme={null}
import "@calcom/atoms/globals.min.css";
import { CalOAuthProvider } from "@calcom/atoms";
function MyApp({ Component, pageProps }) {
const { user } = useAuth(); // Your auth context
return (
);
}
export default MyApp;
```
For all provider properties see [Cal OAuth Provider](/docs/atoms/cal-oauth-provider) docs.
## 7. Start using atoms
Now you can use any atom in your components:
```js theme={null}
import { Booker, AvailabilitySettings } from "@calcom/atoms";
export default function SchedulingPage() {
return (
Book a meeting
Manage availability
);
}
```
***
Overview of Cal.com Atoms
Embed booking functionality
# Stripe Connect
Source: https://cal.com/docs/atoms/stripe-connect
The Stripe connect integration allows users to effortlessly connect their Stripe account for payment processing. This enables users to accept payments for their bookings, with automatic payment collection and processing through Stripe's secure platform.
## Ways to connect Stripe
There are two ways to connect your Stripe account:
Use the standalone Stripe Connect button atom to provide a dedicated connection interface. This is ideal when you want to give users a specific place to manage their Stripe connection.
Below code snippet can be used to render the Stripe connect button:
```js theme={null}
import { StripeConnect } from "@calcom/atoms";
export default function ConnectStripe() {
return (
<>
>
);
}
```
For a demonstration of the Stripe connect atom, please refer to the video below.
Use the Event Type Settings component with the payments tab enabled. This approach integrates Stripe connection directly into the event type configuration flow, allowing users to set up payments while configuring their event types.
Below code snippet can be used to render the Event Type Settings with the payments tab:
```js theme={null}
import { EventTypeSettings } from "@calcom/atoms";
export default function EventType() {
// your event type ID
const eventTypeId = 123;
return (
<>
>
);
}
```
For a demonstration of the Stripe connect atom via Event Type Settings, please refer to the video below.
## Advanced usage
### Team integration
To integrate Stripe to team events, pass the `teamId` prop.
Below code snippet can be used to render the Stripe connect atom for team:
```js theme={null}
import { StripeConnect } from "@calcom/atoms";
export default function ConnectTeamStripe() {
const teamId = 123; // Your team ID
return (
<>
>
);
}
```
### Custom labels
You can customize the button labels for different states (default, loading, and already connected):
```js theme={null}
import { StripeConnect } from "@calcom/atoms";
export default function ConnectStripe() {
return (
<>
>
);
}
```
### Redirect URLs
You can specify custom redirect URLs for successful connections and error scenarios:
```js theme={null}
import { StripeConnect } from "@calcom/atoms";
export default function ConnectStripe() {
return (
<>
>
);
}
```
### Handling connection status
You can use callback functions to handle connection check success and errors:
```js theme={null}
import { StripeConnect } from "@calcom/atoms";
export default function ConnectStripe() {
const handleCheckSuccess = () => {
console.log("Stripe connection verified successfully");
};
const handleCheckError = (error) => {
console.error("Error checking Stripe connection:", error);
};
return (
<>
>
);
}
```
### Custom styling
You can customize the appearance of the button using the `className`, `icon`, and `color` props:
```js theme={null}
import { StripeConnect } from "@calcom/atoms";
export default function ConnectStripe() {
return (
<>
>
);
}
```
## Props
We offer all kinds of customizations to the Stripe connect via props. Below is a list of props that can be passed to the Stripe Connect.
| Name | Required | Description |
| :-------------------- | :------- | :---------------------------------------------------------------------------------------- |
| teamId | No | The ID of the team for team-based Stripe connections |
| icon | No | Custom icon to display on the button (defaults to "credit-card") |
| color | No | Button color variant (defaults to "primary") |
| isClickable | No | Boolean to override the disabled state and make the button always clickable |
| className | No | To pass in custom classnames from outside for styling the atom |
| label | No | The label for the connect button |
| alreadyConnectedLabel | No | Label to display when atom is in already connected state |
| loadingLabel | No | Label to display when atom is in loading state |
| onCheckError | No | A callback function to handle errors when checking the connection status |
| redir | No | A custom redirect URL link where the user gets redirected after successful authentication |
| errorRedir | No | A custom redirect URL link where the user gets redirected after authentication failure |
| initialData | No | Initial data to be passed for the connection check |
| onCheckSuccess | No | A callback function to handle success when checking the connection status |
Please ensure all custom classnames are valid Tailwind CSS classnames. Note that sometimes the custom
classnames may fail to override the styling with the classnames that you might have passed via props. That
is because the clsx utility function that we use to override classnames inside our components has some
limitations. A simple get around to solve this issue is to just prefix your classnames with ! property just
before passing in any classname.
## Setting price and currency
Once you have connected your Stripe account, you can set the price and currency for your event type. This will make sure that the booking is a paid event.
For a demonstration on how to set the price and currency, please refer to the video below.
## Integration with Payment Form
The Stripe connect atom works seamlessly with the Payment Form atom. First, connect your Stripe account using this atom, then use the Payment Form atom to process payments for bookings.
For more information on accepting payments and how to combine payment form with Stripe, see the [Payment Form documentation](/docs/platform/atoms/payment-form).
# Troubleshooter
Source: https://cal.com/docs/atoms/troubleshooter
The Troubleshooter atom helps users debug their availability and scheduling issues. It displays a sidebar where users can select an event type, view their schedule, and toggle connected calendars alongside a large calendar view that visually shows the resulting availability. Essentially, it lets users diagnose why certain time slots may or may not appear as available.
Below code snippet can be used to render the Troubleshooter atom
```js theme={null}
import { TroubleShooter } from "@calcom/atoms";
export default function TroubleShooterAtom() {
return (
<>
>
);
}
```
For a demonstration of the Troubleshooter atom, please refer to the video below.
We offer some customizations to the Troubleshooter atom via props. Below is a list of props that can be passed to the Troubleshooter atom.
| Name | Required | Description |
| :--------------------- | :------- | :------------------------------------------------------------------------------------------------------- |
| onManageCalendarsClick | No | Callback triggered when the "Manage Calendars" button is clicked (shown when calendars are connected) |
| onInstallCalendarClick | No | Callback triggered when the "Install Calendar" button is clicked (shown when no calendars are connected) |
# How to Set Up the API in a Local Instance
Source: https://cal.com/docs/developing/guides/api/how-to-setup-api-in-a-local-instance
To test the API in your local instance, you have the following pre-requisites:
Please clone the cal.com repository. You can do it by following the instructions provided in [Installation](/docs/developing/local-development)
Add a staging license key that goes in as value for `CALCOM_LICENSE_KEY` in your root `.env` file.
You can use the following as staging license key
```
cal_live_QiMeiCoFjEczVQmY6EJTeiJV
```
Start the cal.com server using `yarn dev` on localhost and create the test API keys by visiting `/settings/developer/api-keys`
Copy the `.env.example` file to `.env` file by running `cp apps/api/v2/.env.example apps/api/v2/.env` from the root folder
Start the API v2 server by running `yarn workspace @calcom/api-v2 dev` and start testing your API locally
By default, the app server runs on port 3000 and the API server runs on port 3003
# Build a greeter app
Source: https://cal.com/docs/developing/guides/appstore-and-integration/build-a-greeter-app
## Building a **Greeter** app
Create an app with the title "**Greeter".** Run the following command and provide the information it is looking for.
```bash theme={null}
yarn app-store create
```
The app is created in Step 1 and we should install it now.
The app is installed but it doesn't do anything because we haven't written added any functionality to it. Let's add a button in the main navigation that greets the user.
1. Create a component `greeter/components/GreeterButton.tsx` - You can name it whatever you want.
2. Import this component in `Shell.tsx` and add it wherever you want to so that the button is available on all pages.
```js theme={null}
/**
* GreeterButton.tsx
* It creates a button that can be added anywhere. The button is visible only if the app is installed.
*/
import useApp from "@calcom/lib/hooks/useApp";
import showToast from "@calcom/lib/notification";
import { Button } from "@calcom/ui/button/Button";
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
export default function GreeterButton() {
const { data: user } = useMeQuery();
const { data: greeterApp } = useApp("greeter");
// Make sure that greeterApp is installed. We shouldn't show the button when app is not installed
if (!user || !greeterApp) {
return null;
}
return (
);
}
```
```; theme={null}
/**
* Shell.tsx
*/
// ...
import GreeterButton from "@calcom/app-store/greeter/components/GreeterButton";
// ...
;
```
A sample line where I used the component in the demo
That's it. You now have a fully functional Greeter app. This is the simplest possible demonstration of how you can build an app and what it can do. There are simply no restrictions on what an app can achieve.
# How to build an app
Source: https://cal.com/docs/developing/guides/appstore-and-integration/build-an-app
Since Cal.com is open source we encourage developers to create new apps for others to use. This guide is to help you get started.
You can create an app by simply running the following command. It creates a new directory under [`packages/app-store/`](https://github.com/calcom/cal.com/tree/main/packages/app-store)\`\`
```bash theme={null}
yarn create-app
```
You just need to provide a few inputs and then you will have your app created within a few seconds. [See, how you can build a very simple app that greets your users.](/docs/developing/guides/appstore-and-integration/build-a-greeter-app)
### Steps in creating an app
Create the app using the `create-app` command. The app can be installed now, but at this moment it isn't doing anything. It is just installed and ready to do what you want it to do.
There is pretty much no restriction on what an App can do. For example:
* Hooking into existing functionalities to enhance them - *In all these cases you need to first check if the app is installed or not. There is* `_useApp(appSlug)_` *react hook to do that. It gives you the app details if the app is installed.*
* Apps that need to connect with third parties like Google Calendar, Apple Calendar, Google Meet.
* A very powerful app that has its own data and pages - [Routing Forms App](https://github.com/calcom/cal.com/tree/main/packages/app-store/ee/routing_forms) is a great example of this.
* There are other powerful apps already in the [App Store](https://app.cal.com/apps) that you can get an idea of the capabilities of apps.
### Structure
All apps can be found under [`packages/app-store`](https://github.com/calcom/cal.com/tree/main/packages/app-store). In this folder is `_baseApp` which shows the general structure of an app.
```bash theme={null}
├──_example
| ├──config.json -> Autogenerated by cli. You are free to edit it.
| ├──api
| | ├──add.ts -> Autogenerated by cli. You would want to edit it if your app needs to connect with a third party
|
| ├──components
|
| ├──static -> Add all static assets here
| | ├──icon.svg -> This is used as the logo for your App.
| ├──index.ts
| ├──package.json
| ├──.env.example -> Specify the environmental variables (ex. auth token, API secrets) that your app will need if it's applicable
| ├──README.mdx -> Customize your app description. You can add an image slider as well.
```
### Other Commands
**Deleting an app:**
```bash theme={null}
yarn app-store delete --slug APP_NAME
```
**Editing an app:**
```bash theme={null}
yarn app-store edit --slug APP_NAME
```
### Important Points
* Make sure to have `yarn app-store:watch` command running when developing an app so that autogenerated files are always up to date.
* If app-store cli fails at this step, try to run this command manually first. Solve that problem and then re-run cli. The command can fail because there are Prisma migrations that can't be applied automatically.
A sample failure in create command
* When you edit an app following things aren't updated.
* README.mdx - It would still have the old description. Feel free to edit it manually.
### Publishing Your App to the Cal.com App Store
Once your app is working in your environment, you can publish it to Cal.com App Store by opening a PR [here](https://github.com/calcom/cal.com).
If you need any help feel free to join us in our [GitHub Discussions](https://github.com/calcom/cal.com/discussions).
# Assign people to a call from a CRM or database
Source: https://cal.com/docs/developing/guides/appstore-and-integration/how-to-show-assigned-people-from-a-crm
## Option 1: Based on CRM integration
If you are working with a CRM such as Salesforce, Hubspot, Close.com or similar, you can use our integrations to connect to your apps.
We're using the Assignee APIs from each service to understand which person is assigned to an email of a new lead.
By prefilling your booking link with ?email=[name@acme.com](mailto:name@acme.com) our integration will look up who has spoken to [name@acme.com](mailto:name@acme.com) before and skip all round-robin logic.
Here is an example URL: i.cal.com/sales/exploration?email=[name@acme.com](mailto:name@acme.com)
This works with Embeds as well, just add the parameter to your CalLink property.
## Option 2: Based on Emails
If you are using a different product that we don't integrate with, or you need something more custom you can also achieve this with our Organization plan.
We've built our Organization system in a way where every email can be turned into a scheduling link with your company subdomain:
[jane@acme.com](mailto:jane@acme.com) → acme.cal.com/jane (or schedule.acme.com/jane)
This means if you have a table
| User | AccountExecutive |
| :---------------------------------- | :------------------------------------ |
| [peer@cal.com](mailto:peer@cal.com) | [jane@acme.com](mailto:jane@acme.com) |
You could show [peer@cal.com](mailto:peer@cal.com) an embed inside your application simply by using the value from AccountExecutive to build the link:
[jane@acme.com](mailto:jane@acme.com) → acme.cal.com/jane either with a string replace function or a simple regex.
# Salesforce integration
Source: https://cal.com/docs/developing/guides/appstore-and-integration/salesforce
Configure how Cal.com syncs booking data with Salesforce contacts, leads, and events.
The Salesforce integration lets you automatically sync booking data to your Salesforce CRM. When connected, Cal.com creates Salesforce events linked to contacts or leads, and can write custom field values at different stages of the booking lifecycle.
## Prerequisites
* A Salesforce account with API access
* The Salesforce app installed and connected in your Cal.com instance
## Event type settings
After connecting Salesforce, you can configure per-event-type settings by navigating to the event type's **Apps** tab and expanding the Salesforce section.
### Attendee record type
Choose how Cal.com identifies attendees in Salesforce:
* **Contact** — Look up or create a Contact record for the attendee
* **Lead** — Look up or create a Lead record for the attendee
* **Contact under Account** — Look up the Contact under their Account
### Write to event record on booking
Enable **On booking, write to the event object** to set custom field values on the Salesforce event whenever a booking is created. Add field name and value pairs to configure which event fields are updated.
### Write to contact or lead record on booking
Enable **On booking, write to a custom field on the attendee record** to update fields on the attendee's Contact or Lead record whenever a booking is created. For each field you configure, you can set:
* **Field name** — The Salesforce API name of the field to write to
* **Field type** — The data type (text, date, datetime, phone, checkbox, picklist, textarea, or custom)
* **Value** — The value to write. Date fields support dynamic values like `booking_start_date`, `booking_created_date`, or `booking_cancel_date`. Text values can also embed booking context tokens — see [Context tokens in field values](#context-tokens-in-field-values)
* **When to write** — Choose between writing on every booking or only when the field is empty
### Write to event record on cancellation
Enable **On cancelled booking, write to event record instead of deleting event** to preserve the Salesforce event when a booking is cancelled. Instead of deleting the event, Cal.com writes your configured field values to it. This is useful when you want to keep a record of cancelled meetings in Salesforce.
For each field, configure the field name, type, value, and write condition, the same as for booking-time writes.
When this option is disabled, Cal.com deletes the Salesforce event on cancellation.
### Write to contact or lead record on cancellation
Enable **On cancelled booking, write to a custom field on the attendee record** to update fields on the attendee's Contact or Lead record when a booking is cancelled. This lets you track cancellation status directly on the person record in Salesforce.
Cal.com identifies the correct contact or lead using the Salesforce event's `WhoId` field, which links back to the person originally assigned when the booking was created.
This feature works alongside the "write to record on booking" option. You can configure both independently — for example, writing a booking date on creation and a cancellation status on cancellation.
#### Enable write-to-record on cancellation
Go to **Event Types**, select the event type you want to configure, and open the **Apps** tab.
Find the Salesforce app card and expand its settings.
Turn on **On cancelled booking, write to a custom field on the attendee contact/lead record**.
Add one or more field mappings. For each field, specify:
* **Field name** — The Salesforce API name of the field on the contact or lead object (for example, `Last_Booking_Status__c`)
* **Field type** — The data type (text, date, datetime, phone, checkbox, picklist, textarea, or custom)
* **Value** — The value to write when a booking is cancelled. For date fields, you can use dynamic values like `booking_cancel_date` to automatically populate the cancellation date
* **When to write** — Choose between writing on every cancellation or only when the field is empty
Save your event type. The configured fields update on the attendee's Salesforce record whenever a booking of this type is cancelled.
#### Example: tracking cancellation status and date
A sales team wants to track when prospects cancel discovery calls:
1. In Salesforce, add custom fields to the Contact object (e.g., `Last_Booking_Status__c` and `Last_Cancel_Date__c`)
2. In Cal.com, open the "Discovery Call" event type's Salesforce settings
3. Enable **On cancelled booking, write to a custom field on the attendee contact/lead record**
4. Add a first field mapping:
* Field name: `Last_Booking_Status__c`
* Field type: Text
* Value: `Cancelled`
* When to write: Every cancellation
5. Add a second field mapping:
* Field name: `Last_Cancel_Date__c`
* Field type: Date
* Value: `booking_cancel_date`
* When to write: Every cancellation
When a prospect cancels, their Salesforce contact record automatically updates with the cancellation status and date.
### Create records on lead or contact owner fallback
When a round-robin event type is configured to **Book directly with attendee owner**, Cal.com looks up the matched Contact, Lead, or Account owner and routes the booking to that host. If no owner is found, the fallback path runs and standard round-robin assignment takes over. You can also configure Cal.com to **create Salesforce records on this fallback**, so a new Lead, Contact, or custom object is created for the attendee whenever the owner lookup misses.
Use this when you want every booking to leave a paper trail in Salesforce — even when the attendee has no existing owner mapping — without needing your reps to create records manually after the fact.
Navigate to the event type's **Apps** tab and expand the Salesforce section.
Turn on **On lead or contact owner fallback, create records in Salesforce**.
For each record you want created, select the Salesforce object (for example, Lead, Contact, or a custom object) and configure the field mappings that populate it. You can add multiple records — for example, create a Lead and a related custom object in a single fallback.
For each field on the fallback record, specify:
* **Field name** — The Salesforce API name of the field
* **Field type** — The data type. In addition to the standard types (text, date, datetime, phone, checkbox, picklist, textarea, custom), fallback records support **Account lookup** for fields that reference the resolved Salesforce Account
* **Value** — A literal value, a dynamic booking value, or a string with [context tokens](#context-tokens-in-field-values). For **Account lookup** fields, Cal.com writes the id of the Account resolved from the attendee's email domain
Enable **Skip if booking with account owner** on any record to suppress creation when the resolved host is already the account owner. This avoids duplicate records for bookings that were routed to the "right" rep to begin with.
Save your event type. On future bookings that fall through owner lookup, Cal.com creates each configured record and links Account lookup fields to the matched Account.
If Salesforce returns an error while resolving the Account or its ownership, Cal.com treats the booking as "not owned by the host" and continues creating the fallback records. Salesforce failures never block booking creation.
### Context tokens in field values
Text-based field values in write-to-record and fallback-record configurations can embed context tokens that Cal.com replaces at booking time. Tokens use curly braces and can appear anywhere in the string, including alongside literal text.
| Token | Replaced with |
| --------------------- | ------------------------------------------------------ |
| `{account_id}` | Salesforce id of the Account resolved for the attendee |
| `{organizer_user_id}` | Cal.com user id of the booking's organizer (host) |
| `{booking_uid}` | Cal.com booking uid |
```text Value examples theme={null}
Booked via Cal.com — organizer {organizer_user_id}
https://app.cal.com/booking/{booking_uid}
Account: {account_id} / Rep: {organizer_user_id}
```
Values that contain only a brace-only literal (for example, `{}`) are treated as static text. Any other token — including hyphenated identifiers from routing form questions such as `{my-question}` — is resolved at booking time.
### Record type filtering
Salesforce objects can have multiple Record Types — for example, Contacts may include both "Business Contact" and "Person Account" types. You can exclude specific Record Types so that Cal.com only looks up or creates records of the types you want.
Navigate to the event type's **Apps** tab and expand the Salesforce section.
Below the attendee record type selector, look for the **Exclude Record Types** setting.
Cal.com fetches the available Record Types from your Salesforce org. Select any Record Types you want to skip during contact or lead lookups. Records matching excluded types are filtered out of query results.
Save your event type. Future bookings will only match records whose Record Type is not in the exclusion list.
Record type matching is case-insensitive. This setting applies to both lookups and record creation — Cal.com will not create records with an excluded Record Type.
### Field mapping validation
Cal.com validates your field mappings when you save event type settings, catching configuration errors before they affect live bookings. Validation checks include:
* **Checkbox fields** must map to a boolean value
* **Date fields** must reference a valid date source (for example, `booking_start_date`, `booking_created_date`, or `booking_cancel_date`)
* **Text, phone, picklist, and custom fields** must have a non-empty string value
If a mapping is invalid, an inline error appears next to the field so you can correct it before saving.
### Sync error notifications
When a Salesforce sync fails at booking time — for example, due to a permission error or an invalid field — Cal.com stores the error and displays a diagnostic notification on the Salesforce settings tab. The notification includes:
* The error code and message
* Which fields were dropped from the sync
* A timestamp of when the error occurred
This helps you identify and fix integration issues without needing to check Salesforce logs.
### Fuzzy domain matching
By default, Cal.com matches attendee email domains to Salesforce Account `Website` fields using exact domain matching. When fuzzy domain matching is enabled, Cal.com also matches across top-level domain variants — for example, an attendee with an `@acme.com` email will match a Salesforce Account whose website is `acme.co.uk`.
This is useful when your Salesforce Accounts use regional domains (`.co.uk`, `.de`, `.com.au`) but your contacts book meetings using a different TLD.
Fuzzy domain matching is a per-credential toggle — enable it in your Salesforce connection settings, and it applies to all event types using that credential.
The fuzzy matching resolution follows a waterfall: exact domain match first, then normalized website comparison, then contact email domain lookup. This ensures precise matches are always preferred over fuzzy ones.
### Show CRM contact owner banner on booking page
Enable **Show banner with CRM contact owner on booking page** to display a banner on the booking page identifying the Salesforce contact owner assigned to the attendee. Use this to reassure attendees that they are booking with the account owner already on file, or to make internal handoffs more transparent.
The banner only appears when Cal.com resolves a Salesforce contact owner for the attendee — for example, on round-robin event types where **Book directly with attendee owner** matches a Contact, Lead, or Account owner. If no owner is resolved, no banner is shown.
You can also opt into the banner from a routed URL by appending `cal.showCrmOwnerBanner=true` to the booking link, which is useful when driving traffic from a routing form or external system that already knows the attendee's CRM owner.
### Additional settings
| Setting | Description |
| ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
| **Ignore guests** | Only process the primary attendee, not additional guests |
| **Skip contact creation** | Do not create a new Contact if one doesn't exist (Contact mode only) |
| **Check for existing contact** | When using Lead mode, also check if a Contact exists before creating a Lead |
| **Create contact under account** | When creating contacts, link them to the matching Account |
| **Change record owner on booking** | Reassign the Contact or Lead owner to a specified user when a booking is made |
| **Send no-show attendee data** | Write attendee no-show status to a field on the Salesforce event object |
| **Book directly with attendee owner** | For round-robin event types, skip round-robin logic and route directly to the Salesforce record owner |
| **Show banner with CRM contact owner on booking page** | Show a banner on the booking page identifying the resolved Salesforce contact owner |
## Routing form options
When you add a route to a Cal.com routing form, the Salesforce app exposes routing options that decide which host receives the booking based on Salesforce data. Configure them from the route's settings panel under the Salesforce section.
### Route to a user that matches a lookup field on an account
Use this option when the correct host is stored as a user reference on the booker's Salesforce Account (or on a related object), rather than as the Account owner. Cal.com resolves the Account for the booker, reads the configured user lookup field, and routes to the matching Cal.com user.
Enter the Salesforce API name of the user lookup field in **Lookup field name**. Two formats are supported:
* A field directly on the Account, for example `Owner_Lookup__c`
* A dot-notation path that traverses a relationship on the Account to reach a user lookup field on a connected object, for example `Region__r.Owner__c`
Dot-notation lets you point at a user field that lives on another object linked to the Account — for example a regional territory record with its own owner — without duplicating that field onto the Account itself. Salesforce resolves the relationship server-side, so any depth supported by SOQL relationship queries works.
```text Lookup field name examples theme={null}
Owner_Lookup__c
Region__r.Owner__c
Territory__r.Regional_Manager__c
```
The lookup field name must contain only letters, numbers, and underscores, with dots separating segments for connected objects. Invalid names are flagged inline in the routing form builder. At routing time, an unrecognized field or relationship causes Cal.com to fall through to the form's fallback route.
## Routing trace on booking details
When a booking is routed through a CRM integration such as Salesforce, Cal.com records a routing trace that shows how the booking was assigned. You can view this trace directly on the booking detail page and in the booking list actions dropdown.
Previously, routing trace information was only visible for bookings created through a routing form. Now, any booking that has a routing trace — including direct bookings routed via CRM owner assignment — displays the trace in the UI.
### What the routing trace shows
The routing trace includes information about how and why a booking was assigned to a specific host. For CRM-routed bookings, this typically reflects the Salesforce record owner assignment or account resolution logic that determined the host.
### Where to find it
* **Booking list** — Open the actions dropdown on any booking row. If the booking has a routing trace, you see an option to view it.
* **Booking detail page** — A routing trace badge appears on the detail page for bookings that were routed through CRM, even if no routing form was used.
Routing trace is only available for bookings that have trace data recorded by Cal.com. Bookings created without CRM routing or a routing form do not show a trace.
## Cross-TLD fuzzy domain matching
When using the **Contact under Account** attendee record type, Cal.com resolves the attendee's Salesforce Account by matching their email domain against Account `Website` fields. By default, this lookup requires an exact domain match — `acme.com` only matches Accounts whose website is on `acme.com`.
With **cross-TLD fuzzy domain matching** enabled, Cal.com also matches across different top-level domains. An attendee with an `@acme.co.uk` email can be matched to a Salesforce Account whose website is `acme.com`, `acme.de`, or any other TLD variant sharing the same base domain.
### How account resolution works
Cal.com resolves the attendee's Salesforce Account using a multi-step waterfall:
1. **Exact match** — Look for an Account whose `Website` exactly matches the attendee's email domain (for example, `acme.com`)
2. **Normalized match** — Strip URL prefixes like `www.`, `http://`, and trailing paths, then retry the exact match
3. **Contact email match** — Search for existing Contacts whose email domain matches, and use their linked Account
4. **Fuzzy cross-TLD match** — Extract the base domain (for example, `acme` from `acme.co.uk`) and match it against all Account websites regardless of TLD
Cal.com uses the first match found. If multiple Accounts match at any step, the [tiebreaker waterfall](#tiebreaker-waterfall) runs to select the best candidate. If no Account is resolved at any step, the integration falls back to the behavior configured in your event type settings.
### Tiebreaker waterfall
When multiple Salesforce Accounts match a domain during account resolution, Cal.com runs a tiebreaker waterfall to select the best candidate. This is especially relevant for round-robin event types where the resolved Account owner determines which host receives the booking.
The tiebreaker evaluates candidates in priority order. The first criterion that produces a single winner ends the waterfall.
#### Default tiebreaker rules
The following rules are available. By default, they run in the order shown:
| Priority | Criterion | Category | Description |
| -------- | -------------------- | ------------ | ----------------------------------------------------------------------------------------------------------- |
| P1 | Country match | Geo | Account whose `BillingCountry` matches the booker's country |
| P2 | State/region match | Geo | Account whose `BillingState` matches the booker's state or region |
| P3 | Sub-region match | Geo | Account whose configured sub-region field matches the booker's location (optional, requires a custom field) |
| P4 | *(Reserved)* | — | — |
| P5 | Most child accounts | Relationship | Account with the highest number of child Accounts in its hierarchy |
| P6 | Most opportunities | Relationship | Account with the highest number of related Opportunities |
| P7 | Most contacts | Relationship | Account with the most related Contact records |
| P8 | Most recent activity | Activity | Account with the most recent `LastActivityDate` |
| P9 | Earliest created | Activity | Account with the oldest `CreatedDate` (longest-standing relationship) |
Priorities P1–P3 are **geo tiebreakers** — they use the booker's geographic location (derived from their IP address) to prefer Accounts in the same region. If geo data is unavailable or no geo criterion produces a winner, the waterfall continues to P5.
If all criteria result in a tie, the last-resort rule (P9) selects the Account that was created first.
#### Configuring tiebreaker rules
You can customize which tiebreaker rules are active and their priority order from the event type settings. This lets you tailor account resolution to match your team's routing strategy — for example, prioritizing opportunity count over geographic proximity, or removing rules that are not relevant to your Salesforce data.
To configure tiebreaker rules, navigate to the event type's **Apps** tab, expand the Salesforce section, and find the **Tiebreaker rules** setting.
Toggle the tiebreaker waterfall **on**. When disabled, Cal.com does not run tiebreaker logic and selects a matching Account arbitrarily.
The rule list shows each active tiebreaker rule with its priority number, description, and category (Geo, Relationship, or Activity). Rules run in the order they appear, from top to bottom.
Use the arrow buttons to move a rule up or down in the priority list. Rules higher in the list are evaluated first. Move your most important criteria to the top.
Click the delete icon on any rule to remove it from the waterfall. Removed rules no longer participate in account resolution.
Use the **Add rule** dropdown to add a previously removed rule back to the waterfall. The dropdown shows each rule's description so you can make an informed selection.
Save your event type to apply the updated tiebreaker configuration. The customized order applies to all future bookings for this event type.
The tiebreaker configuration is per event type. Each event type can have its own set of active rules and ordering. If you remove all rules or disable the tiebreaker waterfall, Cal.com skips tiebreaker logic entirely and uses the first Account match from the resolution waterfall.
#### Geo tiebreakers
The geo-tiebreaker rules in the Salesforce CRM integration only apply when a booking comes through a routing form. Direct bookings skip them entirely.
Geo tiebreakers route bookings to the Salesforce Account that is geographically closest to the person booking. Cal.com reads the booker's country and region from IP-based geo-location headers and compares them against the Account's `BillingCountry` and `BillingState` fields in Salesforce.
You can configure them in the Salesforce section of your event type or routing form settings. When enabled, the tiebreaker waterfall checks geography before falling back to the standard relationship-based criteria (child accounts, opportunities, contacts, etc.).
##### Default geo context
When no explicit geo tiebreaker configuration is set, Cal.com automatically extracts the booker's geographic location from IP-based request headers. The fallback is for country and state fields: when a routing form field for country/state is present but left blank, Cal.com falls back to the booker's IP geolocation header. However, this fallback still requires the booking to come through a routing form — it doesn't enable geo rules for direct bookings.
##### When to use geo tiebreakers
Geo tiebreakers are useful when:
* Your team has regional Account owners and you want bookings routed to the owner nearest the booker
* You have multiple Accounts for the same company across different regions (for example, "Acme US" and "Acme EMEA")
* You want to improve lead-to-rep matching by factoring in geographic proximity
##### Sub-region matching
For more granular geographic routing, you can specify a custom Salesforce field for sub-region matching. This field is checked at P3 in the waterfall, after country and state matching. Use this when your Accounts are segmented by territories smaller than a state — for example, a custom `Sales_Territory__c` field.
Geo tiebreakers require the booker's IP geo-location data, which is derived from request headers set by your infrastructure (for example, Cloudflare or Vercel geo headers). If geo data is not available, the geo steps are skipped and the waterfall starts at P5.
#### Host filtering
For round-robin event types, candidates are filtered before the tiebreaker runs. Only Account owners who are hosts on the event type are eligible. Host filtering applies across all routing paths, including when Cal.com falls back from a GraphQL query to a SOQL-based lookup. If this filter removes all candidates, Cal.com falls back to standard round-robin assignment.
### Record type filtering
You can exclude specific Salesforce record types from account resolution. When record type filtering is enabled, Accounts with excluded record types are skipped during the resolution waterfall. This prevents Cal.com from matching against Accounts that are not relevant to booking routing, such as partner or vendor accounts.
Configure record type filtering in the Salesforce section of your event type's **Apps** tab.
### When to use fuzzy matching
Fuzzy domain matching is useful when:
* Your Salesforce Accounts have websites on a single TLD (for example, `acme.com`) but attendees book from regional email domains (for example, `@acme.co.uk`, `@acme.de`)
* You want to automatically link international attendees to the correct parent Account without creating duplicate records
* Your organization operates across multiple country domains under the same brand
Fuzzy matching only applies to the **Contact under Account** attendee record type. It does not affect Contact-only or Lead-only lookups.
## Field mapping validation
Cal.com validates your Salesforce field mappings when you save event type settings. If a field mapping has a type mismatch — for example, a non-boolean value mapped to a checkbox field, or an empty value for a required text field — Cal.com displays an inline error and prevents the save.
If a sync error occurs at runtime (for example, a field was deleted in Salesforce after configuration), Cal.com stores the error and displays a diagnostic notification on the Salesforce settings tab. The notification includes the error code, message, any dropped fields, and a timestamp to help you troubleshoot.
### Validated field types
| Field type | Validation rule |
| ------------------------------- | ---------------------------------------------------------------------- |
| Checkbox | Value must be a boolean (`true` or `false`) |
| Date | Must reference a valid dynamic date value (e.g., `booking_start_date`) |
| Text, phone, picklist, textarea | Value must be a non-empty string |
| Custom | No automatic validation — values are passed through as-is |
# Add changesets
Source: https://cal.com/docs/developing/guides/atoms/add-changesets
We use changesets for every PR that affects atoms, be it a fix or feature to add a log describing the fix or feature. Below video showcases how to add a changeset to a PR.
# Audit logs
Source: https://cal.com/docs/developing/guides/audit-logs
Track security and access control events across your organization
Audit logs record security-sensitive actions performed within your Cal.com organization. Each event captures who performed the action, what changed, and when it happened. Use audit logs to monitor access control changes, investigate security incidents, and satisfy compliance requirements.
Audit logs are an enterprise feature available to organizations on Cal.com. Events are recorded automatically — no configuration is required.
## Tracked events
Cal.com automatically records audit events across the following categories: security, access control, API credential lifecycle, workflow automation, billing, and event type management.
### Security events
| Event | Description |
| -------------------------- | ----------------------------------------------------------------------------- |
| `LOGIN` | A user logged in. Failed login attempts are recorded with a `FAILURE` result. |
| `SSO_LOGIN` | A user authenticated via SSO (SAML or OIDC). |
| `PASSWORD_CHANGED` | A user changed their password. |
| `PASSWORD_RESET_REQUESTED` | A password reset was initiated. |
| `TWO_FACTOR_ENABLED` | Two-factor authentication was turned on. |
| `TWO_FACTOR_DISABLED` | Two-factor authentication was turned off. |
| `IMPERSONATION_START` | An admin began impersonating another user. |
| `IMPERSONATION_STOP` | An admin stopped impersonating another user. |
| `EMAIL_CHANGED` | A user's email address was updated. |
| `ACCOUNT_LOCKED` | A user account was locked. |
| `ACCOUNT_UNLOCKED` | A user account was unlocked. |
### Access control events
These events are recorded when organization or team memberships change.
| Event | Description | Data captured |
| ----------------- | ----------------------------------------------------- | ------------------------------ |
| `MEMBER_ADDED` | A user was invited to join a team or organization. | Invited user and assigned role |
| `MEMBER_REMOVED` | A user was removed from a team or organization. | Removed member and team IDs |
| `ROLE_CHANGED` | A member's role was updated. | Previous role and new role |
| `INVITATION_SENT` | An invitation email was sent to a prospective member. | Invitee email and target team |
| `TEAM_CREATED` | A new team was created within an organization. | Team ID and creator |
| `TEAM_DELETED` | An existing team was deleted. | Team ID |
### API credential events
| Event | Description |
| ----------------- | -------------------------------- |
| `API_KEY_CREATED` | A new API key was generated. |
| `API_KEY_REVOKED` | An existing API key was revoked. |
### Workflow events
| Event | Description |
| ------------------- | --------------------------------- |
| `WORKFLOW_CREATED` | A new workflow was created. |
| `WORKFLOW_MODIFIED` | An existing workflow was updated. |
| `WORKFLOW_DELETED` | A workflow was deleted. |
### Billing events
These events are recorded when subscription or seat changes occur.
| Event | Description |
| ------------------------ | ---------------------------------------------- |
| `PLAN_UPGRADED` | A subscription was upgraded to a higher tier. |
| `PLAN_DOWNGRADED` | A subscription was downgraded to a lower tier. |
| `SUBSCRIPTION_CANCELLED` | A subscription was cancelled. |
| `SEAT_ADDED` | A new seat was added to the subscription. |
### Event type events
| Event | Description |
| --------------------- | ----------------------------------- |
| `EVENT_TYPE_CREATED` | A new event type was created. |
| `EVENT_TYPE_MODIFIED` | An existing event type was updated. |
| `EVENT_TYPE_DELETED` | An event type was deleted. |
## Event structure
Each audit event captures a consistent set of fields:
| Field | Description |
| ------------------------ | ------------------------------------------------------------------------------------------------------------ |
| **Action** | The type of event (e.g., `ROLE_CHANGED`, `LOGIN`). |
| **Actor** | The user who performed the action. |
| **Result** | Whether the action succeeded (`SUCCESS`), failed (`FAILURE`), or was denied (`DENIED`). |
| **Source** | Where the action originated (e.g., webapp, API v1, API v2, SAML, OAuth). |
| **Target** | The resource affected by the action, identified by type and ID (e.g., a specific user, team, or membership). |
| **Previous / new value** | For mutation events like `ROLE_CHANGED`, the value before and after the change. |
| **Timestamp** | When the event occurred. |
IP addresses are stored as HMAC hashes for privacy. Raw IP addresses are never persisted in audit logs.
## Access control event examples
### Role change
When an admin changes a team member's role, the audit log captures both the previous and new role values:
* **Action**: `ROLE_CHANGED`
* **Target type**: `membership`
* **Previous value**: `MEMBER`
* **New value**: `ADMIN`
### Member removal
When members are removed from a team, an audit event is recorded for each member and team combination:
* **Action**: `MEMBER_REMOVED`
* **Target type**: `membership`
* **Target ID**: The affected member's ID
### Member invitation
When new members are invited, the audit log records the invitation details:
* **Action**: `MEMBER_ADDED`
* **Target type**: `membership`
## PBAC permissions for audit logs
If your organization uses [PBAC (Permission-Based Access Control)](/docs/api-reference/v2/access-control#pbac-permission-based-access-control), audit log access is governed by dedicated permissions:
| Permission | Scope |
| --------------------------- | ---------------------------------------------- |
| `booking.readOrgAuditLogs` | View audit logs across the entire organization |
| `booking.readTeamAuditLogs` | View audit logs for a specific team |
Assign these permissions to custom roles to control who can access audit log data.
# Setting up OIDC with okta
Source: https://cal.com/docs/developing/guides/auth-and-provision/how-to-setup-oidc-with-okta
For example, in Okta, once you create an account, you can click on Applications on the sidebar menu:
Enter the Sign in redirect URL (or auth URL) as:
```
https://app.cal.com/api/auth/oidc
```
And the sign out URL as:
```
https://app.cal.com/auth/login
```
Now you should have the Client Secret and Client ID with you. You would also need the Well Known URL which for Okta is generally of the type:
```
https://{yourOktaDomain}/.well-known/openid-configuration
```
So, if your Okta domain is `dev-123456.okta.com`, your well known URL would be:
```
https://dev-123456.okta.com/.well-known/openid-configuration
```
Log in with the Organization Admin user.
Visit `https://app.cal.com/settings/organizations/sso` and you should see something like this:
Click on Configure SSO with OIDC, and then enter the Client Secret, Client ID, and Well Known URL from Step 5, and click save.
That's it. Now when you try to login with SSO, your application on Okta will handle the authentication.
# How to setup scim with okta
Source: https://cal.com/docs/developing/guides/auth-and-provision/how-to-setup-scim-with-okta
Before provisioning users, your organization must have a **verified domain** configured. Cal.com only provisions users whose email domain matches (or is a subdomain of) your organization's verified domain. Provisioning requests for other domains are rejected to prevent account capture. Verify your domain under `Settings → Organization` before enabling SCIM.
For example, in Okta, once you create an account, you can click on Applications on the sidebar menu:
Note you will have to fill in the appropriate fields for the SAML or OIDC setup to continue.
* [SAML Setup](/docs/developing/guides/auth-and-provision/sso-setup#setting-up-saml-login)
* [OIDC Setup](/docs/developing/guides/auth-and-provision/sso-setup#setting-up-oidc-login)
Once the application is created, under General -> App Settings, click "Edit" and then the checkbox "Enable SCIM provisioning".
Next, go to your instance of Cal.com and navigate to `https://app.cal.com/settings/organization/dsync` and click configure.
In the "Configure Directory Sync" form, choose a directory sync name and select "Okta SCIM v2.0" as the "Directory Provider".
In Okta, go to your application. Navigate to the "Provisioning" tab and click "Integration" under "Settings".
* Under "SCIM connector base URL" enter the "SCIM Base URL" from Cal.com
* Under "Unique identifier field for users" enter "email"
* Under "Supported provisioning actions" enable:
* "Import New Users and Profile Updates"
* "Push New Users"
* "Push Profile Updates"
* "Push Groups"
* Under "Authentication Mode" choose "HTTP Header"
* Under "Authentication" enter the "SCIM Bearer Token" from Cal.com
* When you hit save, it will make a test call to the "SCIM Base URL"
After saving, navigate to the "To App" settings, still under the "Provisioning" tab.
Under "Provisioning to App", click "Edit" and enable:
* "Create User"
* "Update User Attributes"
* "Deactivate User"
Under "\{Your application name} Attribute Mapping," remove all fields except for:
* "username"
* "givenName"
* "familyName"
* "email"
* "displayName"
Set each of these properties to "Map from Okta Profile" and the related field. Under "Apply On" select "Create and Update".
You can now assign users and groups to the app.
## Mapping Okta Groups to Cal.com Teams
When provisioning groups to your organization, Okta groups can be mapped to teams within your organization, and users will be auto-assigned to these teams.
On `https://app.cal.com/settings/organization/dsync`, there is a table with the teams under your organization. Click on "Add group name" to map the Okta group to the team.
The group name must be spelled exactly as it is shown on Okta.
When you push the group to your organization, those users will automatically be added to the team.
## Email domain verification
Cal.com verifies that each SCIM-provisioned email belongs to your organization before force-joining the user. Provisioning succeeds only when all of the following are true:
* Your organization has a **verified domain** set.
* The provisioned user's email domain matches that verified domain exactly, or is a subdomain of it (for example, `eu.acme.com` matches `acme.com`).
If any check fails, the SCIM request is rejected with a `Forbidden` error and the user is not created or joined to your organization. If you see this error in your identity provider, confirm that the organization is verified and that the affected user's email domain matches your verified domain.
# SSO setup
Source: https://cal.com/docs/developing/guides/auth-and-provision/sso-setup
Cal.com supports both Security Assertion Markup Language (SAML) and OpenID Connect (OIDC), two of the industry's leading authentication protocols. We prioritize your ease of access and security by providing robust Single Sign-On (SSO) capabilities. Whether you're looking for the XML-based standard of SAML or the lightweight OIDC, our platform is equipped to integrate smoothly with your preferred identity provider, ensuring both convenience and security for your users.
### Setting up SAML login
Follow the instructions here - [SAML Setup](/docs/developing/guides/auth-and-provision/sso-setup#saml-registration-with-identity-providers)
Ensure that all users who need access to Cal.com have access to the IdP SAML app.
Keep the XML metadata from your IdP accessible, as you will need it later.
Visit `settings/organizations/sso`.
Click on the `Configure` button for `SSO with SAML`.
In the SAML configuration section, copy and paste the XML metadata from step 3 and click on Save.
Once setup is complete, provisioned users can log into Cal.com using SAML.
### SAML Registration with Identity Providers
This guide explains the settings you need to use to configure SAML with your Identity Provider. Once configured, obtain an XML metadata file and upload it on your Cal.com instance.
> **Note:** Please do not add a trailing slash at the end of the URLs. Create them exactly as shown below.
**Assertion consumer service URL / Single Sign-On URL / Destination URL:** [https://app.cal.com/api/auth/saml/callback](https://app.cal.com/api/auth/saml/callback)
**Entity ID / Identifier / Audience URI / Audience Restriction:** [https://saml.cal.com](https://saml.cal.com)
**Response:** Signed
**Assertion Signature:** Signed
**Signature Algorithm:** RSA-SHA256
**Assertion Encryption:** Unencrypted
**Name ID Format:** EmailAddress
**Application username:** email
**Mapping Attributes / Attribute Statements:**
| Name | Name Format | Value |
| :-------- | :---------- | :------------- |
| firstName | Basic | user.firstName |
| lastName | Basic | user.lastName |
### Setting up OIDC login
Keep handy the Client Secret, Client ID, and Well Known URL for the next steps.
Visit `/settings/organizations/sso` and you should see something like this:
Click on Configure SSO with OIDC, enter the Client Secret, Client ID, and Well Known URL from Step 1, and click save.
Now, when you try to login with SSO, your OIDC provider will handle the authentication.
# Webhooks
Source: https://cal.com/docs/developing/guides/automation/webhooks
Webhooks offer a great way to automate the flow with other apps when invitees schedule, cancel or reschedule events, or when a meeting starts or ends.
The webhook subscription allows you to listen to specific trigger events, such as when a booking has been scheduled, for example. You can always listen to the webhook by providing a custom subscriber URL with your own development work. However, if you wish to trigger automations without any development work, you can use the integration with Zapier which connects Cal.com to your apps.
Please note that the webhooks can be associated with user as well as individual event types, including team event types.
### Creating a webhook subscription
To create a new webhook subscription, visit `/settings/developer/webhooks` and proceed to enter the following details:
This is the listener URL where the payload will be sent when an event trigger is activated.
The subscriber URL must meet the following requirements:
* **Cal.com SaaS**: Only HTTPS URLs are accepted. HTTP, private/internal IP addresses (e.g., `10.x.x.x`, `192.168.x.x`, `127.0.0.1`), and `localhost` are blocked.
* **Self-hosted**: Both HTTP and HTTPS URLs are accepted, and private IP addresses are allowed for internal webhooks.
* **All environments**: Cloud metadata endpoints (e.g., `169.254.169.254`) and non-HTTP protocols (e.g., `ftp://`, `file://`) are always blocked.
If your subscriber URL does not meet these requirements, the webhook creation request will be rejected with an error.
You can choose which specific triggers to listen to. Currently available triggers include:
* `Booking Cancelled`
* `Booking Created`
* `Booking Rescheduled`
* `Booking Rejected`
* `Booking Requested`
* `Booking Payment Initiated`
* `Booking Paid`
* `Meeting Started`
* `Recording Ready`
* `Form Submitted`
* `Meeting Ended`
* `Instant Meeting Created`
* `Instant Meeting Accepted`
* `Booking No-show Updated`
* `Booking Location Updated`
* `Booking Reassigned`
* `After Hosts Didn't Join Cal Video`
* `After Guests Didn't Join Cal Video`
* `Wrong Assignment Report`
* `Delegation Credential Error`
* `Delegation Credential Secret Rotated`
* `Delegation Credential Secret Rotation Failed`
* `Delegation Credential Rotation Required`
You can provide a secret key with this webhook to [verify it](/docs/core-features/webhooks#verifying-the-authenticity-of-the-received-payload) on the subscriber URL when receiving a payload. This helps confirm whether the payload is authentic or has been tampered with. You can leave it blank if you don't wish to secure the webhook with a secret key.
You have the option to [customize the payload](/docs/core-features/webhooks#adding-a-custom-payload-template) that you receive when a subscribed event is triggered.
### Webhook payload reference
Most webhook payloads are wrapped in the following structure:
```json theme={null}
{
"triggerEvent": "TRIGGER_NAME",
"createdAt": "2024-01-01T00:00:00.000Z",
"payload": { ... }
}
```
`MEETING_STARTED` and `MEETING_ENDED` are exceptions — they use a flat payload where booking fields are at the top level alongside `triggerEvent`, with no `payload` wrapper. See [meeting started and meeting ended webhooks](#meeting-started-and-meeting-ended-webhooks) for details.
Webhook payloads are versioned. The version of the payload sent is included in the x-cal-webhook-version HTTP header.
Example: x-cal-webhook-version: 2021-10-20
For **seated event types**, the `attendees` array in webhook payloads contains only the attendee associated with the specific seat that triggered the webhook. For example, when a new seat is booked, the webhook includes only that seat's attendee — not all attendees across the entire booking. This applies to all booking-related triggers (`BOOKING_CREATED`, `BOOKING_CANCELLED`, `BOOKING_RESCHEDULED`, etc.).
Select a version and trigger event to view the example payload:
```json theme={null}
{
"triggerEvent": "BOOKING_CREATED",
"createdAt": "2024-01-01T00:00:00.000Z",
"payload": {
"bookerUrl": "https://app.example.com",
"title": "Strategy Session between Organizer and Guest",
"startTime": "2024-01-01T10:00:00Z",
"endTime": "2024-01-01T10:15:00Z",
"additionalNotes": "",
"type": "standard-event-type",
"description": "",
"eventTypeId": 123,
"hideCalendarNotes": false,
"hideCalendarEventDetails": false,
"hideOrganizerEmail": false,
"schedulingType": null,
"seatsPerTimeSlot": null,
"seatsShowAttendees": true,
"seatsShowAvailabilityCount": true,
"customReplyToEmail": null,
"disableRescheduling": false,
"disableCancelling": false,
"organizer": {
"id": 1,
"name": "Organizer Name",
"email": "organizer@example.com",
"username": "organizer-handle",
"usernameInOrg": "organizer",
"timeZone": "UTC",
"language": {
"locale": "en"
},
"timeFormat": "h:mma",
"utcOffset": 0
},
"attendees": [
{
"email": "guest@example.com",
"name": "Guest User",
"firstName": "Guest",
"lastName": "User",
"timeZone": "UTC",
"language": {
"locale": "en"
},
"utcOffset": 0
},
{
"email": "additional-guest@example.com",
"name": "Additional Guest",
"firstName": "",
"lastName": "",
"timeZone": "UTC",
"language": {
"locale": "en"
},
"utcOffset": 0
}
],
"customInputs": {},
"responses": {
"name": {
"label": "your_name",
"value": "Guest User",
"isHidden": false
},
"email": {
"label": "email_address",
"value": "guest@example.com",
"isHidden": false
},
"attendeePhoneNumber": {
"label": "phone_number",
"isHidden": true
},
"location": {
"label": "location",
"value": {
"optionValue": "",
"value": "integrations:video-provider"
},
"isHidden": false
},
"title": {
"label": "what_is_this_meeting_about",
"isHidden": true
},
"notes": {
"label": "additional_notes",
"isHidden": false
},
"guests": {
"label": "additional_guests",
"value": [
"additional-guest@example.com"
],
"isHidden": false
},
"rescheduleReason": {
"label": "reason_for_reschedule",
"isHidden": false
}
},
"userFieldsResponses": {},
"location": "integrations:video-provider",
"destinationCalendar": null,
"iCalUID": "unique-identifier-string@example.com",
"iCalSequence": 0,
"requiresConfirmation": false,
"oneTimePassword": null,
"organizationId": 1,
"hashedLink": null,
"uid": "unique-booking-id",
"conferenceData": {
"createRequest": {
"requestId": "00000000-0000-0000-0000-000000000000"
}
},
"videoCallData": {
"type": "video_provider",
"id": "meeting-room-id",
"password": "redacted_token",
"url": "https://video.example.com/meeting-room-id"
},
"appsStatus": [
{
"appName": "video-app",
"type": "video_provider",
"success": 1,
"failures": 0,
"errors": []
}
],
"eventTitle": "Strategy Session",
"eventDescription": "",
"price": 0,
"currency": "usd",
"length": 15,
"bookingId": 100,
"metadata": {
"videoCallUrl": "https://app.example.com/video/unique-booking-id"
},
"status": "ACCEPTED"
}
}
```
```json theme={null}
{
"triggerEvent": "BOOKING_CANCELLED",
"createdAt": "2024-01-01T15:00:00.000Z",
"payload": {
"bookerUrl": "https://app.example.com",
"title": "Strategy Session: Organizer & Guest",
"length": 15,
"type": "standard-event-type",
"additionalNotes": "Standard meeting notes placeholder",
"description": "Standard meeting notes placeholder",
"customInputs": {},
"eventTypeId": 100,
"userFieldsResponses": {},
"responses": {
"name": {
"label": "name",
"value": "Guest User"
},
"email": {
"label": "email",
"value": "guest@example.com"
},
"notes": {
"label": "notes",
"value": "Standard meeting notes placeholder"
},
"guests": {
"label": "guests",
"value": [
"additional-guest@example.com"
]
},
"rescheduleReason": {
"label": "rescheduleReason",
"value": "Original slot no longer works."
}
},
"startTime": "2024-01-04T10:00:00+00:00",
"endTime": "2024-01-04T10:15:00+00:00",
"organizer": {
"id": 1,
"username": "organizer-handle",
"usernameInOrg": "organizer",
"email": "organizer@example.com",
"name": "Organizer Name",
"timeZone": "UTC",
"timeFormat": "h:mma",
"language": {
"locale": "en"
},
"utcOffset": 0
},
"attendees": [
{
"name": "Guest User",
"email": "guest@example.com",
"timeZone": "UTC",
"phoneNumber": null,
"language": {
"locale": "en"
},
"utcOffset": 0
},
{
"name": "Additional Guest",
"email": "additional-guest@example.com",
"timeZone": "UTC",
"phoneNumber": null,
"language": {
"locale": "en"
},
"utcOffset": 0
}
],
"uid": "unique-booking-identifier",
"bookingId": 200,
"location": "integrations:video-provider",
"destinationCalendar": [],
"cancellationReason": "I am no longer able to attend this session.",
"seatsPerTimeSlot": null,
"seatsShowAttendees": false,
"iCalUID": "unique-identifier@example.com",
"iCalSequence": 2,
"hideOrganizerEmail": false,
"customReplyToEmail": null,
"organizationId": 5,
"eventTitle": "Strategy Session",
"eventDescription": null,
"requiresConfirmation": true,
"price": null,
"currency": "usd",
"status": "CANCELLED",
"requestReschedule": false
}
}
```
```json theme={null}
{
"triggerEvent": "BOOKING_RESCHEDULED",
"createdAt": "2024-01-01T10:00:00.000Z",
"payload": {
"bookerUrl": "https://app.example.com",
"title": "Strategy Session: Organizer & Guest",
"startTime": "2024-01-10T14:30:00Z",
"endTime": "2024-01-10T14:45:00Z",
"additionalNotes": "Standard meeting notes placeholder",
"type": "standard-event-type",
"description": "Standard meeting notes placeholder",
"eventTypeId": 100,
"hideCalendarNotes": false,
"hideCalendarEventDetails": false,
"hideOrganizerEmail": false,
"schedulingType": null,
"seatsPerTimeSlot": null,
"seatsShowAttendees": true,
"seatsShowAvailabilityCount": true,
"customReplyToEmail": null,
"disableRescheduling": false,
"disableCancelling": false,
"organizer": {
"id": 1,
"name": "Organizer Name",
"email": "organizer@example.com",
"username": "organizer-handle",
"usernameInOrg": "organizer",
"timeZone": "UTC",
"language": {
"locale": "en"
},
"timeFormat": "h:mma",
"utcOffset": 0
},
"attendees": [
{
"email": "guest@example.com",
"name": "Guest User",
"firstName": "Guest",
"lastName": "User",
"timeZone": "UTC",
"language": {
"locale": "en"
},
"utcOffset": 0
},
{
"email": "additional-guest@example.com",
"name": "Additional Guest",
"firstName": "",
"lastName": "",
"timeZone": "UTC",
"language": {
"locale": "en"
},
"utcOffset": 0
}
],
"customInputs": {},
"responses": {
"name": {
"label": "your_name",
"value": "Guest User",
"isHidden": false
},
"email": {
"label": "email_address",
"value": "guest@example.com",
"isHidden": false
},
"attendeePhoneNumber": {
"label": "phone_number",
"isHidden": true
},
"location": {
"label": "location",
"value": {
"value": "integrations:video-provider",
"optionValue": ""
},
"isHidden": false
},
"title": {
"label": "what_is_this_meeting_about",
"isHidden": true
},
"notes": {
"label": "additional_notes",
"value": "Standard meeting notes placeholder",
"isHidden": false
},
"guests": {
"label": "additional_guests",
"value": [
"additional-guest@example.com"
],
"isHidden": false
},
"rescheduleReason": {
"label": "reason_for_reschedule",
"isHidden": false
}
},
"userFieldsResponses": {},
"location": "integrations:video-provider",
"destinationCalendar": null,
"iCalSequence": 1,
"requiresConfirmation": false,
"oneTimePassword": null,
"organizationId": 1,
"hashedLink": null,
"uid": "new-booking-unique-id",
"videoCallData": {
"type": "video_provider",
"id": "meeting-room-id",
"password": "redacted_jwt_token",
"url": "https://video.example.com/meeting-room-id"
},
"conferenceData": {
"createRequest": {
"requestId": "00000000-0000-0000-0000-000000000000"
}
},
"appsStatus": [
{
"appName": "video-app",
"type": "video_provider",
"success": 1,
"failures": 0,
"errors": []
}
],
"eventTitle": "Strategy Session",
"eventDescription": "",
"price": 0,
"currency": "usd",
"length": 15,
"bookingId": 201,
"rescheduleId": 200,
"rescheduleUid": "previous-booking-unique-id",
"rescheduleStartTime": "2024-01-05T14:30:00Z",
"rescheduleEndTime": "2024-01-05T14:45:00Z",
"metadata": {
"videoCallUrl": "https://app.example.com/video/new-booking-unique-id"
},
"status": "ACCEPTED"
}
}
```
```json theme={null}
{
"triggerEvent": "BOOKING_REQUESTED",
"createdAt": "2024-01-01T12:00:00.000Z",
"payload": {
"bookerUrl": "https://app.example.com",
"title": "Strategy Session: Organizer & Guest",
"startTime": "2024-01-01T13:00:00Z",
"endTime": "2024-01-01T13:15:00Z",
"additionalNotes": "Standard meeting notes placeholder",
"type": "standard-event-type",
"description": "Standard meeting notes placeholder",
"eventTypeId": 100,
"hideCalendarNotes": false,
"hideCalendarEventDetails": false,
"hideOrganizerEmail": false,
"schedulingType": null,
"seatsPerTimeSlot": null,
"seatsShowAttendees": true,
"seatsShowAvailabilityCount": true,
"customReplyToEmail": null,
"disableRescheduling": false,
"disableCancelling": false,
"organizer": {
"id": 1,
"name": "Organizer Name",
"email": "organizer@example.com",
"username": "organizer-handle",
"usernameInOrg": "organizer",
"timeZone": "UTC",
"language": {
"locale": "en"
},
"timeFormat": "h:mma",
"utcOffset": 0
},
"attendees": [
{
"email": "guest@example.com",
"name": "Guest User",
"firstName": "Guest",
"lastName": "User",
"timeZone": "UTC",
"language": {
"locale": "en"
},
"utcOffset": 0
},
{
"email": "additional-guest@example.com",
"name": "Additional Guest",
"firstName": "",
"lastName": "",
"timeZone": "UTC",
"language": {
"locale": "en"
},
"utcOffset": 0
}
],
"customInputs": {},
"responses": {
"name": {
"label": "your_name",
"value": "Guest User",
"isHidden": false
},
"email": {
"label": "email_address",
"value": "guest@example.com",
"isHidden": false
},
"attendeePhoneNumber": {
"label": "phone_number",
"isHidden": true
},
"location": {
"label": "location",
"value": {
"value": "integrations:video-provider",
"optionValue": ""
},
"isHidden": false
},
"title": {
"label": "what_is_this_meeting_about",
"isHidden": true
},
"notes": {
"label": "additional_notes",
"value": "Standard meeting notes placeholder",
"isHidden": false
},
"guests": {
"label": "additional_guests",
"value": [
"additional-guest@example.com"
],
"isHidden": false
},
"rescheduleReason": {
"label": "reason_for_reschedule",
"isHidden": false
}
},
"userFieldsResponses": {},
"location": "integrations:video-provider",
"destinationCalendar": null,
"iCalUID": "unique-booking-id@example.com",
"iCalSequence": 0,
"requiresConfirmation": true,
"oneTimePassword": "00000000-0000-0000-0000-000000000000",
"organizationId": 1,
"hashedLink": null,
"uid": "unique-booking-id",
"eventTitle": "Strategy Session",
"eventDescription": "",
"price": 0,
"currency": "usd",
"length": 15,
"bookingId": 200,
"metadata": {},
"status": "PENDING"
}
}
```
```json theme={null}
{
"triggerEvent": "BOOKING_REJECTED",
"createdAt": "2024-01-01T14:00:00.000Z",
"payload": {
"type": "standard-event-type",
"title": "Strategy Session: Organizer & Guest",
"description": "Standard meeting notes placeholder",
"bookerUrl": "https://app.example.com",
"userFieldsResponses": {},
"responses": {
"name": {
"label": "name",
"value": "Guest User"
},
"email": {
"label": "email",
"value": "guest@example.com"
},
"notes": {
"label": "notes",
"value": "Standard meeting notes placeholder"
},
"guests": {
"label": "guests",
"value": [
"additional-guest@example.com"
]
}
},
"customInputs": {},
"startTime": "2024-01-01T15:00:00.000Z",
"endTime": "2024-01-01T15:15:00.000Z",
"organizer": {
"id": 1,
"email": "organizer@example.com",
"name": "Organizer Name",
"username": "organizer-handle",
"usernameInOrg": "organizer",
"timeZone": "UTC",
"timeFormat": "h:mma",
"language": {
"locale": "en"
},
"utcOffset": 0
},
"attendees": [
{
"name": "Guest User",
"email": "guest@example.com",
"timeZone": "UTC",
"phoneNumber": null,
"language": {
"locale": "en"
},
"utcOffset": 0
},
{
"name": "Additional Guest",
"email": "additional-guest@example.com",
"timeZone": "UTC",
"phoneNumber": null,
"language": {
"locale": "en"
},
"utcOffset": 0
}
],
"location": "integrations:video-provider",
"uid": "unique-booking-identifier",
"destinationCalendar": [],
"requiresConfirmation": true,
"hideOrganizerEmail": false,
"hideCalendarNotes": false,
"hideCalendarEventDetails": false,
"eventTypeId": 100,
"customReplyToEmail": null,
"organizationId": 5,
"additionalNotes": "Standard meeting notes placeholder",
"rejectionReason": "The organizer is no longer available at this time.",
"eventTitle": "Strategy Session",
"eventDescription": "",
"price": 0,
"currency": "usd",
"length": 15,
"bookingId": 200,
"status": "REJECTED"
}
}
```
```json theme={null}
{
"triggerEvent": "BOOKING_PAID",
"createdAt": "2024-01-01T12:00:00.000Z",
"payload": {
"type": "Consultation Session",
"title": "Consultation Session: Organizer & Guest",
"description": "",
"additionalNotes": "",
"customInputs": {},
"startTime": "2024-01-02T10:00:00.000Z",
"endTime": "2024-01-02T10:30:00.000Z",
"organizer": {
"id": 1,
"name": "Organizer Name",
"email": "organizer@example.com",
"username": "organizer-handle",
"timeZone": "UTC",
"language": { "locale": "en" },
"timeFormat": 12
},
"attendees": [
{
"name": "Guest User",
"email": "guest@example.com",
"timeZone": "UTC",
"language": { "locale": "en" }
}
],
"location": "integrations:video-provider",
"destinationCalendar": null,
"hideCalendarNotes": false,
"requiresConfirmation": true,
"eventTypeId": 50,
"seatsShowAttendees": true,
"seatsShowAvailabilityCount": true,
"schedulingType": null,
"iCalUID": "unique-ical-uid@example.com",
"iCalSequence": 0,
"uid": "unique-booking-uid",
"bookingId": 100,
"eventTitle": "Consultation Session",
"eventDescription": "",
"price": 1000,
"currency": "USD",
"length": 30,
"paymentId": 500,
"metadata": {
"identifier": "platform.name",
"bookingId": 100,
"eventTypeId": 50,
"bookerEmail": "guest@example.com",
"eventTitle": "Consultation Session",
"externalId": "pi_0000000000000000"
},
"status": "ACCEPTED"
}
}
```
```json theme={null}
{
"triggerEvent": "BOOKING_PAYMENT_INITIATED",
"createdAt": "2024-01-01T12:00:00.000Z",
"payload": {
"type": "Consultation Session",
"title": "Consultation Session: Organizer & Guest",
"description": "",
"additionalNotes": "",
"customInputs": {},
"startTime": "2024-01-02T10:00:00.000Z",
"endTime": "2024-01-02T10:30:00.000Z",
"organizer": {
"id": 1,
"name": "Organizer Name",
"email": "organizer@example.com",
"username": "organizer-handle",
"timeZone": "UTC",
"language": { "locale": "en" },
"timeFormat": 12
},
"attendees": [
{
"name": "Guest User",
"email": "guest@example.com",
"timeZone": "UTC",
"language": { "locale": "en" }
}
],
"location": "integrations:video-provider",
"destinationCalendar": null,
"hideCalendarNotes": false,
"requiresConfirmation": false,
"eventTypeId": 50,
"seatsShowAttendees": true,
"seatsShowAvailabilityCount": true,
"schedulingType": null,
"iCalUID": "unique-ical-uid@example.com",
"iCalSequence": 0,
"uid": "unique-booking-uid",
"bookingId": 100,
"eventTitle": "Consultation Session",
"eventDescription": "",
"requiresConfirmation": false,
"price": 1000,
"currency": "USD",
"length": 30,
"paymentId": 500,
"status": "ACCEPTED",
"metadata": {}
}
}
```
```json theme={null}
{
"triggerEvent": "BOOKING_NO_SHOW_UPDATED",
"createdAt": "2024-01-01T12:00:00.000Z",
"payload": {
"message": "guest@example.com unmarked as no-show",
"attendees": [
{
"email": "guest@example.com",
"noShow": false
}
],
"bookingUid": "unique-booking-identifier",
"bookingId": 100
}
}
```
Fires when the location of an existing booking is changed. The payload mirrors the standard booking payload, with one addition: `previousLocation` holds the location before the change and existing `location` holds the new location.
```json theme={null}
{
"triggerEvent": "BOOKING_LOCATION_UPDATED",
"createdAt": "2024-01-01T10:00:00.000Z",
"payload": {
"bookerUrl": "https://app.example.com",
"title": "Strategy Session between Organizer and Guest",
"startTime": "2024-01-01T10:00:00Z",
"endTime": "2024-01-01T10:15:00Z",
"additionalNotes": "",
"type": "standard-event-type",
"description": "",
"eventTypeId": 123,
"hideCalendarNotes": false,
"hideCalendarEventDetails": false,
"hideOrganizerEmail": false,
"schedulingType": null,
"seatsPerTimeSlot": null,
"seatsShowAttendees": true,
"seatsShowAvailabilityCount": true,
"customReplyToEmail": null,
"organizer": {
"id": 1,
"name": "Organizer Name",
"email": "organizer@example.com",
"username": "organizer-handle",
"usernameInOrg": "organizer",
"timeZone": "UTC",
"language": {
"locale": "en"
},
"timeFormat": "h:mma",
"utcOffset": 0
},
"attendees": [
{
"email": "guest@example.com",
"name": "Guest User",
"firstName": "Guest",
"lastName": "User",
"timeZone": "UTC",
"language": {
"locale": "en"
},
"utcOffset": 0
}
],
"customInputs": {},
"responses": {
"name": {
"label": "your_name",
"value": "Guest User",
"isHidden": false
},
"email": {
"label": "email_address",
"value": "guest@example.com",
"isHidden": false
},
"location": {
"label": "location",
"value": {
"optionValue": "",
"value": "https://app.example.com/video/unique-booking-id"
},
"isHidden": false
}
},
"userFieldsResponses": {},
"location": "https://app.example.com/video/unique-booking-id",
"previousLocation": "https://meet.google.com/abc-defg-hij",
"destinationCalendar": null,
"iCalUID": "unique-identifier-string@example.com",
"iCalSequence": 1,
"requiresConfirmation": false,
"organizationId": 1,
"uid": "unique-booking-id",
"eventTitle": "Strategy Session",
"eventDescription": "",
"price": 0,
"currency": "usd",
"length": 15,
"bookingId": 100,
"metadata": {
"videoCallUrl": "https://app.example.com/video/unique-booking-id"
},
"status": "ACCEPTED"
}
}
```
Fires when a round-robin booking's host is reassigned (automatic or manual). The payload mirrors the standard booking payload, with two additions: `addedHostUserIds` lists the user IDs of the host(s) newly assigned to the booking and `removedHostUserIds` lists the user IDs of the host(s) removed. `organizer` reflects the new host. Both are IDs only — resolve them via the Cal.com API if you need host names or emails.
```json theme={null}
{
"triggerEvent": "BOOKING_REASSIGNED",
"createdAt": "2024-01-01T10:00:00.000Z",
"payload": {
"bookerUrl": "https://app.example.com",
"title": "Strategy Session between Organizer and Guest",
"startTime": "2024-01-01T10:00:00Z",
"endTime": "2024-01-01T10:15:00Z",
"type": "standard-event-type",
"description": "",
"eventTypeId": 123,
"organizer": {
"id": 2,
"name": "New Host",
"email": "new-host@example.com",
"username": "new-host",
"timeZone": "UTC",
"language": {
"locale": "en"
},
"timeFormat": "h:mma",
"utcOffset": 0
},
"attendees": [
{
"email": "guest@example.com",
"name": "Guest User",
"firstName": "Guest",
"lastName": "User",
"timeZone": "UTC",
"language": {
"locale": "en"
},
"utcOffset": 0
}
],
"responses": {
"name": {
"label": "your_name",
"value": "Guest User",
"isHidden": false
},
"email": {
"label": "email_address",
"value": "guest@example.com",
"isHidden": false
}
},
"location": "https://app.example.com/video/unique-booking-id",
"destinationCalendar": null,
"iCalUID": "unique-identifier-string@example.com",
"iCalSequence": 1,
"uid": "unique-booking-id",
"eventTitle": "Strategy Session",
"bookingId": 100,
"status": "ACCEPTED",
"addedHostUserIds": [2],
"removedHostUserIds": [1]
}
}
```
Unlike other events, `MEETING_STARTED` uses a flat payload structure — booking fields are at the top level, not nested inside a `payload` object. This webhook fires automatically at the booking's scheduled start time. See [meeting started and meeting ended webhooks](#meeting-started-and-meeting-ended-webhooks) for details.
```json theme={null}
{
"triggerEvent": "MEETING_STARTED",
"id": 100,
"uid": "unique-booking-identifier",
"idempotencyKey": "00000000-0000-0000-0000-000000000000",
"userId": 10,
"userPrimaryEmail": "organizer@example.com",
"eventTypeId": 50,
"title": "Strategy Session: Organizer & Guest",
"description": "",
"customInputs": {},
"responses": {
"name": {
"label": "your_name",
"value": "Guest User",
"isHidden": false
},
"email": {
"label": "email_address",
"value": "guest@example.com",
"isHidden": false
},
"attendeePhoneNumber": {
"label": "phone_number",
"isHidden": true
},
"location": {
"label": "location",
"value": {
"optionValue": "",
"value": "integrations:video-provider"
},
"isHidden": false
},
"title": {
"label": "what_is_this_meeting_about",
"isHidden": true
},
"notes": {
"label": "additional_notes",
"isHidden": false
},
"guests": {
"label": "additional_guests",
"value": [
"additional-guest@example.com"
],
"isHidden": false
},
"rescheduleReason": {
"label": "reason_for_reschedule",
"isHidden": false
}
},
"startTime": "2024-01-01T10:00:00.000Z",
"endTime": "2024-01-01T10:15:00.000Z",
"location": "integrations:video-provider",
"createdAt": "2024-01-01T09:45:00.000Z",
"updatedAt": "2024-01-01T09:45:00.000Z",
"status": "ACCEPTED",
"paid": false,
"destinationCalendarId": null,
"cancellationReason": null,
"rejectionReason": null,
"reassignReason": null,
"reassignById": null,
"dynamicEventSlugRef": null,
"dynamicGroupSlugRef": null,
"rescheduled": null,
"fromReschedule": null,
"recurringEventId": null,
"smsReminderNumber": null,
"scheduledJobs": [],
"metadata": {},
"isRecorded": false,
"iCalUID": "unique-id@example.com",
"iCalSequence": 0,
"rating": null,
"ratingFeedback": null,
"noShowHost": false,
"oneTimePassword": null,
"cancelledBy": null,
"rescheduledBy": null,
"creationSource": "WEBAPP",
"user": {
"email": "organizer@example.com",
"name": "Organizer Name",
"timeZone": "UTC",
"username": "organizer-handle"
},
"attendees": [
{
"id": 101,
"email": "guest@example.com",
"name": "Guest User",
"timeZone": "UTC",
"phoneNumber": null,
"locale": "en",
"bookingId": 100,
"noShow": false
},
{
"id": 102,
"email": "additional-guest@example.com",
"name": "Additional Guest",
"timeZone": "UTC",
"phoneNumber": null,
"locale": "en",
"bookingId": 100,
"noShow": false
}
],
"payment": [],
"references": [],
"appsStatus": [
{
"appName": "video-app",
"type": "video_provider",
"success": 1,
"failures": 0,
"errors": []
}
]
}
```
Unlike other events, `MEETING_ENDED` uses a flat payload structure — booking fields are at the top level, not nested inside a `payload` object. This webhook fires automatically at the booking's scheduled end time. See [meeting started and meeting ended webhooks](#meeting-started-and-meeting-ended-webhooks) for details.
```json theme={null}
{
"triggerEvent": "MEETING_ENDED",
"id": 100,
"uid": "unique-booking-identifier",
"idempotencyKey": "00000000-0000-0000-0000-000000000000",
"userId": 10,
"userPrimaryEmail": "organizer@example.com",
"eventTypeId": 50,
"title": "Strategy Session: Organizer & Guest",
"description": "",
"customInputs": {},
"responses": {
"name": {
"label": "your_name",
"value": "Guest User",
"isHidden": false
},
"email": {
"label": "email_address",
"value": "guest@example.com",
"isHidden": false
},
"attendeePhoneNumber": {
"label": "phone_number",
"isHidden": true
},
"location": {
"label": "location",
"value": {
"optionValue": "",
"value": "integrations:video-provider"
},
"isHidden": false
},
"title": {
"label": "what_is_this_meeting_about",
"isHidden": true
},
"notes": {
"label": "additional_notes",
"isHidden": false
},
"guests": {
"label": "additional_guests",
"value": [
"additional-guest@example.com"
],
"isHidden": false
},
"rescheduleReason": {
"label": "reason_for_reschedule",
"isHidden": false
}
},
"startTime": "2024-01-01T10:00:00.000Z",
"endTime": "2024-01-01T10:15:00.000Z",
"location": "integrations:video-provider",
"createdAt": "2024-01-01T09:45:00.000Z",
"updatedAt": "2024-01-01T10:20:00.000Z",
"status": "ACCEPTED",
"paid": false,
"destinationCalendarId": null,
"cancellationReason": null,
"rejectionReason": null,
"reassignReason": null,
"reassignById": null,
"dynamicEventSlugRef": null,
"dynamicGroupSlugRef": null,
"rescheduled": null,
"fromReschedule": null,
"recurringEventId": null,
"smsReminderNumber": null,
"scheduledJobs": [],
"metadata": {},
"isRecorded": false,
"iCalUID": "unique-id@example.com",
"iCalSequence": 0,
"rating": null,
"ratingFeedback": null,
"noShowHost": false,
"oneTimePassword": null,
"cancelledBy": null,
"rescheduledBy": null,
"creationSource": "WEBAPP",
"user": {
"email": "organizer@example.com",
"name": "Organizer Name",
"timeZone": "UTC",
"username": "organizer-handle"
},
"attendees": [
{
"id": 101,
"email": "guest@example.com",
"name": "Guest User",
"timeZone": "UTC",
"phoneNumber": null,
"locale": "en",
"bookingId": 100,
"noShow": false
},
{
"id": 102,
"email": "additional-guest@example.com",
"name": "Additional Guest",
"timeZone": "UTC",
"phoneNumber": null,
"locale": "en",
"bookingId": 100,
"noShow": false
}
],
"payment": [],
"references": [],
"appsStatus": [
{
"appName": "video-app",
"type": "video_provider",
"success": 1,
"failures": 0,
"errors": []
}
]
}
```
```json theme={null}
{
"triggerEvent": "RECORDING_READY",
"createdAt": "2025-12-22T05:50:04.675Z",
"payload": {
"type": "meeting between User_A and User_B",
"title": "meeting between User_A and User_B",
"startTime": "2025-12-22T05:50:00.000Z",
"endTime": "2025-12-22T06:00:00.000Z",
"organizer": {
"email": "organizer@example.com",
"name": "Organizer Name",
"timeZone": "Asia/Dubai",
"language": {
"locale": "en"
},
"utcOffset": 240
},
"attendees": [
{
"id": 00000000,
"name": "Attendee Name",
"email": "attendee@example.com",
"timeZone": "Asia/Dubai",
"language": {
"locale": "en"
},
"utcOffset": 240
}
],
"uid": "REDACTED_UID_STRING",
"customReplyToEmail": null,
"downloadLink": "https://app.cal.com/api/video/recording?token=REDACTED_TOKEN"
}
}
```
```json theme={null}
{
"triggerEvent": "RECORDING_TRANSCRIPTION_GENERATED",
"createdAt": "2025-12-22T05:52:57.101Z",
"payload": {
"type": "meeting between User_A and User_B",
"title": "meeting between User_A and User_B",
"startTime": "2025-12-22T05:50:00.000Z",
"endTime": "2025-12-22T06:00:00.000Z",
"organizer": {
"email": "organizer@example.com",
"name": "Organizer Name",
"timeZone": "Asia/Dubai",
"language": {
"locale": "en"
},
"utcOffset": 240
},
"attendees": [
{
"id": 00000000,
"name": "Attendee Name",
"email": "attendee@example.com",
"timeZone": "Asia/Dubai",
"language": {
"locale": "en"
},
"utcOffset": 240
}
],
"uid": "REDACTED_UID",
"customReplyToEmail": null,
"downloadLinks": {
"transcription": [
{
"format": "json",
"link": "https://s3.amazonaws.com/path/to/transcript.json?REDACTED_AWS_SIGNATURE"
},
{
"format": "srt",
"link": "https://s3.amazonaws.com/path/to/transcript.srt?REDACTED_AWS_SIGNATURE"
},
{
"format": "txt",
"link": "https://s3.amazonaws.com/path/to/transcript.txt?REDACTED_AWS_SIGNATURE"
},
{
"format": "vtt",
"link": "https://s3.amazonaws.com/path/to/transcript.vtt?REDACTED_AWS_SIGNATURE"
}
],
"recording": "https://app.cal.com/api/video/recording?token=REDACTED_TOKEN"
}
}
}
```
```json theme={null}
{
"triggerEvent": "INSTANT_MEETING",
"createdAt": "2024-01-01T12:00:00.000Z",
"payload": {
"triggerEvent": "INSTANT_MEETING",
"uid": "unique-instant-meeting-id",
"responses": {
"name": "Guest User",
"email": "guest@example.com",
"notes": "Standard meeting notes placeholder",
"guests": [
"additional-guest@example.com"
]
},
"connectAndJoinUrl": "https://app.example.com/connect-and-join?token=REDACTED_ACCESS_TOKEN",
"eventTypeId": 100,
"eventTypeTitle": "Instant Meeting",
"customInputs": {}
}
}
```
Fired when a host accepts an instant meeting and becomes the organizer via the connect-and-join flow. This only triggers for the first host to accept — if the booking has already been accepted by another host, the webhook is not sent.
```json theme={null}
{
"triggerEvent": "INSTANT_MEETING_ACCEPTED",
"createdAt": "2024-01-01T12:01:00.000Z",
"payload": {
"bookingId": 100,
"bookingUid": "unique-instant-meeting-id",
"eventTypeId": 100,
"status": "ACCEPTED",
"title": "Instant Meeting between Host and Guest",
"startTime": "2024-01-01T12:00:00.000Z",
"endTime": "2024-01-01T12:30:00.000Z",
"organizer": {
"id": 1,
"name": "Host Name",
"email": "host@example.com",
"username": "host-handle",
"timeZone": "UTC",
"language": {
"locale": "en"
},
"timeFormat": "h:mma",
"utcOffset": 0
},
"attendees": [
{
"email": "guest@example.com",
"name": "Guest User",
"timeZone": "UTC",
"language": {
"locale": "en"
},
"utcOffset": 0
}
],
"responses": {
"name": {
"label": "your_name",
"value": "Guest User"
},
"email": {
"label": "email_address",
"value": "guest@example.com"
}
},
"location": "integrations:video-provider",
"metadata": {
"videoCallUrl": "https://app.example.com/video/unique-instant-meeting-id"
}
}
}
```
`toUser` is the redirect/forwarding user and can be `null` if no redirect is set.
```json theme={null}
{
"triggerEvent": "OOO_CREATED",
"createdAt": "2024-01-01T08:00:00.000Z",
"payload": {
"oooEntry": {
"id": 100,
"start": "2024-01-05T00:00:00+00:00",
"end": "2024-01-07T23:59:59+00:00",
"createdAt": "2024-01-01T08:00:00.000Z",
"updatedAt": "2024-01-01T08:00:00.000Z",
"notes": "Holiday Leave",
"reason": {
"emoji": "🌴",
"reason": "ooo_reasons_vacation"
},
"reasonId": 5,
"user": {
"id": 1,
"name": "Organizer Name",
"username": "organizer-handle",
"email": "organizer@example.com",
"timeZone": "UTC"
},
"toUser": null,
"uuid": "00000000-0000-0000-0000-000000000000"
}
}
}
```
Root-level fields (e.g., `name`, `email`, `department`) are duplicated for backward compatibility. The `value` field in responses is deprecated; use `response` instead. For select/multiselect fields, `response` includes both the label and ID.
```json theme={null}
{
"What Language do you prefer": [
"Placeholder Language"
],
"Where are you from": [
"Placeholder Location"
],
"triggerEvent": "FORM_SUBMITTED",
"createdAt": "2024-01-01T12:00:00.000Z",
"payload": {
"formId": "00000000-0000-0000-0000-000000000000",
"formName": "Customer Intake Form",
"teamId": null,
"responses": {
"What Language do you prefer": {
"value": [
"Placeholder Language"
],
"response": [
{
"label": "Placeholder Language",
"id": "00000000-0000-0000-0000-000000000000"
}
]
},
"Where are you from": {
"value": [
"Placeholder Location"
],
"response": [
{
"label": "Placeholder Location",
"id": "00000000-0000-0000-0000-000000000000"
}
]
}
}
}
}
```
Same structure as `FORM_SUBMITTED`. Triggered 15 minutes after form submission if no booking was made from the routing form.
```json theme={null}
{
"triggerEvent": "FORM_SUBMITTED_NO_EVENT",
"createdAt": "2024-01-01T12:00:00.000Z",
"payload": {
"formId": "00000000-0000-0000-0000-000000000000",
"formName": "Customer Inquiry Form",
"teamId": null,
"redirect": {
"type": "customPageMessage",
"value": "Thank you for your interest! Our team will review your details."
},
"responseId": 100,
"responses": {
"What Language do you prefer": {
"value": [
"Placeholder Language"
],
"response": [
{
"label": "Placeholder Language",
"id": "00000000-0000-0000-0000-000000000000"
}
]
},
"Where are you from": {
"value": [
"Placeholder Location"
],
"response": [
{
"label": "Placeholder Location",
"id": "00000000-0000-0000-0000-000000000000"
}
]
}
}
}
}
```
```json theme={null}
{
"triggerEvent": "AFTER_HOSTS_CAL_VIDEO_NO_SHOW",
"createdAt": "2024-01-15T10:05:00.000Z",
"payload": {
"title": "30 Minute Meeting",
"bookingId": 123,
"bookingUid": "abc123-def456-ghi789",
"startTime": "2024-01-15T10:00:00.000Z",
"attendees": [
{
"id": 123,
"email": "jane@example.com",
"name": "Jane Smith",
"timeZone": "Europe/London",
"phoneNumber": null,
"locale": "en",
"bookingId": 123,
"noShow": true
}
],
"endTime": "2024-01-15T10:30:00.000Z",
"participants": [],
"hostEmail": "john@example.com",
"noShowHost": true,
"eventType": {
"id": 789,
"teamId": null,
"parentId": null,
"calVideoSettings": null
},
"webhook": {
"id": "webhook-abc123",
"subscriberUrl": "https://example.com/webhook",
"appId": null,
"time": 5,
"timeUnit": "MINUTE",
"eventTriggers": [
"AFTER_HOSTS_CAL_VIDEO_NO_SHOW",
"AFTER_GUESTS_CAL_VIDEO_NO_SHOW"
],
"payloadTemplate": null
},
"message": "Host with email john@example.com didn't join the call or didn't join before 2024-01-15 10:05:00 +00:00"
}
}
```
```json theme={null}
{
"triggerEvent": "AFTER_GUESTS_CAL_VIDEO_NO_SHOW",
"createdAt": "2024-01-15T10:05:00.000Z",
"payload": {
"title": "30 Minute Meeting",
"bookingId": 123,
"bookingUid": "abc123-def456-ghi789",
"startTime": "2024-01-15T10:00:00.000Z",
"attendees": [
{
"id": 123,
"email": "jane@example.com",
"name": "Jane Smith",
"timeZone": "Europe/London",
"phoneNumber": null,
"locale": "en",
"bookingId": 123,
"noShow": true
}
],
"endTime": "2024-01-15T10:30:00.000Z",
"participants": [],
"guests": [
{
"id": 123,
"email": "jane@example.com",
"name": "Jane Smith",
"timeZone": "Europe/London",
"phoneNumber": null,
"locale": "en",
"bookingId": 123,
"noShow": true
}
],
"eventType": {
"id": 789,
"teamId": null,
"parentId": null,
"calVideoSettings": null
},
"webhook": {
"id": "webhook-abc123",
"subscriberUrl": "https://example.com/webhook",
"appId": null,
"time": 5,
"timeUnit": "MINUTE",
"eventTriggers": [
"AFTER_HOSTS_CAL_VIDEO_NO_SHOW",
"AFTER_GUESTS_CAL_VIDEO_NO_SHOW"
],
"payloadTemplate": null
},
"message": "Guest didn't join the call or didn't join before 2024-01-15 10:05:00 +00:00"
}
}
```
```json theme={null}
{
"triggerEvent": "DELEGATION_CREDENTIAL_ERROR",
"createdAt": "2024-01-01T12:00:00.000Z",
"payload": {
"error": {
"type": "CalendarAppDelegationCredentialError",
"message": "Invalid grant: account has been disabled or password changed"
},
"credential": {
"id": 100,
"type": "calendar_provider_type",
"appId": "calendar-app-identifier",
"delegationCredentialId": "00000000-0000-0000-0000-000000000000"
},
"user": {
"id": 1,
"email": "user@example.com",
"organizationId": 500
}
}
}
```
Fired when a pending secret has been verified and promoted to active. It does not fire when a replacement secret is only minted and staged as pending.
```json theme={null}
{
"triggerEvent": "DELEGATION_CREDENTIAL_SECRET_ROTATED",
"createdAt": "2024-01-01T12:00:00.000Z",
"payload": {
"delegationCredential": {
"id": "00000000-0000-0000-0000-000000000000",
"workspacePlatform": "office365",
"domain": "example.com",
"organizationId": 500,
"secretExpiresAt": "2024-06-30T00:00:00.000Z"
}
}
}
```
Fired only when an automatic secret mint or promotion has kept failing continuously for 48+ hours and the credential is marked as blocked (`secretRotationBlocked=true`). Transient failures before that 48-hour terminal window are retried automatically and do not fire a webhook. `error.code` is `SECRET_MINT_FAILED` when minting the replacement secret failed, or `SECRET_PROMOTION_FAILED` when a minted pending secret could not be verified with the workspace platform.
```json theme={null}
{
"triggerEvent": "DELEGATION_CREDENTIAL_SECRET_ROTATION_FAILED",
"createdAt": "2024-01-01T12:00:00.000Z",
"payload": {
"delegationCredential": {
"id": "00000000-0000-0000-0000-000000000000",
"workspacePlatform": "office365",
"domain": "example.com",
"organizationId": 500,
"secretExpiresAt": "2024-01-15T00:00:00.000Z"
},
"error": {
"code": "SECRET_PROMOTION_FAILED",
"message": "Failed to validate pending secret with workspace platform"
}
}
}
```
Fired for credentials that have opted out of automatic secret rotation (`optOutAutoSecretRotation=true`) whose active secret is approaching expiry — it is a manual-rotation reminder, not a signal that automatic rotation failed or was blocked. Terminal automatic-rotation failures (including an abandoned pending secret) fire `DELEGATION_CREDENTIAL_SECRET_ROTATION_FAILED` instead. Rotate the secret manually from the delegation credential settings when you receive this event.
```json theme={null}
{
"triggerEvent": "DELEGATION_CREDENTIAL_ROTATION_REQUIRED",
"createdAt": "2024-01-01T12:00:00.000Z",
"payload": {
"delegationCredential": {
"id": "00000000-0000-0000-0000-000000000000",
"workspacePlatform": "office365",
"domain": "example.com",
"organizationId": 500,
"secretExpiresAt": "2024-01-10T00:00:00.000Z"
}
}
}
```
This webhook is triggered when a team member reports that a booking from a routing form was assigned to the wrong person. This is useful for tracking routing accuracy and improving CRM data quality.
This webhook only fires for bookings that came through a routing form and have an assignment reason.
```json theme={null}
{
"triggerEvent": "WRONG_ASSIGNMENT_REPORT",
"createdAt": "2025-01-15T10:30:00.000Z",
"payload": {
"booking": {
"uid": "abc123def456",
"id": 12345,
"title": "Sales Call between Team and Lead",
"startTime": "2025-01-15T14:00:00.000Z",
"endTime": "2025-01-15T14:30:00.000Z",
"status": "ACCEPTED",
"eventType": {
"id": 100,
"title": "Sales Call",
"slug": "sales-call",
"teamId": 50
}
},
"report": {
"reportedBy": {
"id": 1,
"email": "reporter@example.com",
"name": "Reporter Name"
},
"routingReason": "Routed based on company size: Enterprise",
"guest": "lead@company.com",
"host": {
"email": "assigned-rep@example.com",
"name": "Assigned Rep"
},
"correctAssignee": "correct-rep@example.com",
"additionalNotes": "This lead should have been routed to the Enterprise team based on their company size."
}
}
}
```
Version `2026-07-27` extends `2021-10-20` — every payload shape and field above is
unchanged. The only difference: `BOOKING_CREATED`, `BOOKING_RESCHEDULED`,
`BOOKING_REASSIGNED`, `BOOKING_CANCELLED`, and `BOOKING_PAID` (when the payment accepted the booking) payloads
additionally carry `attendeeIcsContent` and `organizerIcsContent` — pre-generated `.ics`
calendar invite strings for a subscriber that sends its own confirmation/cancellation
emails in place of Cal.com's. Use `attendeeIcsContent` for emails addressed to the attendees
and `organizerIcsContent` for emails addressed to the organizer. Either field may be absent
from a payload depending on the booking's calendar and privacy settings — treat both as
optional.
```json theme={null}
{
"triggerEvent": "BOOKING_CREATED",
"createdAt": "2024-01-01T00:00:00.000Z",
"payload": {
"bookerUrl": "https://app.example.com",
"title": "Strategy Session between Organizer and Guest",
"startTime": "2024-01-01T10:00:00Z",
"endTime": "2024-01-01T10:15:00Z",
"organizer": {
"id": 1,
"name": "Organizer Name",
"email": "organizer@example.com",
"timeZone": "UTC"
},
"attendees": [
{
"email": "guest@example.com",
"name": "Guest User",
"timeZone": "UTC"
}
],
"uid": "unique-booking-id",
"iCalUID": "unique-identifier-string@example.com",
"iCalSequence": 0,
"status": "ACCEPTED",
"attendeeIcsContent": "BEGIN:VCALENDAR\nVERSION:2.0\nMETHOD:REQUEST\nBEGIN:VEVENT\nUID:unique-identifier-string@example.com\nSEQUENCE:0\nORGANIZER;CN=Organizer Name:mailto:organizer@example.com\nATTENDEE;CN=Guest User;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=TRUE:mailto:guest@example.com\nEND:VEVENT\nEND:VCALENDAR",
"organizerIcsContent": "BEGIN:VCALENDAR\nVERSION:2.0\nMETHOD:REQUEST\nBEGIN:VEVENT\nUID:unique-identifier-string@example.com\nSEQUENCE:0\nORGANIZER;CN=Organizer Name:mailto:organizer@example.com\nATTENDEE;CN=Organizer Name;ROLE=CHAIR;PARTSTAT=ACCEPTED;RSVP=FALSE:mailto:organizer@example.com\nEND:VEVENT\nEND:VCALENDAR"
}
}
```
Payload fields other than `attendeeIcsContent`/`organizerIcsContent` are truncated
above for brevity — see the `BOOKING_CREATED` example under the `2021-10-20` tab for
the full field list, all of which are unchanged here.
```json theme={null}
{
"triggerEvent": "BOOKING_RESCHEDULED",
"createdAt": "2024-01-01T10:00:00.000Z",
"payload": {
"title": "Strategy Session: Organizer & Guest",
"startTime": "2024-01-10T14:30:00Z",
"endTime": "2024-01-10T14:45:00Z",
"organizer": {
"id": 1,
"name": "Organizer Name",
"email": "organizer@example.com",
"timeZone": "UTC"
},
"attendees": [
{
"email": "guest@example.com",
"name": "Guest User",
"timeZone": "UTC"
}
],
"uid": "new-booking-unique-id",
"iCalUID": "unique-identifier-string@example.com",
"iCalSequence": 1,
"rescheduleUid": "previous-booking-unique-id",
"rescheduleStartTime": "2024-01-05T14:30:00Z",
"rescheduleEndTime": "2024-01-05T14:45:00Z",
"status": "ACCEPTED",
"attendeeIcsContent": "BEGIN:VCALENDAR\nVERSION:2.0\nMETHOD:REQUEST\nBEGIN:VEVENT\nUID:unique-identifier-string@example.com\nSEQUENCE:1\nORGANIZER;CN=Organizer Name:mailto:organizer@example.com\nATTENDEE;CN=Guest User;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=TRUE:mailto:guest@example.com\nEND:VEVENT\nEND:VCALENDAR",
"organizerIcsContent": "BEGIN:VCALENDAR\nVERSION:2.0\nMETHOD:REQUEST\nBEGIN:VEVENT\nUID:unique-identifier-string@example.com\nSEQUENCE:1\nORGANIZER;CN=Organizer Name:mailto:organizer@example.com\nATTENDEE;CN=Organizer Name;ROLE=CHAIR;PARTSTAT=ACCEPTED;RSVP=FALSE:mailto:organizer@example.com\nEND:VEVENT\nEND:VCALENDAR"
}
}
```
Payload fields other than `attendeeIcsContent`/`organizerIcsContent` are truncated
above for brevity — see the `BOOKING_RESCHEDULED` example under the `2021-10-20` tab for
the full field list, all of which are unchanged here.
```json theme={null}
{
"triggerEvent": "BOOKING_CANCELLED",
"createdAt": "2024-01-01T15:00:00.000Z",
"payload": {
"title": "Strategy Session: Organizer & Guest",
"startTime": "2024-01-04T10:00:00+00:00",
"endTime": "2024-01-04T10:15:00+00:00",
"organizer": {
"id": 1,
"name": "Organizer Name",
"email": "organizer@example.com",
"timeZone": "UTC"
},
"attendees": [
{
"email": "guest@example.com",
"name": "Guest User",
"timeZone": "UTC"
}
],
"uid": "unique-booking-identifier",
"bookingId": 200,
"cancellationReason": "I am no longer able to attend this session.",
"iCalUID": "unique-identifier@example.com",
"iCalSequence": 2,
"status": "CANCELLED",
"attendeeIcsContent": "BEGIN:VCALENDAR\nVERSION:2.0\nMETHOD:CANCEL\nBEGIN:VEVENT\nUID:unique-identifier@example.com\nSEQUENCE:2\nSTATUS:CANCELLED\nORGANIZER;CN=Organizer Name:mailto:organizer@example.com\nATTENDEE;CN=Guest User;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=TRUE:mailto:guest@example.com\nEND:VEVENT\nEND:VCALENDAR",
"organizerIcsContent": "BEGIN:VCALENDAR\nVERSION:2.0\nMETHOD:CANCEL\nBEGIN:VEVENT\nUID:unique-identifier@example.com\nSEQUENCE:2\nSTATUS:CANCELLED\nORGANIZER;CN=Organizer Name:mailto:organizer@example.com\nATTENDEE;CN=Organizer Name;ROLE=CHAIR;PARTSTAT=ACCEPTED;RSVP=FALSE:mailto:organizer@example.com\nEND:VEVENT\nEND:VCALENDAR"
}
}
```
Payload fields other than `attendeeIcsContent`/`organizerIcsContent` are truncated
above for brevity — see the `BOOKING_CANCELLED` example under the `2021-10-20` tab for
the full field list, all of which are unchanged here.
```json theme={null}
{
"triggerEvent": "BOOKING_PAID",
"createdAt": "2024-01-01T12:00:00.000Z",
"payload": {
"type": "Consultation Session",
"title": "Consultation Session: Organizer & Guest",
"startTime": "2024-01-02T10:00:00.000Z",
"endTime": "2024-01-02T10:30:00.000Z",
"organizer": {
"id": 1,
"name": "Organizer Name",
"email": "organizer@example.com",
"timeZone": "UTC"
},
"attendees": [
{
"name": "Guest User",
"email": "guest@example.com",
"timeZone": "UTC"
}
],
"iCalUID": "unique-ical-uid@example.com",
"iCalSequence": 0,
"uid": "unique-booking-uid",
"bookingId": 100,
"paymentId": 500,
"metadata": {
"identifier": "platform.name",
"bookingId": 100,
"eventTypeId": 50,
"bookerEmail": "guest@example.com",
"eventTitle": "Consultation Session",
"externalId": "pi_0000000000000000"
},
"status": "ACCEPTED",
"attendeeIcsContent": "BEGIN:VCALENDAR\nVERSION:2.0\nMETHOD:REQUEST\nBEGIN:VEVENT\nUID:unique-ical-uid@example.com\nSEQUENCE:0\nORGANIZER;CN=Organizer Name:mailto:organizer@example.com\nATTENDEE;CN=Guest User;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=TRUE:mailto:guest@example.com\nEND:VEVENT\nEND:VCALENDAR",
"organizerIcsContent": "BEGIN:VCALENDAR\nVERSION:2.0\nMETHOD:REQUEST\nBEGIN:VEVENT\nUID:unique-ical-uid@example.com\nSEQUENCE:0\nORGANIZER;CN=Organizer Name:mailto:organizer@example.com\nATTENDEE;CN=Organizer Name;ROLE=CHAIR;PARTSTAT=ACCEPTED;RSVP=FALSE:mailto:organizer@example.com\nEND:VEVENT\nEND:VCALENDAR"
}
}
```
Payload fields other than `attendeeIcsContent`/`organizerIcsContent` are truncated
above for brevity — see the `BOOKING_PAID` example under the `2021-10-20` tab for the
full field list, all of which are unchanged here. These fields are present only when
the payment resulted in an `ACCEPTED` booking; rejected or failed payments carry no ICS.
### Booking No-show Updated webhook payload
This webhook is triggered when an attendee is manually marked or unmarked as no-show on the `/bookings/past` page.
**Example payload when marking an attendee as no-show:**
```json theme={null}
{
"triggerEvent": "BOOKING_NO_SHOW_UPDATED",
"createdAt": "2025-12-01T12:34:34.774Z",
"payload": {
"message": "test@example.com marked as no-show",
"attendees": [
{
"email": "test@example.com",
"noShow": true
}
],
"bookingUid": "5vQFqxDFMjdgKGMijqtqRw",
"bookingId": 31
}
}
```
**Example payload when unmarking an attendee as no-show:**
```json theme={null}
{
"triggerEvent": "BOOKING_NO_SHOW_UPDATED",
"createdAt": "2025-12-01T12:38:31.663Z",
"payload": {
"message": "test@example.com unmarked as no-show",
"attendees": [
{
"email": "test@example.com",
"noShow": false
}
],
"bookingUid": "5vQFqxDFMjdgKGMijqtqRw",
"bookingId": 31
}
}
```
### Cal Video No-Show Detection webhooks
These webhooks are triggered **automatically** when hosts or guests don't join a Cal Video meeting within the configured time after the booking starts. Unlike `BOOKING_NO_SHOW_UPDATED` which requires manual action, these are detected automatically by checking Cal Video participant data.
These webhooks only work for bookings that use Cal Video as the meeting location.
**`Example payload when host didn't join (AFTER_HOSTS_CAL_VIDEO_NO_SHOW):`**
```json theme={null}
{
"triggerEvent": "AFTER_HOSTS_CAL_VIDEO_NO_SHOW",
"createdAt": "2025-12-01T15:23:30.320Z",
"payload": {
"title": "30min between Owner 1 and test",
"bookingId": 32,
"bookingUid": "qVyXscm1ryg2QoQ4K8uHLW",
"startTime": "2025-12-02T04:00:00.000Z",
"attendees": [
{
"id": 32,
"email": "test@example.com",
"name": "test",
"timeZone": "Asia/Kolkata",
"phoneNumber": null,
"locale": "en",
"bookingId": 32,
"noShow": false
}
],
"endTime": "2025-12-02T04:30:00.000Z",
"participants": [],
"hostEmail": "owner1-dunder@example.com",
"eventType": {
"id": 50,
"teamId": null,
"parentId": null,
"calVideoSettings": null
},
"webhook": {
"id": "2bdfc20b-aa4a-4863-a499-932cb1d4e69a",
"subscriberUrl": "https://webhook.site/example",
"appId": null,
"time": 5,
"timeUnit": "MINUTE",
"eventTriggers": [
"AFTER_HOSTS_CAL_VIDEO_NO_SHOW",
"AFTER_GUESTS_CAL_VIDEO_NO_SHOW"
],
"payloadTemplate": null
},
"message": "Host with email owner1-dunder@example.com didn't join the call or didn't join before 2025-12-02 04:05:00 +00:00"
}
}
```
**`Example payload when guest didn't join (AFTER_GUESTS_CAL_VIDEO_NO_SHOW):`**
```json theme={null}
{
"triggerEvent": "AFTER_GUESTS_CAL_VIDEO_NO_SHOW",
"createdAt": "2025-12-01T15:23:30.323Z",
"payload": {
"title": "30min between Owner 1 and test",
"bookingId": 32,
"bookingUid": "qVyXscm1ryg2QoQ4K8uHLW",
"startTime": "2025-12-02T04:00:00.000Z",
"attendees": [
{
"id": 32,
"email": "test@example.com",
"name": "test",
"timeZone": "Asia/Kolkata",
"phoneNumber": null,
"locale": "en",
"bookingId": 32,
"noShow": false
}
],
"endTime": "2025-12-02T04:30:00.000Z",
"participants": [],
"eventType": {
"id": 50,
"teamId": null,
"parentId": null,
"calVideoSettings": null
},
"webhook": {
"id": "2bdfc20b-aa4a-4863-a499-932cb1d4e69a",
"subscriberUrl": "https://webhook.site/example",
"appId": null,
"time": 5,
"timeUnit": "MINUTE",
"eventTriggers": [
"AFTER_HOSTS_CAL_VIDEO_NO_SHOW",
"AFTER_GUESTS_CAL_VIDEO_NO_SHOW"
],
"payloadTemplate": null
},
"message": "Guest didn't join the call or didn't join before 2025-12-02 04:05:00 +00:00"
}
}
```
### Meeting started and meeting ended webhooks
The `MEETING_STARTED` and `MEETING_ENDED` webhooks are **time-delayed** — they fire automatically at the booking's scheduled start time and end time, respectively. You do not need to take any action to trigger them beyond subscribing to these events.
**Key behaviors:**
* **Automatic scheduling**: When a booking is confirmed, Cal.com schedules the `MEETING_STARTED` webhook to fire at the booking's `startTime` and the `MEETING_ENDED` webhook to fire at the booking's `endTime`.
* **Cancellation handling**: If a booking is cancelled or rescheduled, any pending `MEETING_STARTED` and `MEETING_ENDED` webhooks for that booking are automatically cancelled. When a booking is rescheduled, new webhooks are scheduled for the updated times.
* **Flat payload format**: Unlike other webhook events, these two events use a flat payload structure. The booking data is spread at the top level alongside `triggerEvent`, rather than nested inside a `payload` object. See the [example payloads](#webhook-payload-reference) for the exact structure.
* **No custom payload template support**: Custom payload templates are not applied to `MEETING_STARTED` and `MEETING_ENDED` webhooks. The payload is always sent as raw JSON.
If you are migrating from a system that consumes other webhook events, note that the payload shape for `MEETING_STARTED` and `MEETING_ENDED` differs from events like `BOOKING_CREATED`. Ensure your webhook handler accounts for the flat structure.
### Delegation credential secret rotation webhooks
Delegation credentials let an organization act on behalf of its members with a workspace platform. Microsoft 365 (Azure Entra ID) delegation credentials authenticate with a client secret that expires, so Cal.com periodically rotates it and emits webhooks so administrators can track rotation health. Google delegation credentials don't have an expiring secret and are not part of this rotation lifecycle.
Subscribe to these events on an organization-scoped webhook to be notified about the delegation credential rotation lifecycle:
* **`DELEGATION_CREDENTIAL_SECRET_ROTATED`** — a pending secret was verified and promoted, and is now active.
* **`DELEGATION_CREDENTIAL_SECRET_ROTATION_FAILED`** — an automatic mint or promotion attempt has kept failing for 48+ hours and rotation is now blocked for that credential (`error.code` is `SECRET_MINT_FAILED` or `SECRET_PROMOTION_FAILED`). Transient failures before that terminal window are retried automatically and do not fire a webhook.
* **`DELEGATION_CREDENTIAL_ROTATION_REQUIRED`** — a reminder for credentials that opted out of automatic rotation (`optOutAutoSecretRotation=true`) whose active secret is nearing expiry. This event does not indicate that automatic rotation failed or was blocked — see `DELEGATION_CREDENTIAL_SECRET_ROTATION_FAILED` for that. In both cases, an administrator must rotate the secret manually from the delegation credential settings.
`DELEGATION_CREDENTIAL_SECRET_ROTATED` and `DELEGATION_CREDENTIAL_SECRET_ROTATION_FAILED` fire only for delegation credentials with automatic secret rotation enabled. `DELEGATION_CREDENTIAL_ROTATION_REQUIRED` fires only for delegation credentials that have opted out of automatic secret rotation. They are delivered asynchronously with retries on transient network errors and HTTP 408, 429, and 5xx responses.
### Verifying the authenticity of the received payload
Simply add a new **secret key** to your webhook configuration and save it.
The webhook will trigger when an event is created, cancelled, rescheduled, or when a meeting ends.
Use the **secret key** to create an HMAC, and update it with the webhook payload received to generate an SHA256 hash.
Compare the hash received in the header of the webhook `(x-cal-signature-256)` with the one you created using the **secret key** and the body of the payload. If they don't match, the received payload has been adulterated and cannot be trusted.
### Adding a custom payload template
Customizable webhooks are a great way reduce the development effort and in many cases remove the need for a developer to build an additional integration service.
An example of a custom payload template is provided here:
```json theme={null}
{
"content": "A new event has been scheduled",
"type": "{{type}}",
"name": "{{title}}",
"organizer": "{{organizer.name}}",
"booker": "{{attendees.0.name}}"
}
```
where `{{type}}` represents the event type slug and `{{title}}` represents the title of the event type. Note that the variables should be added with a double parenthesis as shown above. Here’s a breakdown of the payload that you would receive via an incoming webhook, with an exhaustive list of all the supported variables provided below:
#### Webhook variable list
| Variable | Type | Description |
| --------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| triggerEvent | String | The name of the trigger event \[BOOKING\_CREATED, BOOKING\_RESCHEDULED, BOOKING\_CANCELLED, MEETING\_ENDED, BOOKING\_REJECTED, BOOKING\_REQUESTED, BOOKING\_PAYMENT\_INITIATED, BOOKING\_PAID, MEETING\_STARTED, RECORDING\_READY, INSTANT\_MEETING, INSTANT\_MEETING\_ACCEPTED, FORM\_SUBMITTED, BOOKING\_NO\_SHOW\_UPDATED, AFTER\_HOSTS\_CAL\_VIDEO\_NO\_SHOW, AFTER\_GUESTS\_CAL\_VIDEO\_NO\_SHOW, WRONG\_ASSIGNMENT\_REPORT, DELEGATION\_CREDENTIAL\_ERROR, DELEGATION\_CREDENTIAL\_SECRET\_ROTATED, DELEGATION\_CREDENTIAL\_SECRET\_ROTATION\_FAILED, DELEGATION\_CREDENTIAL\_ROTATION\_REQUIRED] |
| createdAt | Datetime | The time of the webhook |
| type | String | The event type slug |
| title | String | The event type name |
| startTime | Datetime | The event's start time |
| endTime | Datetime | The event's end time |
| description | String | The event's description as described in the event type settings |
| location | String | Location of the event |
| organizer | Person | The organizer of the event |
| attendees | Person\[] | The event booker & any guests. For seated event types, this contains only the attendee for the specific seat that triggered the webhook. |
| uid | String | The UID of the booking |
| rescheduleUid | String | The UID for rescheduling |
| cancellationReason | String | Reason for cancellation |
| rejectionReason | String | Reason for rejection |
| team.name | String | Name of the team booked |
| team.members | String\[] | Members of the team booked |
| metadata | JSON | Contains metadata of the booking, including the meeting URL (videoCallUrl) in case of Google Meet and Cal Video |
| platformOAuthClientId | String \| null | The platform OAuth client that owns the event (`null` for non-platform events). Present on builder-produced payloads for every payload version; omitted from Zapier deliveries, custom payload templates that do not reference the field, the `AFTER_HOSTS_CAL_VIDEO_NO_SHOW` / `AFTER_GUESTS_CAL_VIDEO_NO_SHOW` bodies (delivered verbatim from the fire-time blob, never built) and the legacy `FORM_SUBMITTED_NO_EVENT` fallback delivery. |
`platformOAuthClientId` names the platform OAuth client that owns the **event**, which is not the same thing as the `oAuthClientId` field on the API v2 webhook resource — that one names the client that owns the **subscription**. In a custom payload template for the booking triggers the value is a root variable, `{{platformOAuthClientId}}`, not `{{payload.platformOAuthClientId}}`.
#### Person Structure
| Variable | Type | Description |
| ---------------- | ------ | ---------------------------------------------------------------------- |
| name | String | Name of the individual |
| email | Email | Email of the individual |
| timezone | String | Timezone of the individual (e.g., "America/New\_York", "Asia/Kolkata") |
| language?.locale | String | Locale of the individual (e.g., "en", "fr") |
# Setting up mailtrap for email testing
Source: https://cal.com/docs/developing/guides/email/setup-mailtrap-for-email-testing
Mailtrap is a versatile tool for testing email notifications without sending them to the real users of your application. It's especially useful for testing and ensuring that the system sends out accurate emails in a secure and efficient manner.
To set up Mailtrap for email testing on Cal.com, follow these steps:
The first step is to register an account with Mailtrap. It offers a free plan for small testing needs, and also paid plans for larger organizations.
After registering, create an inbox for your application. Each inbox comes with its SMTP and POP3 credentials, which can be used to send and retrieve emails.
Change the tab to nodemailer and copy the information to `.env`:
```
EMAIL_SERVER_HOST='sandbox.smtp.mailtrap.io'
EMAIL_SERVER_PORT=2525
EMAIL_SERVER_USER='XXX'
EMAIL_SERVER_PASSWORD='XXX'
```
Please remember to change the user and password and replace 'XXX' above with their respective values.
Set the `EMAIL_FROM` environment variable in the `.env` file. For example:
```
EMAIL_FROM='notifications@yourselfhostedcal.com'
```
Once your instance is configured, you can start sending test emails. These emails are "trapped" in the Mailtrap inbox, enabling you to verify email content, headers, and attachments.
Mailtrap provides features to analyze and debug your emails. You can check whether the HTML displays correctly, ensure the email is not treated as spam, and also see if it contains any broken links, invalid format, or incorrect images.
Happy testing!
# Customize the embed with CSS variables
Source: https://cal.com/docs/developing/guides/embeds/customize-embed-css-variables
You can fully customize the look and feel of the Cal.com embed by passing CSS variables through the `Cal("ui", { cssVarsPerTheme })` API. This lets you match the Booker to your brand's colors, spacing, and border radius — for both light and dark themes.
Pass the variable name **without** the `--` prefix as the key in `cssVarsPerTheme`. For example, to override `--cal-brand`, use `"cal-brand": "#6F61C0"`.
## Variable reference
### Brand
| Key | Purpose |
| -------------------- | --------------------------------------------------------------------- |
| `cal-brand` | Primary brand color — selected date circle, confirm button bg |
| `cal-brand-emphasis` | Brand hover state — confirm button hover |
| `cal-brand-text` | Text on brand surfaces — "Confirm" button label, selected date number |
| `cal-brand-subtle` | Lighter brand accent — secondary indicators |
| `cal-brand-accent` | Contrasting accent on brand buttons — icon color on brand bg |
### Text
| Key | Purpose |
| ------------------- | ------------------------------------------------------------------ |
| `cal-text` | Default body text — event description, time slot labels "14:30" |
| `cal-text-emphasis` | High-priority text — event title, month header "April 2026" |
| `cal-text-subtle` | Secondary text — day-of-week headers "SUN MON TUE", timezone label |
| `cal-text-muted` | Lowest contrast text — disabled date numbers, placeholder text |
| `cal-text-inverted` | Text on dark/inverted backgrounds — tooltips |
### Text semantic
| Key | Purpose |
| ----------------------------- | ------------------------------------------------- |
| `cal-text-semantic-info` | Info alert text — "Scheduling information" notice |
| `cal-text-semantic-attention` | Warning alert text — "Limited spots left" notice |
| `cal-text-semantic-error` | Error alert text — "This time is unavailable" |
### Text status
These feed the `text-info` / `text-error` / `text-success` / `text-attention` utility classes as well as matching `fill-*` icon colors within the Booker (for example, Badge variants and form validation messages).
| Key | Purpose |
| -------------------- | ----------------------------------------------------------- |
| `cal-text-info` | Info badge text and icon fill |
| `cal-text-success` | Success badge text and icon fill |
| `cal-text-attention` | Attention/warning badge text and icon fill |
| `cal-text-error` | Error text and icon fill — form validation "Required field" |
### Background
| Key | Purpose |
| ------------------ | ------------------------------------------------------------- |
| `cal-bg` | Main booker background — the white canvas behind everything |
| `cal-bg-emphasis` | Highlighted surface — hovered date cell, active date range bg |
| `cal-bg-subtle` | Secondary surface — time slot hover bg, card backgrounds |
| `cal-bg-muted` | Faintest surface — unavailable day bg, gradient fade edges |
| `cal-bg-inverted` | Inverted surface — dark overlay backgrounds, tooltips |
| `cal-bg-attention` | Warning badge background — "orange" badge variant |
| `cal-bg-error` | Error badge/alert background — "red" badge variant |
### Background semantic
| Key | Purpose |
| ---------------------------------- | ------------------------------- |
| `cal-bg-semantic-info-subtle` | Subtle info alert background |
| `cal-bg-semantic-attention-subtle` | Subtle warning alert background |
| `cal-bg-semantic-error-subtle` | Subtle error alert background |
### Border
| Key | Purpose |
| -------------------------------------- | ---------------------------------------------------------- |
| `cal-border` | Default dividers — calendar grid lines, card borders |
| `cal-border-emphasis` | Strong borders — focused input ring, active section border |
| `cal-border-subtle` | Soft borders — date picker separator, time slot dividers |
| `cal-border-muted` | Faintest borders — inner column dividers |
| `cal-border-error` | Error input border — red ring on invalid form field |
| `cal-border-semantic-error` | Semantic error border — error banner outline |
| `cal-border-semantic-attention-subtle` | Warning banner border |
| `cal-border-semantic-error-subtle` | Subtle error banner border |
### Booker border
| Key | Purpose |
| ------------------------- | ------------------------------------------------------------------ |
| `cal-border-booker` | Outer border color of the entire booker widget |
| `cal-border-booker-width` | Thickness of the outer booker border — for example, "1px" or "3px" |
### Border radius
| Key | Purpose |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `radius` | Base radius — controls all border-radius sizes across the booker. All sized variants (sm, md, lg, xl) are derived from this value. Set to `0px` for fully sharp corners. |
### Spacing
| Key | Purpose |
| --------- | ----------------------------------------------------------------- |
| `spacing` | Base spacing unit — all padding/margin/gap values scale from this |
## Full example
```js theme={null}
Cal("ui", {
theme: "light",
hideEventTypeDetails: false,
cssVarsPerTheme: {
light: {
// ── Brand ──
"cal-brand": "#6F61C0", // Selected date circle, confirm button bg
"cal-brand-emphasis": "#5A4EA6", // Confirm button hover state
"cal-brand-text": "#FFFFFF", // Text on brand surfaces — "Confirm" label, selected date number
"cal-brand-subtle": "#B8B0E0", // Lighter brand accent for secondary indicators
"cal-brand-accent": "#FFFFFF", // Contrasting accent on brand buttons
// ── Text ──
"cal-text": "#6F61C0", // Default body text — event description, time slot labels
"cal-text-emphasis": "#4D408D", // High-priority text — event title, month header
"cal-text-subtle": "#8A7FCC", // Secondary text — day-of-week headers, timezone
"cal-text-muted": "#C0B8FF", // Lowest contrast text — disabled dates, placeholders
"cal-text-inverted": "#FFFFFF", // Text on dark/inverted backgrounds
// ── Text Semantic ──
"cal-text-semantic-info": "#3B3080", // Info alert text
"cal-text-semantic-attention": "#8B3A1A", // Warning alert text
"cal-text-semantic-error": "#7C2020", // Error alert text
// ── Text Status ──
"cal-text-info": "#3B3080", // Info badge text and icon fill
"cal-text-success": "#1A5C30", // Success badge text and icon fill
"cal-text-attention": "#8B3A1A", // Attention badge text and icon fill
"cal-text-error": "pink", // Error text and icon fill — form validation
// ── Background ──
"cal-bg": "#FFFFFF", // Main booker background
"cal-bg-emphasis": "#E1DFFF", // Highlighted surface — hovered date, active range
"cal-bg-subtle": "#F0EEFF", // Secondary surface — time slot hover, cards
"cal-bg-muted": "#F8F7FF", // Faintest surface — unavailable day bg, gradient edges
"cal-bg-inverted": "#2D2554", // Inverted surface — dark overlays, tooltips
"cal-bg-attention": "#FFF3E0", // Warning badge background
"cal-bg-error": "#FFE8EC", // Error badge/alert background
// ── Background Semantic ──
"cal-bg-semantic-info-subtle": "#F0EDFF", // Subtle info alert bg
"cal-bg-semantic-attention-subtle": "#FFF3E0", // Subtle warning alert bg
"cal-bg-semantic-error-subtle": "#FFE8EC", // Subtle error alert bg
// ── Border ──
"cal-border": "#A090E0", // Default dividers — calendar grid lines
"cal-border-emphasis": "#4D408D", // Strong borders — focused inputs
"cal-border-subtle": "#A090E0", // Soft borders — separators, dividers
"cal-border-muted": "#D6D0F0", // Faintest borders
"cal-border-error": "#FFAAAA", // Error input border
"cal-border-semantic-error": "#FFAAAA", // Error banner outline
"cal-border-semantic-attention-subtle": "#FFD4A0", // Warning banner border
"cal-border-semantic-error-subtle": "#FFAAAA", // Subtle error banner border
// ── Booker Border ──
"cal-border-booker": "#A090E0", // Outer border of the entire booker widget
"cal-border-booker-width": "3px", // Thickness of the outer booker border
// ── Border Radius ──
"radius": "0.25rem", // Base radius — all sizes derive from this (default 10px)
// ── Spacing ──
"spacing": "1px", // Base spacing unit — all padding/margin/gap scale from this
},
dark: {
// ── Brand ──
"cal-brand": "#A090E0",
"cal-brand-emphasis": "#C0B8FF",
"cal-brand-text": "#1A1540",
"cal-brand-subtle": "#5A4EA6",
"cal-brand-accent": "#1A1540",
// ── Text ──
"cal-text": "#D6D0F0",
"cal-text-emphasis": "#F0EEFF",
"cal-text-subtle": "#A090E0",
"cal-text-muted": "#8A7FCC",
"cal-text-inverted": "#1A1540",
// ── Text Semantic ──
"cal-text-semantic-info": "#A8A0F0",
"cal-text-semantic-attention": "#E0A060",
"cal-text-semantic-error": "#F08080",
// ── Text Status ──
"cal-text-info": "#C0B8FF",
"cal-text-success": "#80E0B0",
"cal-text-attention": "#F0C090",
"cal-text-error": "#FFB0B0",
// ── Background ──
"cal-bg": "#1A1540",
"cal-bg-emphasis": "#2D2554",
"cal-bg-subtle": "#231E4A",
"cal-bg-muted": "#1E1845",
"cal-bg-inverted": "#F0EEFF",
"cal-bg-attention": "#6E2D14",
"cal-bg-error": "#601818",
// ── Background Semantic ──
"cal-bg-semantic-info-subtle": "#1E1860",
"cal-bg-semantic-attention-subtle": "#3E1E0A",
"cal-bg-semantic-error-subtle": "#3E1010",
// ── Border ──
"cal-border": "#4D408D",
"cal-border-emphasis": "#8A7FCC",
"cal-border-subtle": "#3B3080",
"cal-border-muted": "#231E4A",
"cal-border-error": "#802020",
"cal-border-semantic-error": "#802020",
"cal-border-semantic-attention-subtle": "#6E2D14",
"cal-border-semantic-error-subtle": "#802020",
// ── Booker Border ──
"cal-border-booker": "#4D408D",
"cal-border-booker-width": "3px",
// ── Border Radius ──
"radius": "0.25rem", // Base radius — all sizes derive from this (default 10px)
// ── Spacing ──
"spacing": "1px",
},
},
});
```
# Embed Events
Source: https://cal.com/docs/developing/guides/embeds/embed-events
For comprehensive documentation on embed events including usage examples and all available events, please refer to the [Cal.com Help Center - Embed Events](https://cal.com/help/embedding/embed-events).
## Internal Events
These events are used internally by the embed system for communication between the iframe and parent window. They are prefixed with `__` and are not intended for external use.
| Action | Description | Properties |
| ---------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| \_\_iframeReady | Fired when the embedded iframe is ready to communicate with parent snippet. | `isPrerendering: boolean` // Whether the iframe is in prerender mode |
| \_\_windowLoadComplete | Tells that window load for iframe is complete. | None |
| \_\_dimensionChanged | Tells that dimensions of the content inside the iframe changed. | `iframeWidth: number` `iframeHeight: number` `isFirstTime: boolean` // Whether this is the first dimension change |
| \_\_routeChanged | Fired when the route changes within the iframe. | None |
| \_\_closeIframe | Fired when the iframe should be closed. | None |
| \_\_connectInitiated | Fired when connection to a prerendered iframe is initiated. | None |
| \_\_connectCompleted | Fired when connection to a prerendered iframe is completed. | None |
| \_\_scrollByDistance | Instructs the parent to scroll by a specific distance. | `distance: number` // Distance in pixels to scroll by |
Events that start with `__` are internal and should not be relied upon for external integrations as they may change without notice.
To get more details on how Embed actually works, you can refer to this [Embed Flowchart](https://www.figma.com/file/zZ5oaUpg12Fuu5mGZrPlP5/Embed-Flowchart).
# How to Add a New Booking Chart to Cal.com Insights Page
Source: https://cal.com/docs/developing/guides/insights/add-new-booking-charts
This guide walks you through creating a new booking chart component for the insights page, covering the entire stack from UI component to backend service.
## Overview
The insights booking system follows this architecture:
```
UI Component → tRPC Handler → Insights Service → Database Query → Response
```
Create your chart component in `packages/features/insights/components/booking/`:
```typescript theme={null}
// packages/features/insights/components/booking/MyNewChart.tsx
import { LineChart, XAxis, YAxis, CartesianGrid, Tooltip, Line, ResponsiveContainer } from "recharts";
import { useLocale } from "@calcom/i18n/useLocale";
import { trpc } from "@calcom/trpc/react";
import { useInsightsBookingParameters } from "../../hooks/useInsightsBookingParameters";
import { ChartCard } from "../ChartCard";
import { LoadingInsight } from "../LoadingInsights";
export const MyNewChart = () => {
const { t } = useLocale();
const insightsBookingParams = useInsightsBookingParameters();
const { data, isSuccess, isPending } = trpc.viewer.insights.myNewChartData.useQuery(insightsBookingParams, {
staleTime: 180000, // 3 minutes
refetchOnWindowFocus: false,
trpc: { context: { skipBatch: true } },
});
if (isPending) return ;
return (
{isSuccess && data?.length > 0 ? (
) : (
{t("no_data_yet")}
)}
);
};
```
Update the booking components index file:
```typescript theme={null}
// packages/features/insights/components/booking/index.ts
export { AverageEventDurationChart } from "./AverageEventDurationChart";
export { BookingKPICards } from "./BookingKPICards";
// ... existing exports
export { MyNewChart } from "./MyNewChart"; // Add this line
```
Add your component to the main insights page:
```typescript theme={null}
// apps/web/modules/insights/insights-view.tsx
import {
AverageEventDurationChart,
BookingKPICards, // ... existing imports
MyNewChart, // Add this import
} from "@calcom/features/insights/components/booking";
export default function InsightsPage() {
// ... existing code
return (
{/* Existing components */}
{/* Add your new chart */}
{/* Other existing components */}
);
}
```
Add the tRPC endpoint in the insights router using the `getInsightsBookingService()` DI container function:
```typescript theme={null}
// packages/features/insights/server/trpc-router.ts
import { bookingRepositoryBaseInputSchema } from "@calcom/features/insights/server/raw-data.schema";
import { userBelongsToTeamProcedure } from "@calcom/trpc/server/procedures/authedProcedure";
import { TRPCError } from "@trpc/server";
export const insightsRouter = router({
// ... existing procedures
myNewChartData: userBelongsToTeamProcedure
.input(bookingRepositoryBaseInputSchema)
.query(async ({ ctx, input }) => {
// `createInsightsBookingService` is defined at the root level in this file
const insightsBookingService = createInsightsBookingService(ctx, input);
try {
return await insightsBookingService.getMyNewChartData();
} catch (e) {
throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" });
}
}),
});
```
Add your new method to the `InsightsBookingBaseService` class:
```typescript theme={null}
// packages/lib/server/service/InsightsBookingBaseService.ts
export class InsightsBookingBaseService {
// ... existing methods
async getMyNewChartData() {
const baseConditions = await this.getBaseConditions();
// Example: Get booking counts by day using raw SQL for performance
// Note: Use Prisma.sql for the entire query (Prisma v6 requirement)
// Prisma v6 no longer allows mixing template literals with Prisma.sql fragments
const query = Prisma.sql`
SELECT
DATE("createdAt") as date,
COUNT(*)::int as "bookingsCount"
FROM "BookingTimeStatusDenormalized"
WHERE ${baseConditions}
GROUP BY DATE("createdAt")
ORDER BY date ASC
`;
const data = await this.prisma.$queryRaw<
Array<{
date: Date;
bookingsCount: number;
}>
>(query);
// Transform the data for the chart
return data.map((item) => ({
date: item.date.toISOString().split("T")[0], // Format as YYYY-MM-DD
value: item.bookingsCount,
}));
}
}
```
## Best Practices
1. **Use `getInsightsBookingService()`**: Always use the DI container function for consistent service creation
2. **Raw SQL for Performance**: Use `$queryRaw` for complex aggregations and better performance
3. **Base Conditions**: Always use `await this.getBaseConditions()` for proper filtering and permissions
4. **Error Handling**: Wrap service calls in try-catch blocks with `TRPCError`
5. **Loading States**: Always show loading indicators with `LoadingInsight`
6. **Consistent Styling**: Use `recharts` for new charts
7. **Date Handling**: Use `getDateRanges()` and `getTimeView()` for time-based charts
8. **Prisma v6 Compatibility**: Use `Prisma.sql` for the entire query instead of mixing template literals with SQL fragments
# Local Development
Source: https://cal.com/docs/developing/local-development
Cal.com runs with pretty minimal hardware requirements by itself. The most intensive part for the software is when you actually build the software, but once it's running it's relatively lightweight.
Cal.com works with a very large range of operating systems, as it only requires JavaScript execution to run. **Cal.com is known to work well with Windows, Mac, Linux and BSD.** Although they do work well on all of them, for production deployments we would suggest Linux as the ideal platform. Any operating system that runs Node.js should be able to work too, but these are some of the common operating systems that we know work well.
To run Cal.com, you need to install a few things. **Node.js, yarn, Git and PostgreSQL**. We use **Prisma** for database maintenance, and is one of the dependencies. We won’t publish installation guides for these as they have their own resources available on the internet. If you're on Linux/BSD, all of these things should be readily available on your package manager. Your best bet is searching for something like **Debian 12 PostgreSQL**, which will give you a guide to installing and configuring PostgreSQL on Debian Linux 12.
To ensure optimal performance and compatibility, we highly recommend using Node.js version 18 for your development environment. This version provides the best balance of stability, features, and security for this project. Please make sure to update your Node.js installation if necessary.
## Development Setup & Production Build
1. **First, you git clone the repository** with the following command, so you have a copy of the code.
```git theme={null}
git clone https://github.com/calcom/cal.com.git
```
If you are on windows, you would need to use the following command when cloning, with **admin privileges**:
```git theme={null}
git clone -c core.symlinks=true https://github.com/calcom/cal.com.git
```
2. Then, go into the directory you just cloned with
```bash theme={null}
cd cal.com
```
and run
```bash theme={null}
yarn
```
to install all of the dependencies. Essentially, dependencies are just things that Cal.com needs to install to be able to work.
3. Then, you just need to set up a couple of things. For that, we use a `.env` file. We just need to copy and paste the `.env.example` file and rename the copy to `.env`. Here you'll have a template with comments showing you the settings you need/might want to set.
4. Next, use the command
```bash theme={null}
openssl rand -base64 32
```
(or another secret generator tool if you prefer) to generate a key and add it under `NEXTAUTH_SECRET` in the .env file.
5. You'll also want to fill out the `.env.appStore` file similar to the `.env` file as this includes keys to enable apps.
**Development tips**
> Add `NEXT_PUBLIC_DEBUG=1` anywhere in your `.env` to get logging information for all the queries and mutations driven by **trpc**.
```bash theme={null}
echo 'NEXT_PUBLIC_DEBUG=1' >> .env
```
> For email testing, set it to "1" if you need to email checks in E2E tests locally. Make sure to run mailhog container manually or with `yarn dx`.
```bash theme={null}
echo 'E2E_TEST_MAILHOG_ENABLED=1' >> .env
```
### E2E Testing
Be sure to set the environment variable `NEXTAUTH_URL` to the correct value. If you are running locally, as the documentation within `.env.example` mentions, the value should be `http://localhost:3000`.
In a terminal just run:
```bash theme={null}
yarn test-e2e
```
To open last HTML report run:
```bash theme={null}
yarn playwright show-report test-results/reports/playwright-html-report
```
### Manual setup
1. Configure environment variables in the .env file. Replace ``, ``, ``, `` with their applicable values
```bash theme={null}
DATABASE_URL='postgresql://:@:'
```
2. Set a 24 character random string in your .env file for the `CALENDSO_ENCRYPTION_KEY` (You can use a command like
```bash theme={null}
openssl rand -base64 24
```
to generate one).
3. Set up the database using the Prisma schema (found in `packages/prisma/schema.prisma`)
```bash theme={null}
yarn workspace @calcom/prisma db-deploy
```
4. Run (in development mode)
```bash theme={null}
yarn dev
```
When you're testing out the enterprise features locally, you should see a warning shown in the image below, clarifying the need to purchase a license for such features in production.
## **Development quick start with** **`yarn dx`**
> * **Requires Docker and Docker Compose to be installed**
>
> * Will start a local Postgres instance with a few test users - the credentials will be logged in the console
```bash theme={null}
yarn dx
```
## **Cron Jobs**
There are a few features which require cron job setup. At cal.com, the cron jobs are found in the following directory:
```bash theme={null}
/apps/web/app/api/cron
```
## App store seeder
We recommend using the admin UI/wizard instead of the seeder to enable app store apps
## API
#### Step 1
Copy the .env files from their respective example files depending on the version of the API (v1 or v2):
```bash theme={null}
cp apps/api/{version}/.env.example apps/api/{version}/.env
cp .env.example .env
```
#### Step 2
Install packages with yarn:
```bash theme={null}
yarn
```
### Running API server
Run the API v1 with yarn:
```bash theme={null}
yarn workspace @calcom/api dev
```
Run the API v2 with yarn:
Please note API v2 requires the database running in docker
```bash theme={null}
yarn workspace @calcom/api-v2 dev
```
On windows, you would need to update the script to explicitly set port to 3003 and run yarn dev under `apps/api/package.json` So, it should look something like this after the changes:
`"dev": "set PORT=3003 && next dev"` Now, running `yarn workspace @calcom/api dev` should start the server.
Open [http://localhost:3003](http://localhost:3003/) with your browser to see the result.
If you wish to test how API works locally, please [check out our guide here](/docs/developing/guides/api/how-to-setup-api-in-a-local-instance).
# Code styling
Source: https://cal.com/docs/developing/open-source-contribution/code-styling
Keeping our code styles consistent is key to making the repository easy to read and work with.
We use a number of style guides written by other amazing companies, simply because they are widely used and because we like working with them.
We don't expect you to study every single rule of each style guide, but these are great reference points as to how your code should be styled. We may reject a pull request if your code style significantly differs from these style guides however.
### Biome
Cal.com now uses Biome for both linting and formatting. The repository already contains the configuration, so we recommend hooking Biome into your editor to keep code aligned with the project guidelines automatically.
### JavaScript/TypeScript
We use the
for all JavaScript and Typescript code.
### HTML & CSS
We use the [Google HTML/CSS Style Guide](https://google.github.io/styleguide/htmlcssguide.html) for any HTML and CSS markup. However, exceptions to the HTML guide apply where JSX differentiates from standard HTML.
# Contributor's Guide
Source: https://cal.com/docs/developing/open-source-contribution/contributors-guide
## Introduction
Cal.com is structured as a monorepo using [Turborepo](https://github.com/vercel/turbo). Cal.com is built on [NextJS]("https://github.com/vercel/next.js") with [Typescript]("https://www.typescriptlang.org/"), [PostgreSQL]("https://www.postgresql.org/") database and the interactions with the database are carried out using [Prisma]("https://www.prisma.io/"). We use [TRPC]("https://trpc.io/") for end-to-end typesafe procedure calls to Prisma and utilize [Zod]("https://zod.dev/") for validation and type-safe parsing of API calls.
## Setting Up the Development Environment
### Prerequisites
Here is what you need to be able to run Cal.com.
* Node.js (Version: >=18.x)
* PostgreSQL
* Yarn (recommended)
Cal.com can be set up and run on your machine locally. Please take a look at [this quick guide](https://cal.com/docs/developing/local-development) for instructions on setting it up.
## Understanding the Project Structure
Cal.com makes use of Turborepo for a monorepo structure. This allows us to manage multiple code projects (like libraries, apps, services) and share code and resources effectively.
You can look at [turbo.json](https://github.com/calcom/cal.com/blob/main/turbo.json) to see how we use it. In simple terms,
* Each entry in the `pipeline` section corresponds to a different part of the project. Understanding these will help in navigating the codebase.
* Knowing which tasks depend on others is crucial for understanding the build process.
* The `env` entries under each task highlight the need for specific configuration settings, which you will need to set up in your development environment.
* You should focus on the tasks related to the area you are contributing to. For example, if working on the Prisma schema, look at tasks prefixed with `@calcom/prisma`
* The various packages can be found in the [/packages](https://github.com/calcom/cal.com/tree/main/packages) folder from the root.
* The various pages and components for the web can be found in the [/apps/web](https://github.com/calcom/cal.com/tree/main/apps/web) folder from the root.
* The public API related codebase can be found in the [/apps/api](https://github.com/calcom/cal.com/tree/main/apps/api) folder from the root.
* The Cal.ai related codebase can be found in the [/apps/ai](https://github.com/calcom/cal.com/tree/main/apps/ai) folder from the root
## File Naming Conventions
To ensure consistency and make files easy to fuzzy-find, we follow the naming conventions below for **services**, **repositories**, and other class-based files.
### Repository Files
* Repository class files must include the `Repository` suffix.
* If the repository is backed by a specific technology (e.g. Prisma), prefix the filename and class name with it.
* File name must match the exported class exactly (PascalCase).
**Pattern:**
Prisma``Repository.ts
**Examples:**
```js theme={null}
// File: PrismaAppRepository.ts
export class PrismaAppRepository { ... }
// File: PrismaMembershipRepository.ts
export class PrismaMembershipRepository { ... }
```
This avoids ambiguous filenames like app.ts and improves discoverability in editors.
### Service Files
Service class files must include the Service suffix.
File name should be in PascalCase, matching the exported class.
Keep naming specific — avoid generic names like AppService.ts.
**Pattern:**
``Service.ts
**Examples:**
```js theme={null}
// File: MembershipService.ts
export class MembershipService { ... }
// File: HashedLinkService.ts
export class HashedLinkService { ... }
```
New files must avoid dot-suffixes like .service.ts or .repository.ts; these will be migrated from the existing codebase progressively.\
We still reserve suffixes such as .test.ts, .spec.ts, and .types.ts for their respective use cases.
## How we use TRPC at cal.com
Cal.com makes use of the TRPC calls for CRUD operations on the database by utilizing the [useQuery()](https://trpc.io/docs/client/react/useQuery) & [useMutation()](https://trpc.io/docs/client/react/useMutation) hooks. These are essentially wrappers around @tanstack/react-query and you can learn more about them here, respectively: [queries](https://tanstack.com/query/v4/docs/react/guides/queries), [mutations](https://tanstack.com/query/v4/docs/react/guides/mutations).
Here’s an example usage of useQuery at cal.com:
```JS theme={null}
const { data: webhooks } = trpc.viewer.webhook.list.useQuery(undefined, {
suspense: true,
enabled: session.status === "authenticated",
});
```
Here’s an example usage of useMutation at cal.com:
```JS theme={null}
const createWebhookMutation = trpc.viewer.webhook.create.useMutation({
async onSuccess() {
showToast(t("webhook_created_successfully"), "success");
await utils.viewer.webhook.list.invalidate();
router.back();
},
onError(error) {
showToast(`${error.message}`, "error");
},
});
```
Both the examples are taken from [packages/features/webhooks/pages/webhook-new-view.tsx](https://github.com/calcom/cal.com/blob/main/packages/features/webhooks/pages/webhook-new-view.tsx) file. We use the useQuery hook for fetching data from the database. On the contrary, the useMutation hook is used for Creating/Updating/Deleting data from the database.
The path prior to the useQuery or useMutation hook represents the actual path of the procedure: in this case, [packages/trpc/server/routers/viewer/webhook](https://github.com/calcom/cal.com/tree/main/packages/trpc/server/routers/viewer/webhook). You will find `create.handler.ts` in case of
```js theme={null}
trpc.viewer.webhook.create.useMutation()
```
& `list.handler.ts` in case of
```js theme={null}
trpc.viewer.webhook.list.useQuery()
```
in the path mentioned above. There is usually a schema file in the same directory for each handler as well (*name*.schema.ts). This is generally how we make use of the TRPC calls in the cal.com repo.
## The Cal.com API v1
Our [API v1](https://github.com/calcom/cal.com/tree/main/apps/api) uses [zod validations](https://zod.dev/) to keep us typesafe and give us extensive parsing control and error handling. These validations reside in [/apps/api/lib/validations](https://github.com/calcom/cal.com/tree/main/apps/api/lib/validations). We also have [helper](https://github.com/calcom/cal.com/tree/main/apps/api/lib/helpers) and [utility](https://github.com/calcom/cal.com/tree/main/apps/api/lib/utils) functions which are used primarily within our API endpoints.
Here’s an example usage of our [zod validation in action with the respective API endpoint](https://github.com/calcom/cal.com/blob/main/apps/api/lib/validations/destination-calendar.ts):
```js theme={null}
export const schemaDestinationCalendarBaseBodyParams = DestinationCalendar.pick({
integration: true,
externalId: true,
eventTypeId: true,
bookingId: true,
userId: true,
}).partial();
const schemaDestinationCalendarCreateParams = z
.object({
integration: z.string(),
externalId: z.string(),
eventTypeId: z.number().optional(),
bookingId: z.number().optional(),
userId: z.number().optional(),
})
.strict();
export const schemaDestinationCalendarCreateBodyParams = schemaDestinationCalendarBaseBodyParams.merge(
schemaDestinationCalendarCreateParams
);
```
And here’s the usage of this validation for parsing the request at the [respective endpoint](https://github.com/calcom/cal.com/blob/main/apps/api/pages/api/destination-calendars/_post.ts):
```js theme={null}
async function postHandler(req: NextApiRequest) {
const { userId, isAdmin, prisma, body } = req;
const parsedBody = schemaDestinationCalendarCreateBodyParams.parse(body);
```
This is the general approach of validation and parsing in our API submodule.
## Building Apps for Cal.com
We’ve got an app store containing apps which complement the scheduling platform. From video conferencing apps such as Zoom, Google Meet, etc. to Calendar Apps such as Google Calendar, Apple Calendar, CalDAV, etc. and even payment, automation, analytics and other miscellaneous apps, it is an amazing space to improve the scheduling experience.
We have a CLI script in place that makes building apps a breeze. [You can learn more about it here](https://cal.com/docs/how-to-guides/how-to-build-an-app).
## Running Tests
### E2E-Testing
Be sure to set the environment variable `NEXTAUTH_URL` to the correct value. If you are running locally, as the documentation within `.env.example` mentions, the value should be `http://localhost:3000`.
In a terminal just run:
```bash theme={null}
yarn test-e2e
```
To open the last HTML report run:
```bash theme={null}
`yarn playwright show-report test-results/reports/playwright-html-report`
```
#### Resolving issues
**E2E test browsers not installed**
If you face the following error when running `yarn test-e2e`:
Executable doesn't exist at /Users/alice/Library/Caches/ms-playwright/chromium-1048/chrome-mac/Chromium.app
You can resolve it by running:
```bash theme={null}
npx playwright install
```
to download test browsers.
## Contribution and Collaboration Guides
* [Code Styling Guide](/docs/developing/open-source-contribution/code-styling)
* [Pull Requests Guide](/docs/developing/open-source-contribution/pull-requests)
# Introduction
Source: https://cal.com/docs/developing/open-source-contribution/introduction
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
* Before jumping into a PR be sure to search [existing PRs](https://github.com/calcom/cal.com/pulls) or [issues](https://github.com/calcom/cal.com/issues) for an open or closed item that relates to your submission.
### Priorities
| Type of issue | Priority |
| :--------------------------------------------------------------- | :------- |
| Minor improvements, non-core feature requests | |
| Confusing UX (... but working) | |
| Core Features (Booking page, availability, timezone calculation) | |
| Core Bugs (Login, Booking page, Emails are not working) | |
### Developing
The development branch is `main`. This is the branch that all pull
requests should be made against. The changes on the `main`
branch are tagged into a release monthly.
To develop locally:
1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your
own GitHub account and then
[clone](https://help.github.com/articles/cloning-a-repository/) it to your local device.
2. Create a new branch:
```bash theme={null}
git checkout -b MY_BRANCH_NAME
```
3. Install yarn:
```bash theme={null}
npm install -g yarn
```
4. Install the dependencies with:
```bash theme={null}
yarn
```
5. Start developing and watch for code changes:
```bash theme={null}
yarn dev
```
### Building
You can build the project with:
```bash theme={null}
yarn build
```
Please be sure that you can make a full production build before pushing code.
### Testing
More info on how to add new tests coming soon.
### Running tests
This will run and test all flows in multiple Chromium windows to verify that no critical flow breaks:
```bash theme={null}
yarn test-e2e
```
### Linting
To check the formatting of your code:
```bash theme={null}
yarn lint
```
If you get errors, be sure to fix them before committing.
### Making a Pull Request
* Be sure to [check the "Allow edits from maintainers" option](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork) while creating you PR.
* If your PR refers to or fixes an issue, be sure to add `refs #XXX` or `fixes #XXX` to the PR description. Replacing `XXX` with the respective issue number. See more about [Linking a pull request to an issue
](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue).
* Be sure to fill the PR Template accordingly.
### Guidelines for committing yarn lockfile
Do not commit your `yarn.lock` unless you've made changes to the `package.json`. If you've already committed `yarn.lock` unintentionally, follow these steps to undo:
If your last commit has the `yarn.lock` file alongside other files and you only wish to uncommit the `yarn.lock`:
```git theme={null}
git checkout HEAD~1 yarn.lock
git commit -m "Revert yarn.lock changes"
```
If you've pushed the commit with the `yarn.lock`:
1. Correct the commit locally using the above method.
2. Carefully force push:
```git theme={null}
git push origin --force
```
If `yarn.lock` was committed a while ago and there have been several commits since, you can use the following steps to revert just the `yarn.lock` changes without impacting the subsequent changes:
1. **Checkout a Previous Version**:
* Find the commit hash before the `yarn.lock` was unintentionally committed. You can do this by viewing the Git log:
```git theme={null}
git log yarn.lock
```
* Once you have identified the commit hash, use it to checkout the previous version of `yarn.lock`:
```git theme={null}
git checkout yarn.lock
```
2. **Commit the Reverted Version**:
* After checking out the previous version of the `yarn.lock`, commit this change:
```git theme={null}
git commit -m "Revert yarn.lock to its state before unintended changes"
```
3. **Proceed with Caution**:
* If you need to push this change, first pull the latest changes from your remote branch to ensure you're not overwriting other recent changes:
```git theme={null}
git pull origin
```
* Then push the updated branch:
```git theme={null}
git push origin
```
# Pull requests
Source: https://cal.com/docs/developing/open-source-contribution/pull-requests
### Requirements
We have a number of requirements for PRs to ensure they are as easy to review as possible and to ensure that they are up to standard with the code.
### Title & Content
Start by providing a short and concise title. Don’t put something generic (e.g. bug fixes), and instead mention more specifically what your PR achieves, for instance “Fixes dropdown not expanding on settings page”.
For the PR description, you should go into much greater detail about what your PR adds or fixes. Firstly, the description should include a link to any relevant issues or discussions surrounding the feature or bug that your PR addresses.
### Feature PRs
Give a functional overview of how your feature works, including how the user can use the feature. Then, share any technical details in an overview of how the PR works (e.g. “Once the user enters their password, the password is hashed using BCrypt and stored in the Users database field”).
### Bug Fix PRs
Give an overview of how your PR fixes the bug both as a high-level overview and a technical explanation of what caused the issue and how your PR resolves this.
Feel free to add a short video or screenshots of what your PR achieves. Loom is a great way of sharing short videos.
### Code Quality & Styling
All submitted code must match our [code styling](./code-styling) standards. We will reject pull requests that differ significantly from our standardised code styles.
All code is automatically checked by Codacy and our linting process, and will notify you if there are any issues with the code that you submit. We require that code passes these quality checks before merging.
### PR review process
At least two members of the Cal.com team should review and approve any PR before it is merged.
Once two members of the team have approved this, someone from the team will merge the PR. If you are part of the Cal.com team, you should merge your own PRs once you have received both approvals.
# MCP server
Source: https://cal.com/docs/mcp-server
Connect AI clients to Cal.com scheduling through the Model Context Protocol using the hosted server at mcp.cal.com or a local instance.
The Cal.com MCP server wraps the [Cal.com API v2](/docs/api-reference/v2/introduction) in the [Model Context Protocol](https://modelcontextprotocol.io/introduction), letting you manage bookings, event types, schedules, and more through natural language in any MCP-compatible client.
## Hosted server (mcp.cal.com)
The fastest way to get started is to connect your MCP client directly to `mcp.cal.com`. The hosted server uses Streamable HTTP transport with OAuth 2.1 authentication — your client handles the authorization flow automatically.
### Connect your client
Point your MCP client to the hosted server URL:
```
https://mcp.cal.com/mcp
```
When you first connect, your client walks you through an OAuth authorization flow where you grant the server access to your Cal.com account. No API key is needed.
Add the following to your `claude_desktop_config.json`:
* **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
* **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
```json theme={null}
{
"mcpServers": {
"calcom": {
"url": "https://mcp.cal.com/mcp"
}
}
}
```
Open **Settings → MCP** and add a new server with the URL `https://mcp.cal.com/mcp`, or add it to your `.cursor/mcp.json`:
```json theme={null}
{
"mcpServers": {
"calcom": {
"url": "https://mcp.cal.com/mcp"
}
}
}
```
Add the server to your VS Code MCP settings:
```json theme={null}
{
"mcpServers": {
"calcom": {
"url": "https://mcp.cal.com/mcp"
}
}
}
```
Add the following to your `~/.fx/mcp.json`:
```json theme={null}
{
"mcp": {
"calcom": {
"type": "http",
"url": "https://mcp.cal.com/mcp"
}
}
}
```
Then run `/mcp auth calcom --open` in fx to authorize. See the
[fx MCP docs](https://fx.sh/docs/capabilities/mcp) for transports,
authentication options, and tool discovery.
## Self-hosted server (stdio)
If you prefer to run the server locally, you can use stdio transport with an API key. This is useful for development or when you want full control over the server.
### Prerequisites
* Node.js >= 18
* A Cal.com API key — generate one in [Settings → Developer → API Keys](https://app.cal.com/settings/developer/api-keys)
### Connect your client
Add the following to your MCP client's configuration:
```json theme={null}
{
"mcpServers": {
"calcom": {
"command": "npx",
"args": ["@calcom/cal-mcp@latest"],
"env": {
"CAL_API_KEY": "cal_live_xxxx"
}
}
}
}
```
Replace `cal_live_xxxx` with your actual API key.
Never share or commit your API key. If exposed, rotate it immediately in your [Cal.com settings](https://app.cal.com/settings/developer/api-keys).
## Available tools
The MCP server exposes 34 tools organized by category:
### User profile
| Tool | Description |
| ----------- | ----------------------------------- |
| `get_me` | Get your authenticated user profile |
| `update_me` | Update your user profile |
### Event types
| Tool | Description |
| ------------------- | ------------------------------- |
| `get_event_types` | List all event types |
| `get_event_type` | Get a specific event type by ID |
| `create_event_type` | Create a new event type |
| `update_event_type` | Update an event type |
| `delete_event_type` | Delete an event type |
### Bookings
| Tool | Description |
| ----------------------- | ----------------------------------- |
| `get_bookings` | List bookings with optional filters |
| `get_booking` | Get a specific booking by UID |
| `create_booking` | Create a new booking |
| `reschedule_booking` | Reschedule a booking |
| `cancel_booking` | Cancel a booking |
| `confirm_booking` | Confirm a pending booking |
| `mark_booking_absent` | Mark a booking absence |
| `get_booking_attendees` | Get all attendees for a booking |
| `add_booking_attendee` | Add an attendee to a booking |
| `get_booking_attendee` | Get a specific attendee |
### Schedules
| Tool | Description |
| ---------------------- | ----------------------------- |
| `get_schedules` | List all schedules |
| `get_schedule` | Get a specific schedule by ID |
| `create_schedule` | Create a new schedule |
| `update_schedule` | Update a schedule |
| `delete_schedule` | Delete a schedule |
| `get_default_schedule` | Get your default schedule |
### Availability
| Tool | Description |
| ------------------ | ----------------------------- |
| `get_availability` | Get available time slots |
| `get_busy_times` | Get busy times from calendars |
### Conferencing
| Tool | Description |
| ----------------------- | ------------------------------ |
| `get_conferencing_apps` | List conferencing applications |
### Routing forms
| Tool | Description |
| ------------------------------ | ---------------------------------------------- |
| `calculate_routing_form_slots` | Calculate slots based on routing form response |
### Organizations
| Tool | Description |
| -------------------------------- | --------------------------------- |
| `get_org_memberships` | Get all organization memberships |
| `create_org_membership` | Create an organization membership |
| `get_org_membership` | Get an organization membership |
| `delete_org_membership` | Delete an organization membership |
| `get_org_routing_forms` | Get organization routing forms |
| `get_org_routing_form_responses` | Get routing form responses |
## Example prompts
Once connected, you can interact with Cal.com using natural language:
* "What bookings do I have this week?"
* "Create a 30-minute event type called 'Quick Chat'"
* "Cancel my meeting with John tomorrow"
* "Show me my available slots for next Monday"
* "Reschedule my 2pm meeting to Thursday at 3pm"
* "What event types do I have?"
## Related resources
* [AI agents guide](/docs/agents) — build AI agents using the Cal.com API directly
* [API v2 reference](/docs/api-reference/v2/introduction) — full API endpoint documentation
* [GitHub repository](https://github.com/calcom/companion/tree/main/apps/mcp-server) — source code