# 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.
### 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).
# 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 ").
# 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.
**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.
**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.
# 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.
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. For integration locations (e.g. Zoom, Google Meet, Cal Video), the endpoint also provisions a conference link and updates the corresponding calendar event via the organizer's connected credentials.
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 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
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
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.
# 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. 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.
# 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 all event types
Source: https://cal.com/docs/api-reference/v2/event-types/get-all-event-types
/api-reference/v2/openapi.json get /v2/event-types
Hidden event types are returned only if authentication is provided and it belongs to the event type owner.
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.
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 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.
Access control: This endpoint fetches an event type by ID and returns it only if the authenticated user is authorized. Authorization is granted to:
- System admins
- The event type owner
- Hosts of the event type or users assigned to the event type
- Team admins/owners of the team that owns the team event type
- Organization admins/owners of the event type owner's organization
- Organization admins/owners of the team's parent organization
Note: Update and delete endpoints remain restricted to the event type owner only. If accessed using an OAuth access token, the `EVENT_TYPE_READ` 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.
If accessed using an OAuth access token, the `EVENT_TYPE_WRITE` scope is required.
# 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.read`. 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.read`. 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 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.
# 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.
# 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 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) |
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) |
### 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) |
## 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
# 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.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_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.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_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.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_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.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_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
# 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 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
# 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.
# 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
# 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.
# 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.listMembers`. 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.listMembers`. 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.read`. 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.read`. 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.read`. 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.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 `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.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 `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.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 `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
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
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.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_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.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_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 a phone call
Source: https://cal.com/docs/api-reference/v2/orgs-teams-event-types/create-a-phone-call
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/teams/{teamId}/event-types/{eventTypeId}/create-phone-call
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 event type
Source: https://cal.com/docs/api-reference/v2/orgs-teams-event-types/create-an-event-type
/api-reference/v2/openapi.json post /v2/organizations/{orgId}/teams/{teamId}/event-types
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 team event type
Source: https://cal.com/docs/api-reference/v2/orgs-teams-event-types/delete-a-team-event-type
/api-reference/v2/openapi.json delete /v2/organizations/{orgId}/teams/{teamId}/event-types/{eventTypeId}
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/orgs-teams-event-types/get-all-team-event-types
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/event-types
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: `team admin`. PBAC permission: `eventType.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 `ORG_EVENT_TYPE_READ` scope is required.
# Get an event type
Source: https://cal.com/docs/api-reference/v2/orgs-teams-event-types/get-an-event-type
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/event-types/{eventTypeId}
Required membership role: `team admin`. PBAC permission: `eventType.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_EVENT_TYPE_READ` scope is required.
# Get team event types
Source: https://cal.com/docs/api-reference/v2/orgs-teams-event-types/get-team-event-types
/api-reference/v2/openapi.json get /v2/organizations/{orgId}/teams/{teamId}/event-types
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.
# Update a team event type
Source: https://cal.com/docs/api-reference/v2/orgs-teams-event-types/update-a-team-event-type
/api-reference/v2/openapi.json patch /v2/organizations/{orgId}/teams/{teamId}/event-types/{eventTypeId}
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 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.listMembers`. 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.listMembers`. 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
# 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}
# 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
# 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.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_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.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_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.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_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.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_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.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_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.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_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.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_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.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_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.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_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.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 `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.
# 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
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
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.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 `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.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 `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.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_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.
# 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.listMembers`. 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.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 `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.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 `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=2050-09-05&end=2050-09-06&timeZone=Europe/Rome'
2. By event type slug + username. Example '/v2/slots?eventTypeSlug=intro&username=bob&start=2050-09-05&end=2050-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=2050-09-05&end=2050-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=2050-09-05&end=2050-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=2050-09-05&end=2050-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=2050-09-05&end=2050-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=2050-09-05&end=2050-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}
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
# 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 a phone call
Source: https://cal.com/docs/api-reference/v2/teams-event-types/create-a-phone-call
/api-reference/v2/openapi.json post /v2/teams/{teamId}/event-types/{eventTypeId}/create-phone-call
If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_WRITE` scope is required.
# Create an event type
Source: https://cal.com/docs/api-reference/v2/teams-event-types/create-an-event-type
/api-reference/v2/openapi.json post /v2/teams/{teamId}/event-types
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/teams-event-types/delete-a-team-event-type
/api-reference/v2/openapi.json delete /v2/teams/{teamId}/event-types/{eventTypeId}
If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_WRITE` scope is required.
# Get an event type
Source: https://cal.com/docs/api-reference/v2/teams-event-types/get-an-event-type
/api-reference/v2/openapi.json get /v2/teams/{teamId}/event-types/{eventTypeId}
If accessed using an OAuth access token, the `TEAM_EVENT_TYPE_READ` scope is required.
# Get team event types
Source: https://cal.com/docs/api-reference/v2/teams-event-types/get-team-event-types
/api-reference/v2/openapi.json get /v2/teams/{teamId}/event-types
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.
# Update a team event type
Source: https://cal.com/docs/api-reference/v2/teams-event-types/update-a-team-event-type
/api-reference/v2/openapi.json patch /v2/teams/{teamId}/event-types/{eventTypeId}
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}
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.
# 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.
# 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}
```
### 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"] |
| 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 basically a weekly calendar view wherein users can see entire week at a glance, clearly showing both available and unavailable time slots. This view is designed to provide a comprehensive and easy-to-read schedule, helping users quickly identify open slots for booking or planning.
## Individual event type
Below code snippet can be used to render the calendar view atom for an individual event
```js theme={null}
import { CalendarView } from "@calcom/atoms";
export default function Booker( props : BookerProps ) {
return (
)
}
```
## Team event type
Below code snippet can be used to render the calendar view atom for a team event
```js theme={null}
import { CalendarView } from "@calcom/atoms";
export default function Booker( props : BookerProps ) {
return (
)
}
```
For a demonstration of the calendar view 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 | | |
| :-------- | :------- | :-------------------------------------------------------------------------------------------- | - | - |
| eventSlug | Yes | Unique slug created for a particular event | | |
| username | Optional | Username of the person whose schedule is to be displayed, required only for individual events | | |
| teamId | Optional | Id of the team for which the event is created, required only for team events | | |
# 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
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
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`
* **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.
### 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.
### 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 |
## 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
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 geo tiebreakers in the Salesforce section of your routing form settings. When enabled, the tiebreaker waterfall checks geography before falling back to the standard relationship-based criteria (child accounts, opportunities, contacts, etc.).
##### 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, these 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. 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
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.
# 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`
* `After Hosts Didn't Join Cal Video`
* `After Guests Didn't Join Cal Video`
* `Wrong Assignment Report`
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
}
}
```
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
}
}
}
```
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."
}
}
}
```
### 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.
### 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] |
| 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 |
#### 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 — small inputs, chips (4px) |
| `radius-sm` | Extra-small radius — tiny badges (2px) |
| `radius-md` | Medium radius — buttons, dropdowns (6px) |
| `radius-lg` | Large radius — cards, modals (8px) |
| `radius-xl` | Extra-large radius — large cards (12px) |
| `radius-2xl` | 2XL radius — dialog containers (16px) |
| `radius-3xl` | 3XL radius — hero cards (24px) |
| `radius-full` | Full pill shape — avatar circles, pill badges |
| `radius-none` | No rounding — sharp-cornered elements |
### 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 — small inputs, chips (4px)
"radius-sm": "0.125rem", // Extra-small radius — tiny badges (2px)
"radius-md": "0.375rem", // Medium radius — buttons, dropdowns (6px)
"radius-lg": "0.5rem", // Large radius — cards, modals (8px)
"radius-xl": "0.75rem", // Extra-large radius — large cards (12px)
"radius-2xl": "1rem", // 2XL radius — dialog containers (16px)
"radius-3xl": "1.5rem", // 3XL radius — hero cards (24px)
"radius-full": "9999px", // Full pill shape — avatars, pill badges
"radius-none": "0px", // No rounding — sharp corners
// ── 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",
"radius-sm": "0.125rem",
"radius-md": "0.375rem",
"radius-lg": "0.5rem",
"radius-xl": "0.75rem",
"radius-2xl": "1rem",
"radius-3xl": "1.5rem",
"radius-full": "9999px",
"radius-none": "0px",
// ── 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"
}
}
}
```
## 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
# Admin security requirements
Source: https://cal.com/docs/self-hosting/admin-security-requirements
Understand the password and two-factor authentication requirements for admin accounts in self-hosted Cal.com instances.
Admin accounts on self-hosted Cal.com instances must meet specific security requirements. If these requirements are not met, the admin's privileges are temporarily restricted until they update their credentials.
## Requirements
To retain full admin access, your account must satisfy **both** of the following:
1. **Password length** — at least 15 characters (must also include uppercase, lowercase, and a number)
2. **Two-factor authentication (2FA)** — enabled on the account
If either requirement is not met, your role is automatically changed to `INACTIVE_ADMIN` at login. You can still access the application, but admin-level actions are unavailable until you resolve the issue.
## What happens when requirements are not met
When you log in as an admin without meeting the security criteria, Cal.com:
1. Sets your session role to `INACTIVE_ADMIN`
2. Displays a persistent warning banner at the top of every page explaining what needs to be fixed
3. Links you directly to the relevant settings page
The banner message varies depending on what is missing:
| Missing requirement | Banner action |
| ------------------- | ----------------------------------------------- |
| Password and 2FA | Directs you to change your password |
| Password only | Directs you to change your password |
| 2FA only | Directs you to enable two-factor authentication |
After you update your password or enable 2FA, you are signed out automatically so the system can re-evaluate your credentials on the next login.
## How to resolve
Go to **Settings > Security > Password** and set a new password that is at least 15 characters long and includes uppercase letters, lowercase letters, and a number.
Go to **Settings > Security > Two-factor authentication** and follow the prompts to enable 2FA on your account.
After making changes, you are signed out automatically. Log in again with your updated credentials to regain full admin access.
This enforcement only applies to admin accounts that use Cal.com credential-based authentication. Admins who sign in through an external identity provider (such as SAML or OIDC) are not affected.
# Daily
Source: https://cal.com/docs/self-hosting/apps/install-apps/daily
#### Obtaining Daily API Credentials
Go to [Daily](https://www.daily.co/) and sign into your account.
From within your dashboard, go to the [developers](https://dashboard.daily.co/developers) tab.
Copy the API key provided in the developers tab.
Now paste the API key into the `DAILY_API_KEY` field in your `.env` file.
If you have the [Daily Scale Plan](https://www.daily.co/pricing), set the `DAILY_SCALE_PLAN` variable to `true` in the `.env` file to use features like video recording.
# Google
Source: https://cal.com/docs/self-hosting/apps/install-apps/google
#### Obtaining the Google API Credentials
Go to [Google API Console](https://console.cloud.google.com/apis/dashboard). If you don't have a project in your Google Cloud subscription, create one before proceeding. Under the Dashboard pane, select "Enable APIs and Services".
In the search box, type "calendar" and select the Google Calendar API search result.
Enable the selected API to proceed.
Go to the [OAuth consent screen](https://console.cloud.google.com/apis/credentials/consent) from the side pane. Select the app type (Internal or External) and enter the basic app details on the first page.
On the Data Access page, select "Add or Remove Scopes". Search for Calendar.event and select the scopes with values `.../auth/calendar.events`, `.../auth/calendar.readonly`, and then click "Update".
If User type is external on the Audience page, add the Google account(s) you'll be using. Verify details on the last page to complete the consent screen configuration.
From the side pane, select [Credentials](https://console.cloud.google.com/apis/credentials) and then "Create Credentials". Choose "OAuth Client ID".
Choose "Web Application" as the Application Type.
Under Authorized redirect URI's, add the URIs:
```
/api/integrations/googlecalendar/callback
/api/auth/callback/google
```
Replace `` with the URL where your application runs.
The key will be created, redirecting you back to the Credentials page. Select the new client ID under "OAuth 2.0 Client IDs", then click "Download JSON". Copy the JSON file contents and paste the entire string into the `.env` and `.env.appStore` files under the `GOOGLE_API_CREDENTIALS` key.
In the `.env` file, set the following environment variable:
```
GOOGLE_LOGIN_ENABLED=false
```
This will configure the Google integration as an Internal app, restricting login access.
In the `.env` file, set the following environment variable:
```
CALCOM_ENV="local"
```
This will allow you to test the Google integration locally. Do it only when you are testing Cal.com locally.
### **Adding Google Calendar to Cal.com App Store**
After adding Google credentials, you can now add the Google Calendar App to the app store. Repopulate the App store by running:
Run `yarn workspace @calcom/prisma seed-app-store` to update the app store and include the newly added Google Calendar integration.
# HubSpot
Source: https://cal.com/docs/self-hosting/apps/install-apps/hubspot
### Obtaining HubSpot Client ID and Secret
Go to [HubSpot Developer](https://developer.hubspot.com/) and sign into your account, or create a new one.
From the Developer account home page, go to "Manage apps".
Click the "Create app" button located at the top right corner of the page.
In the "App info" tab, fill in any information you want for your app.
Navigate to the "Auth" tab to set up authentication for the app.
Copy the Client ID and Client Secret and add them to your `.env` file under the fields:
```
HUBSPOT_CLIENT_ID
HUBSPOT_CLIENT_SECRET
```
Set the Redirect URL for OAuth to:
```
/api/integrations/hubspot/callback
```
Replace `` with the URL where your application is hosted.
In the "Scopes" section, select "Read" and "Write" for the scope called `crm.objects.contacts`.
Click the "Save" button at the bottom of the page.
You’re all set! Any booking in Cal.com will now be created as a meeting in HubSpot for your contacts.
# Introduction
Source: https://cal.com/docs/self-hosting/apps/install-apps/introduction
Install desired apps in your self-hosted instance for better integration.
Install Google for supporting Google Calendar and Google Meet integrations
Install Microsoft to support Exchange365 integration
Install Zoom to support Zoom video conferencing integration
Install Daily to support Cal Video integration
Install HubSpot to support HubSpot CRM integration
Install SendGrid to support SendGrid Email integration
Install Stripe to support Stripe integration for paid bookings
Install Twilio to support Workflows integration for sms reminders
Install Zoho to support Zoho CRM integration
# Microsoft
Source: https://cal.com/docs/self-hosting/apps/install-apps/microsoft
#### Obtaining Microsoft Graph Client ID and Secret
Go to [Azure App Registration](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps) and select "New registration".
Provide a name for your application to proceed with the registration.
Set **Who can use this application or access this API?** to **Accounts in any organizational directory (Any Azure AD directory - Multitenant)**.
Set the **Web** redirect URI to:
```
/api/integrations/office365calendar/callback
```
Replace `` with the URL where your application runs.
Use the **Application (client) ID** as the value for **MS\_GRAPH\_CLIENT\_ID** in your `.env` file.
Click on **Certificates & secrets**, create a new client secret, and use the generated value as the **MS\_GRAPH\_CLIENT\_SECRET** in your `.env` file.
# Sendgrid
Source: https://cal.com/docs/self-hosting/apps/install-apps/sendgrid
Go to [https://signup.sendgrid.com/](https://signup.sendgrid.com/) and create a new SendGrid account.
Navigate to **Settings** -> **API Keys** and create a new API key.
Copy the generated API key and add it to your `.env` file under the field:
```
SENDGRID_API_KEY
```
Go to **Settings** -> **Sender Authentication** and verify a single sender.
Copy the verified email address and add it to your `.env` file under the field:
```
SENDGRID_EMAIL
```
This app is **required** for Workflows
# Stripe
Source: https://cal.com/docs/self-hosting/apps/install-apps/stripe
### Setting up Stripe
Go to Stripe and either create a new account or log into an existing one. For testing, activate the Test-Mode toggle in the top right of the Stripe dashboard.
Open [Stripe API Keys](https://dashboard.stripe.com/apikeys), then save the token starting with `pk_...` to `NEXT_PUBLIC_STRIPE_PUBLIC_KEY` and `sk_...` to `STRIPE_PRIVATE_KEY` in the `.env` file.
Go to [Stripe Connect Settings](https://dashboard.stripe.com/settings/connect) and activate OAuth for Standard Accounts.
Add the following redirect URL, replacing `` with your application's URL:
```
/api/integrations/stripepayment/callback
```
Copy your client ID (`ca_...`) to `STRIPE_CLIENT_ID` in the `.env` file.
Open [Stripe Webhooks](https://dashboard.stripe.com/webhooks) and add:
```
/api/integrations/stripepayment/webhook
```
as the webhook for connected applications.
Select all `payment_intent` and `setup_intent` events for the webhook.
Copy the webhook secret (`whsec_...`) to `STRIPE_WEBHOOK_SECRET` in the `.env` file.
# Twilio
Source: https://cal.com/docs/self-hosting/apps/install-apps/twilio
Go to [Twilio](https://www.twilio.com/try-twilio) and create a new account.
Click on 'Get a Twilio phone number' to obtain a phone number for your account.
Copy the Account SID and add it to your `.env` file under the field:
```
TWILIO_SID
```
Copy the Auth Token and add it to your `.env` file under the field:
```
TWILIO_TOKEN
```
Copy your Twilio phone number and add it to your `.env` file under the field:
```
TWILIO_PHONE_NUMBER
```
Add your own sender ID to the `.env` file under the field:
```
NEXT_PUBLIC_SENDER_ID
```
(The fallback is set to "Cal" if not specified.)
Navigate to **Develop** -> **Messaging** -> **Services**, then create a new messaging service.
Choose a name for the messaging service. This can be anything you like.
Click 'Add Senders' and select 'Phone number' as the sender type.
Add the listed phone number to the messaging service.
Leave all other fields as they are, then complete the setup by clicking ‘View my new Messaging Service’.
Copy the Messaging Service SID and add it to your `.env` file under the field:
```
TWILIO_MESSAGING_SID
```
Go to the Twilio dashboard, create a new Verify Service, and name it as you wish.
Copy the Verify Service SID and add it to your `.env` file under the field:
```
TWILIO_VERIFY_SID
```
This app is **required** for Workflows
# Zoho
Source: https://cal.com/docs/self-hosting/apps/install-apps/zoho
#### Obtaining ZohoCRM Client ID and Secret
Go to [Zoho API Console](https://api-console.zoho.com/) and sign into your account, or create a new one.
From the API console page, navigate to "Applications".
Click the "ADD CLIENT" button at the top right and select "Server-based Applications".
Enter any desired information in the "Client Details" tab.
Navigate to the "Client Secret" tab.
Copy the Client ID and Client Secret into your `.env` file under:
```
ZOHOCRM_CLIENT_ID
ZOHOCRM_CLIENT_SECRET
```
Set the Redirect URL for OAuth to:
```
/api/integrations/zohocrm/callback
```
Replace `` with your application URL.
In the "Settings" section, check the "Multi-DC" option if you wish to use the same OAuth credentials for all data centers.
Click "Save" or "UPDATE" at the bottom of the page.
Your ZohoCRM integration can now be easily added in the Cal.com settings.
#### Obtaining Zoho Calendar Client ID and Secret
Go to [Zoho API Console](https://api-console.zoho.com/) and sign into your account, or create a new one.
Choose "Server-based Applications" and set the Redirect URL for OAuth to:
```
/api/integrations/zohocalendar/callback
```
Replace `` with your application URL.
Enter any information you want in the "Client Details" tab.
Navigate to the "Client Secret" tab.
Copy the Client ID and Client Secret to your app keys in the Cal.com admin panel at:
```
/settings/admin/apps
```
In the "Settings" section of Zoho API Console, check the "Multi-DC" option if you wish to use the same OAuth credentials across data centers.
Click "Save" or "UPDATE" at the bottom of the page.
Your Zoho Calendar integration is now ready and can be managed at:
```
/settings/my-account/calendars
```
You can access your Zoho calendar at [https://calendar.zoho.com/](https://calendar.zoho.com/).
If you use multiple calendars with Cal, make sure to enable the toggle to prevent double-bookings across calendars. This setting can be found at `/settings/my-account/calendars`.
#### Obtaining Zoho Bigin Client ID and Secret
Go to [Zoho API Console](https://api-console.zoho.com/) and sign into your account, or create a new one.
Click the "ADD CLIENT" button at the top right, and select "Server-based Applications".
Set the Redirect URL for OAuth to:
```
/api/integrations/zoho-bigin/callback
```
Replace `` with your application URL.
Navigate to the "Client Secret" tab.
Copy the Client ID and Client Secret into the `.env.appStore` file under:
```
ZOHO_BIGIN_CLIENT_ID
ZOHO_BIGIN_CLIENT_SECRET
```
In the "Settings" section, check the "Multi-DC" option if you wish to use the same OAuth credentials across data centers.
Your Zoho Bigin integration is now ready and can be added from the Cal.com app store.
# Zoom
Source: https://cal.com/docs/self-hosting/apps/install-apps/zoom
**Obtaining Zoom Client ID and Secret**
Go to [Zoom Marketplace](https://marketplace.zoom.us/) and sign in with your Zoom account.
In the upper right, click "Develop" and then "Build App".
Under "OAuth", select "Create".
Provide a name for your app.
Select "User-managed app" as the app type.
De-select the option to publish the app on the Zoom App Marketplace.
Click "Create" to proceed.
Copy the Client ID and Client Secret and add them to your `.env` file under the fields:
```
ZOOM_CLIENT_ID
ZOOM_CLIENT_SECRET
```
Set the Redirect URL for OAuth to:
```
/api/integrations/zoomvideo/callback
```
Replace `` with your application URL.
Add the redirect URL to the allow list and enable "Subdomain check". Ensure it displays "saved" below the form.
You don't need to provide basic information about your app. Instead, go to "Scopes", click "+ Add Scopes", then select the category "Meeting" on the left, and check the scope `meeting:write`.
Click "Done" to save the scope settings.
Your Zoom integration is now ready and can be easily added in the Cal.com settings.
# Database migrations
Source: https://cal.com/docs/self-hosting/database-migrations
As described in the [upgrade guide](/docs/self-hosting/upgrading), you should use the
```
yarn workspace @calcom/prisma db-migrate
```
or
```
yarn workspace @calcom/prisma db-deploy
```
command to update the database.
We use database migrations in order to handle changes to the database schema in a more secure and stable way. This is actually very common. The thing is that when just changing the schema in `schema.prisma` without creating migrations, the update to the newer database schema can damage or delete all data in production mode, since the system sometimes doesn't know how to transform the data from A to B. Using migrations, each step is reproducible, transparent and can be undone in a simple way.
### Creating migrations
If you are modifying the codebase and make a change to the `schema.prisma` file, you must create a migration.
To create a migration for your previously changed `schema.prisma`, simply run the following:
```
yarn workspace @calcom/prisma db-migrate
```
Now, you must create a short name for your migration to describe what changed (for example, "user\_add\_email\_verified"). Then just add and commit it with the corresponding code that uses your new database schema.
Always keep an eye on what migrations Prisma is generating.\*\* Prisma often happily will drop entire columns of data because it can't figure out what to do.
### Error: The database schema is not empty
Prisma uses a database called `_prisma_migrations` to keep track of which migrations have been applied and which haven't. If your local migrations database doesn't match up with what's in the actual database, then Prisma will throw the following error:
```
Error: P3005
The database schema for `localhost:5432` is not empty. Read more about how to baseline an existing production database: https://pris.ly/d/migrate-baseline
```
In order to fix this, we need to tell Prisma which migrations have already been applied.
This can be done by running the following command, replacing `migration_name` with each migration that you have already applied:
```
yarn prisma migrate resolve --applied migration_name
```
You will need to run the command for each migration that you want to mark as applied.
### Resetting Prisma migrate
When your local Prisma database runs out of sync with migrations on local and you are tearing your hair out, I’ve been there, so you don’t have to:
**PostgreSQL**
```
DELETE FROM "_prisma_migrations";
```
**Quickly re-index**
```
# Run the following to easily apply all migrations in the prisma/migrations directory
ls -1a prisma/migrations/ | grep 2021 | xargs -I{} prisma migrate resolve --applied {}
```
# AWS
Source: https://cal.com/docs/self-hosting/deployments/aws
Deploying Cal.com on AWS
***
## Manual Deployment
1. Amazon Web Services account
2. Familiarity with AWS services and management console
3. Access to the Cal.com source code
1. Create an AWS Account: If not already set up, create an account on AWS.
2. Management Console: Log in to the AWS Management Console.
1. Create a New IAM User: Set up an IAM user with the necessary permissions for deploying and managing the application.
2. Set Up Required Services: Establish services like Amazon EC2, RDS for PostgreSQL, etc., as needed for your application.
1. Clone the Repository: Get the Cal.com repository onto your local environment.
2. Update Configuration: Modify the .env file to include your AWS resource details (like database endpoints).
1. Deploy Application: Utilize AWS services such as EC2 or Elastic Beanstalk to deploy the Cal.com application.
2. Database Configuration: Set up and connect the RDS instance to your application.
3. Verify Deployment: Ensure the application is operational and accessible.
1. DNS Setup: Update your DNS settings to point to your AWS deployment.
2. Monitoring and Scaling: Leverage AWS monitoring tools to keep track of your application's performance and scale resources accordingly.
### Best Practices
1. Adhere to AWS's recommended security practices.
2. Regularly update your deployment with the latest Cal.com releases.
### Additional Resources
[https://docs.aws.amazon.com/](https://docs.aws.amazon.com/)
# Azure
Source: https://cal.com/docs/self-hosting/deployments/azure
Deploying Cal.com on Azure
***
## One Click Deployment
## Manual Deployment
1. Microsoft Azure account
2. Basic knowledge of Azure services
3. Access to Cal.com source code
1. Create an Azure Account
2. Azure Portal: Familiarize yourself with the Azure Portal.
1. Creating Azure Resources: In the Azure Portal, create a new resource group for your Cal.com project.
2. Create Azure Services: Set up required services such as Azure App Service, Azure Database for PostgreSQL, etc.
#### Create Web App
#### Setup Database, Networking
#### Setup Monitoring
1. Clone Repository: Clone the Cal.com repository to your local machine.
2. Configuration Files: Update the .env file with necessary Azure configurations (e.g., database connection strings).
1. Deploying Web App: Use Azure App Service to deploy the Cal.com web application.
2. Database Setup: Deploy and configure the Azure Database for PostgreSQL with Cal.com.
3. Deployment Verification: Ensure that the application is running smoothly post-deployment.
1. DNS Configuration: Configure your DNS settings to point to the Azure deployment.
2. Monitor and Scale: Utilize Azure monitoring tools to keep track of performance and scale resources as needed.
# Elestio
Source: https://cal.com/docs/self-hosting/deployments/elestio
You can deploy Cal.com on Elestio using the button below.
## One Click Deployment
# GCP
Source: https://cal.com/docs/self-hosting/deployments/gcp
Deploying Cal.com on Google Cloud Platform (GCP)
In this guide, we will go over the steps to deploy Cal.com on Google Cloud Platform (GCP). We will cover how to create a virtual machine, configure it, install Docker, and finally deploy the Cal.com application.
## One Click Deployment
## Manual Deployment
### Go to the GCP Console
First, open the Google Cloud Platform console by visiting the [https://console.cloud.google.com/](https://console.cloud.google.com/) website.
### Create a New Project
If you haven't already, create a new project by clicking on the "Select a project" dropdown menu and selecting "New Project". Enter a name for your project and click on the "Create" button.
### Create a New VM Instance
Click on the navigation menu icon (three horizontal lines) and select "Compute Engine" from the list. Then, click on "VM instances" in the sub-menu.
Click on the "Create" button to create a new virtual machine.
### Select the Machine Type
Choose the machine type that best suits your needs. Ideally, 2 vCPUs and 2-4GB RAM is enough.
### Set Up Networking
Make sure the "Networking" tab is selected and click on the "Add network" button. Choose the "Default" network and click on the "Add" button.
### Create the Instance
Review the details of your virtual machine and click on the "Create" button to create the instance.
Note the public IP of the VM.
Once your virtual machine is created, you need to configure it to allow traffic on port 80.
### Open Port 80
Click on the navigation menu icon (three horizontal lines) and select "Compute Engine" from the list. Then, click on "VM instances" in the sub-menu. Find your newly created instance and click on its name to enter its details page.
Click on the "Firewalls" tab and then click on the "Add firewall rule" button. Select "Allow all" as the source and destination, set the protocol to "tcp" and the ports to "80". Click on the "Add" button to save the changes.
Now that your virtual machine is configured, you need to install Docker on it.
### Connect to Your Instance
Open a terminal window on your local machine and use SSH to connect to your virtual machine. You can find the external IP address or DNS name of your instance in the GCP console. Use the following command to connect to your instance:
```bash theme={null}
gcloud ssh --project=[PROJECT_ID] --zone=[ZONE] [INSTANCE_NAME]
```
Replace `[PROJECT_ID]` with your project ID, `[ZONE]` with the zone where your instance is located, and `[INSTANCE_NAME]` with the name of your instance.
You can also use web based SSH.
### Install Docker
Once connected, update the package list and install Docker using the following commands:
```bash theme={null}
sudo apt-get update
sudo apt-get install docker.io
```
### Start the Docker Service
Start the Docker service using the following command:
```bash theme={null}
sudo systemctl start docker
```
Now that Docker is installed and running, you can deploy Cal.com on your virtual machine.
### Pull the Docker Image
Use the following command to pull the Cal.com Docker image from Docker Hub:
```bash theme={null}
docker pull cal/calcom
```
### Run the Docker Container
Run the Docker container using the following command:
```bash theme={null}
docker run -d -p 80:80 cal/cal.com
```
This command maps port 80 on your local machine to port 80 inside the container, so you can access Cal.com from outside the container.
### Access Cal.com
Open a web browser and navigate to `http://localhost`. You should now be able to access the Cal.com homepage.
Congratulations! You have successfully deployed Cal.com on Google Cloud Platform.
# Northflank
Source: https://cal.com/docs/self-hosting/deployments/northflank
You can deploy Cal.com on Northflank using the button below.
The team at Northflank also have a [detailed blog post](https://northflank.com/guides/deploy-calcom-with-northflank) on deploying Cal.com on their platform.
## One Click Deployment
# Railway
Source: https://cal.com/docs/self-hosting/deployments/railway
You can deploy Cal.com on Railway using the button below.
## One Click Deployment
# Render
Source: https://cal.com/docs/self-hosting/deployments/render
You can deploy Cal.com on Render using the button below.
## One Click Deployment
# Vercel
Source: https://cal.com/docs/self-hosting/deployments/vercel
## Requirements
Currently Vercel Pro Plan is required to be able to Deploy this application with Vercel, due to limitations on the number of serverless functions on the free plan.
You need a PostgresDB database hosted somewhere. [Supabase](https://supabase.com/) offers a great free option.
## One Click Deployment
## Manual Deployment
### Local settings
```
git clone https://github.com/\/cal.com.git
```
Copy the `.env.example` file in `apps/web`, rename it to `.env` and fill it with your settings ([See manual setup](https://github.com/calcom/cal.com#manual-setup) and [Obtaining the Google API Credentials](https://github.com/calcom/cal.com#obtaining-the-google-api-credentials))
```
yarn install
```
Schema is located in at `packages/prisma/schema.prisma`.
```
yarn workspace @calcom/prisma db-deploy
```
To look at or modify the database content
```
yarn db-studio
```
Click on the `User` model to add a new user record.
Fill out the fields (remembering to encrypt your password with [BCrypt](https://bcrypt-generator.com/)) and click `Save 1 Record` to create your first user.
Open a browser to [port 3000](http://localhost:3000/) on your localhost and login with your just created, first user.
Sometimes, yarn install might fail during deployment on Vercel, in which case, you can use `YARN_ENABLE_IMMUTABLE_INSTALLS=false yarn install` as the install command instead.
#### Deployment
Override to:
```
cd ../.. && yarn build --no-deps
```
# Docker
Source: https://cal.com/docs/self-hosting/docker
### Introduction
This image can be found on DockerHub at [https://hub.docker.com/r/calcom/cal.com](https://hub.docker.com/r/calcom/cal.com).
Note for ARM Users: Use the -arm suffix for pulling images. Example: `docker pull calcom/cal.com:v5.6.19-arm`.
### Contributing
The Docker configuration for Cal.com is officially maintained in the main Cal.com repository. The Dockerfile and docker-compose.yml are located in the root of the [calcom/cal.com repository](https://github.com/calcom/cal.com).
If you want to contribute to the Docker setup, please open an issue or pull request in the [main Cal.com repository](https://github.com/calcom/cal.com).
### Requirements
Make sure you have `docker` & `docker compose` installed on the server / system.
Note: `docker compose` without the hyphen is now the primary method of using docker-compose, per the Docker documentation.
### Getting Started
1. Clone the Cal.com repository
```
git clone https://github.com/calcom/cal.com.git
```
2. Change into the directory
```
cd cal.com
```
3. Prepare your configuration: Rename .env.example to .env and then update .env
```
cp .env.example .env
```
Most configurations can be left as-is, but for configuration options see Important Run-time variables below.
Update the appropriate values in your .env file, then proceed.
4. (optional) Pre-Pull the images by running the following command
```
docker compose pull
```
5. Start Cal.com via docker compose
(Most basic users, and for First Run) To run the complete stack, which includes a local Postgres database, Cal.com web app, and Prisma Studio:
```bash theme={null}
docker compose up -d
```
To run Cal.com web app and Prisma Studio against a remote database, ensure that DATABASE\_URL is configured for an available database and run:
```bash theme={null}
docker compose up -d calcom studio
```
To run only the Cal.com web app, ensure that DATABASE\_URL is configured for an available database and run:
```bash theme={null}
docker compose up -d calcom
```
6. Open a browser to [http://localhost:3000](http://localhost:3000), or your defined NEXT\_PUBLIC\_WEBAPP\_URL. The first time you run Cal.com, a setup wizard will initialize. Define your first user, and you're ready to go!
### Update Calcom Instance
1. Stop the Cal.com stack
```bash theme={null}
docker compose down
```
2. Pull the latest changes
```bash theme={null}
docker compose pull
```
3. Update environment variables as necessary.
4. Re-start the Cal.com stack
```bash theme={null}
docker compose up -d
```
### Configuration
#### Build-time variables
These variables must be provided at the time of the docker build, and can be provided by updating the .env file. Changing these is not required for evaluation, but may be required for running in production. Currently, if you require changes to these variables, you must follow the instructions to build and publish your own image.
* NEXT\_PUBLIC\_WEBAPP\_URL
* NEXT\_PUBLIC\_LICENSE\_CONSENT
* NEXT\_PUBLIC\_TELEMETRY\_KEY
#### Important Run-time variables
* NEXTAUTH\_SECRET
## Advanced Users - Building and Configuring
For more detailed instructions on how to build and configure your own Docker image, refer to the Dockerfile and docker-compose.yml in the root of the [Cal.com repository](https://github.com/calcom/cal.com).
## Troubleshooting
* SSL edge termination: If running behind a load balancer which handles SSL certificates, you will need to add the environmental variable `NODE_TLS_REJECT_UNAUTHORIZED=0` to prevent requests from being rejected. Only do this if you know what you are doing and trust the services/load-balancers directing traffic to your service.
* Failed to commit changes: Invalid 'prisma.user.create()': Certain versions may have trouble creating a user if the field `metadata` is empty. Using an empty json object `{}` as the field value should resolve this issue. Also, the `id` field will autoincrement, so you may also try leaving the value of `id` as empty.
### CLIENT\_FETCH\_ERROR
If you experience this error, it may be the way the default Auth callback in the server is using the WEBAPP\_URL as a base url. The container does not necessarily have access to the same DNS as your local machine, and therefor needs to be configured to resolve to itself. You may be able to correct this by configuring `NEXTAUTH_URL=http://localhost:3000/api/auth`, to help the backend loop back to itself.
```
docker-calcom-1 | @calcom/web:start: [next-auth][error][CLIENT_FETCH_ERROR]
docker-calcom-1 | @calcom/web:start: https://next-auth.js.org/errors#client_fetch_error request to http://testing.localhost:3000/api/auth/session failed, reason: getaddrinfo ENOTFOUND testing.localhost {
docker-calcom-1 | @calcom/web:start: error: {
docker-calcom-1 | @calcom/web:start: message: 'request to http://testing.localhost:3000/api/auth/session failed, reason: getaddrinfo ENOTFOUND testing.localhost',
docker-calcom-1 | @calcom/web:start: stack: 'FetchError: request to http://testing.localhost:3000/api/auth/session failed, reason: getaddrinfo ENOTFOUND testing.localhost\n' +
docker-calcom-1 | @calcom/web:start: ' at ClientRequest. (/calcom/node_modules/next/dist/compiled/node-fetch/index.js:1:65756)\n' +
docker-calcom-1 | @calcom/web:start: ' at ClientRequest.emit (node:events:513:28)\n' +
docker-calcom-1 | @calcom/web:start: ' at ClientRequest.emit (node:domain:489:12)\n' +
docker-calcom-1 | @calcom/web:start: ' at Socket.socketErrorListener (node:_http_client:494:9)\n' +
docker-calcom-1 | @calcom/web:start: ' at Socket.emit (node:events:513:28)\n' +
docker-calcom-1 | @calcom/web:start: ' at Socket.emit (node:domain:489:12)\n' +
docker-calcom-1 | @calcom/web:start: ' at emitErrorNT (node:internal/streams/destroy:157:8)\n' +
docker-calcom-1 | @calcom/web:start: ' at emitErrorCloseNT (node:internal/streams/destroy:122:3)\n' +
docker-calcom-1 | @calcom/web:start: ' at processTicksAndRejections (node:internal/process/task_queues:83:21)',
docker-calcom-1 | @calcom/web:start: name: 'FetchError'
docker-calcom-1 | @calcom/web:start: },
docker-calcom-1 | @calcom/web:start: url: 'http://testing.localhost:3000/api/auth/session',
docker-calcom-1 | @calcom/web:start: message: 'request to http://testing.localhost:3000/api/auth/session failed, reason: getaddrinfo ENOTFOUND testing.localhost'
docker-calcom-1 | @calcom/web:start: }
```
# How to sync third party apps
Source: https://cal.com/docs/self-hosting/guides/appstore-and-integration/syncing-third-party-apps
1. `CALCOM_WEBHOOK_SECRET`
* You can generate this by running `openssl rand -base64 32`. This is required when sending 3rd party app credentials from your platform to your instance of Cal.com.
2. On your self-hosted instance of Cal.com visit settings/admin/apps under an admin account. Here you can enable/disable apps on Cal.com and set the app keys (client id, client secret, etc.). These keys should match the ones on your platform.
3. `CALCOM_WEBHOOK_HEADER_NAME`
* The header name is expected to contain the webhook secret. The default is `calcom-webhook-secret`
4. `CALCOM_CREDENTIAL_SYNC_ENDPOINT`
* The endpoint on your platform that your instance of [Cal.com](https://Cal.com) will make a request to if the 3rd party app credentials are expired.
5. `CALCOM_APP_CREDENTIAL_ENCRYPTION_KEY`
* When sending 3rd party app credentials between your platform and your instance Cal.com, we expect these to be encrypted using AES256. When you encrypt the 3rd party app credentials, ensure the same key is used.
When a user adds a 3rd party app on your platform, you should send the credentials that are created to your instance of [Cal.com](http://Cal.com) to `${CALCOM_WEBAPP_URL}/api/webhook/app-credential` . The payload should contain the following
```javascript theme={null}
{
// UserId of the Cal.com user
userId: number;
// The app slug that is on Cal.com.
// Can be found in the Cal.com database in the App table
appSlug: string;
// The credentials from the 3rd party app. (ex. Access token, refresh token).
// Ideally it should contain the access token and expiry date
// AES256 encrypted with CALCOM_APP_CREDENTIAL_ENCRYPTION_KEY
keys: string;
}
```
When [Cal.com](https://Cal.com) needs to refresh the app credentials it will make a request to `CALCOM_CREDENTIAL_SYNC_ENDPOINT`. The request contains the following.
```javascript theme={null}
{
calcomUserId: string;
// App slug on Cal.com
appSlug: string;
}
```
This only works if you have integrated [Cal.com](https://cal.com/) into your platform. Users **must** consent to give access to your platform, and you are simply using [Cal.com](http://cal.com/)'s code within your platform.
# Custom SMTP configuration
Source: https://cal.com/docs/self-hosting/guides/organization/custom-smtp-configuration
Configure a custom SMTP server for your organization's outgoing emails.
Organizations can use a custom SMTP server to send emails from their own domain instead of the default Cal.com email infrastructure. When configured, all outgoing emails for the organization (booking confirmations, reminders, and notifications) are routed through your SMTP server.
## Prerequisites
* A Cal.com organization (see [Organization setup](/docs/self-hosting/guides/organization/organization-setup))
* Organization admin access
* The `custom-smtp-for-orgs` feature flag enabled for your organization
* SMTP server credentials (host, port, username, and password)
## Configure custom SMTP
Log in as an organization admin and navigate to your organization's settings.
Provide the following details:
| Field | Description |
| ----------------- | ------------------------------------------------------------------------------------------------ |
| **From email** | The email address that appears as the sender (e.g., `notifications@yourcompany.com`). |
| **From name** | The display name shown alongside the from email (e.g., `Your Company`). |
| **SMTP host** | Your mail server hostname (e.g., `smtp.yourcompany.com`). Must be a publicly reachable address. |
| **SMTP port** | The port your SMTP server listens on. Common values: `587` (STARTTLS), `465` (SSL/TLS), or `25`. |
| **SMTP username** | The username for SMTP authentication. |
| **SMTP password** | The password for SMTP authentication. |
| **SMTP secure** | Whether to use a secure TLS connection. Defaults to `true`. |
After entering your credentials, test the SMTP connection to verify that Cal.com can reach your mail server. This checks connectivity and authentication without sending an email.
Send a test email to confirm that messages are delivered correctly through your SMTP server. Enter a recipient email address and check that the email arrives.
## Update or delete a configuration
You can update individual fields of your SMTP configuration at any time without re-entering all values. To stop using a custom SMTP server and revert to the default Cal.com email infrastructure, delete the configuration from your organization settings.
Each organization can have only one SMTP configuration. To change SMTP providers, delete the existing configuration first, then create a new one.
## Validation and security
* **Host validation** — SMTP hosts must resolve to a public IP address. Private, loopback, and link-local addresses are blocked.
* **Encrypted credentials** — SMTP usernames and passwords are encrypted at rest and are never exposed through the API.
* **Input sanitization** — All inputs are sanitized to prevent CRLF injection attacks.
If your SMTP server uses a self-signed certificate, you may need to set the `NODE_EXTRA_CA_CERTS` environment variable to the path of your CA certificate so that Cal.com can establish a secure connection.
## Troubleshooting
| Issue | Solution |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Connection test fails | Verify that the SMTP host is publicly accessible and the port is open. Check that your firewall allows outbound connections on the configured port. |
| Authentication error | Double-check your SMTP username and password. Some providers require app-specific passwords when two-factor authentication is enabled. |
| Test email not received | Check spam/junk folders. Verify that your DNS records (SPF, DKIM, DMARC) are configured to authorize the SMTP server to send on behalf of your domain. |
| "SMTP host must be a public address" | The hostname resolves to a private or reserved IP. Use a publicly routable SMTP server. |
# Guest notification settings
Source: https://cal.com/docs/self-hosting/guides/organization/guest-notification-settings
Configure organization-level notification preferences for booking guests
Organization admins can control how and when booking guests receive notifications. These settings define the default notification behavior for all members in the organization, while individual users can still override preferences at their own level.
## How notification precedence works
Cal.com resolves notification preferences using a hierarchical model:
1. **Organization defaults** — set by the org admin, applied to all members
2. **User overrides** — individual members can override organization defaults
When a notification event occurs (for example, a booking confirmation), Cal.com checks whether the user has a personal preference set. If not, the organization default applies.
## Supported notification events
The following booking events can trigger guest notifications:
| Event | Description |
| ------------------- | -------------------------------------- |
| Booking confirmed | A new booking is confirmed |
| Booking cancelled | An existing booking is cancelled |
| Booking rescheduled | A booking is rescheduled to a new time |
| Booking reminder | A reminder is sent before the booking |
## Supported notification channels
Organization admins can enable or disable notifications across these channels:
* **Email** — standard email notifications to guests
* **Web push** — browser push notifications for users with an active Cal.com session
* **SMS** — text message notifications (when a phone number is available)
## Configuring organization notification defaults
Go to **Settings** → **Organization** → **Notification preferences** in your Cal.com admin panel.
For each notification event, choose which channels should be enabled by default for all organization members.
Click **Save** to apply the new defaults. These settings take effect immediately for all members who have not set their own overrides.
By default, all notification channels are enabled for all events. Organization settings only need to be configured if you want to restrict or customize the default behavior.
## User-level overrides
Individual organization members can override the organization defaults from their personal notification settings:
1. Go to **Settings** → **Notifications**
2. Adjust preferences for specific events or channels
3. Save changes
User-level preferences always take priority over organization defaults.
## API configuration
You can also manage notification preferences programmatically using the Cal.com API. Organization notification preferences are managed through the organization settings endpoints.
```bash theme={null}
# Example: Update organization notification preferences
curl -X PATCH https://api.cal.com/v2/organizations/{orgId} \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"notificationPreferences": {
"bookingConfirmed": {
"email": true,
"webPush": true,
"sms": false
},
"bookingCancelled": {
"email": true,
"webPush": false,
"sms": false
}
}
}'
```
Refer to the [API v2 Reference](/docs/api-reference/v2/introduction) for full details on available endpoints and parameters.
# Notification preferences
Source: https://cal.com/docs/self-hosting/guides/organization/notification-preferences
Configure organization-wide notification defaults and allow users to override them with hierarchical preference resolution.
Organizations can define default notification preferences that apply to all members. Individual users can then override these defaults for their own accounts. This hierarchical system follows an **organization → user** precedence model, so organization admins set the baseline and users customize from there.
## How it works
Notification preferences control which channels are used for each notification event. The supported channels are:
* **Email** — standard email notifications
* **In-app** — notifications within the Cal.com interface
* **Push** — browser push notifications
* **SMS** — text message notifications
Each notification event (such as booking confirmations) can be independently enabled or disabled per channel.
### Precedence rules
When Cal.com determines how to notify a user, it resolves preferences using this order:
1. **User preferences** — if the user has set a preference for a specific event and channel, that value is used
2. **Organization preferences** — if the user has not set a preference, the organization default is used
3. **System defaults** — if neither the user nor the organization has set a preference, the built-in system default applies
Preferences are resolved per event and per channel. A user can override the email channel for booking confirmations while still inheriting the organization default for push notifications on the same event.
## Setting organization defaults
Organization admins can configure default notification preferences that apply to all members. These defaults act as the baseline configuration — any member who has not set their own preference inherits the organization-level setting.
To configure organization notification defaults:
Go to **Settings** → **Organization** → **Notification preferences**.
For each notification event, toggle the channels (email, in-app, push, SMS) on or off as needed.
Save your changes. The new defaults apply immediately to all members who have not set their own preferences.
## User-level overrides
Individual users can override organization defaults from their personal notification settings. When a user sets a preference, it takes priority over the organization default for that specific event and channel.
Users who have not customized their preferences automatically inherit whatever the organization admin has configured.
## Supported notification events
Notification preferences can be configured for events such as:
* **Booking confirmed** — when a booking is confirmed
* **Booking cancelled** — when a booking is cancelled
* **Booking rescheduled** — when a booking is rescheduled
The list of supported notification events may expand over time. Check your notification settings page for the current set of configurable events.
## Cleanup behavior
Notification preferences are automatically removed when the associated entity is deleted:
* Deleting a **user** removes all of that user's notification preferences
* Deleting a **team** removes all team-level notification preferences
* Deleting an **organization** removes all organization-level notification preferences
No manual cleanup is required.
# Organization Setup
Source: https://cal.com/docs/self-hosting/guides/organization/organization-setup
This guide will walk you through setting up and configuring the organizations feature for your self-hosted Cal.com instance. Organizations allow you to create branded, multi-tenant environments within your Cal.com deployment.
## Prerequisites
Before setting up organizations, ensure you have:
* A working Cal.com instance already installed and running
* Admin access to your Cal.com instance
* Access to modify environment variables and restart your server
## Step 1: Enable Organizations Feature
1. Login as admin using `admin@example.com`
2. Navigate to **Settings** → **Admin** → **Features**
3. Turn on the **Organizations** feature flag under the Features section
## Step 2: Configure Environment Variables
Set the following environment variables in your `.env` file:
```bash theme={null}
NEXT_PUBLIC_WEBAPP_URL=http://app.cal.local:3000
NEXT_PUBLIC_WEBSITE_URL=http://app.cal.local:3000
NEXTAUTH_URL=http://app.cal.local:3000
ORGANIZATIONS_ENABLED=1
```
## Step 3: Configure Local DNS
Add the following entry to your hosts file to enable local access:
```bash theme={null}
127.0.0.1 app.cal.local
```
**Host file locations:**
* **Linux/Mac**: `/etc/hosts`
* **Windows**: `C:\Windows\System32\drivers\etc\hosts`
## Step 4: Start/Restart Development Server
After making the configuration changes, start/restart your development server:
```bash theme={null}
yarn dev
```
## Step 5: Create an Organization
1. Login using `pro@example.com` (or any user account)
2. Visit `http://app.cal.local:3000/settings/organizations/new`
3. Follow the onboarding steps:
* Choose a slug for the organization(say `myorg`)
* Ignore pricing information (not required for self-hosting)
* Complete the first step (remaining steps can be skipped for now)
After creating the organization, you'll be moved inside it and all existing Cal.com links will redirect to the organization URL (e.g., `yourdomain.cal.local:3000`).
## Accessing Your Organization
Now if everything above went well, all booking pages for the organization will be accessible at:
```
http://myorg.cal.local:3000/{CAL_LINK}
```
Read more about some other [Organization related environment variables](./understanding-organization-env-variables.mdx) to configure your Cal.com instance to work with multiple organizations.
# Single Organization Setup
Source: https://cal.com/docs/self-hosting/guides/organization/single-organization-setup
This guide will walk you through setting up your cal.com instance to work with an organization using the same domain.
Note that an organization's booking pages are by default accessible at `http://{orgSlug}.yourcalinstance.example/{CAL_LINK}` but with this setup, you won't need another domain, if you want to use just one organization.
## Step 1: Identify what slug you want to use for your organization
For a company with domain `yourdomain.com`, a good slug would be `yourdomain`.
## Step 2: Setup and create an organization
Follow the [Organization Setup](./organization-setup) guide to enable organizations feature.
Make sure to use the slug you decided in Step 1.
## Step 3: Add Environment Variable
Set the following environment variable in your `.env` file:
```bash theme={null}
# yourdomain is the slug you decided in Step 1
NEXT_PUBLIC_SINGLE_ORG_SLUG=yourdomain
```
## Step 4: Restart Development Server
After making the configuration changes, restart your development server:
```bash theme={null}
yarn dev
```
# Environment Variables
Source: https://cal.com/docs/self-hosting/guides/organization/understanding-organization-env-variables
This guide explains the key environment variables used to configure organizations in your Cal.com self-hosted instance.
## RESERVED\_SUBDOMAINS
This is a comma-separated list of subdomains that are not allowed to be used as organization slugs.
For example, if you set `RESERVED_SUBDOMAINS="app,auth,help"`, then the domains `app.cal.com`, `auth.cal.com` and `help.cal.com` are reserved for use by the Cal.com instance. This means you cannot create an organization with the slug `app`, `auth` or `help`.
## ALLOWED\_HOSTNAMES
This is a comma-separated list of domains on whose subdomains the booking pages and dashboard are allowed to work.
**Examples:**
If `ALLOWED_HOSTNAMES="cal.com,cal.dev"`, then:
* `acme.cal.com` is a valid organization booking domain and `WEBAPP_URL` can be set to `http://app.cal.com`
* `dunder.cal.dev` is also a valid organization subdomain and `WEBAPP_URL` can be set to `http://app.cal.dev`
If `ALLOWED_HOSTNAMES="cal.domain.tld"`, then:
* `WEBAPP_URL` should be set to `http://app.cal.domain.tld`
* Valid organization domains include: `acme.cal.domain.tld`, `dunder.cal.domain.tld`, `xxxx.cal.domain.tld`
Make sure your DNS is properly configured to point organization subdomains to your Cal.com instance.
# Can Cal.com sponsor my open source project?
Source: https://cal.com/docs/self-hosting/guides/sponsorship/can-calcom-sponsor-my-open-source-project
Cal.com is a commercial open source company and thereby we fully support other open source projects, whether it's commercial or non-commercial. We're friends of many other COSS companies.
## Sponsorship Criteria
* The project should be non-commercial or a commercial open source company with less than \$2M in ARR
* The project should be open source
* Include our "Book us with Cal.com" banner in your footer and link to a Cal.com booking link of your choice
* Include our "Book us with Cal.com" banner in your open source repository's README.md file and link to a Cal.com booking link of your choice
* Add the UTM tag with the format for the banners: ?utm\_source=banner\&utm\_campaign=oss
or
### HTML Code:
```html theme={null}
```
or
```html theme={null}
```
## How to apply
Please note that we have a limited number of openings for OSS sponsorships each month. If you're interested in applying for a sponsorship for your open source software project, please follow these steps:
* Ensure that you meet all the sponsorship criteria mentioned above.
* Send an email to [peer@cal.com](mailto:peer@cal.com) and explain what you are working on and confirm that you meet all the sponsorship criteria (watch out, Peer may invest a small angel check in your COSS company 👀)
* Explaining why Cal.com's free tier or open core is insufficient for your project
* Include your Cal.com team slug and provide a link to your open source repository in the email.
Please note that sponsorship requests are reviewed on a monthly basis, so allow some time before reaching out for updates. We reserve the right to reject any project at will without reason.
# Instance-wide theming using color tokens
Source: https://cal.com/docs/self-hosting/guides/white-labeling/color-tokens
Cal.com offers instance-wide theming capabilities with minimum friction. Administrators and developers have the flexibility to tailor the look and feel of the app to align with their brand identity or specific aesthetic preferences with very easy and minor changes.
We use color tokens, which are the primitive color values in our design system, represented by context names. Their values can easily be set inside the `apps/web/styles/globals.css` file, an example of which is shown below:
```css theme={null}
**apps/web/styles/globals.css**
/* background */
:root {
--cal-bg-emphasis: #e5e7eb;
--cal-bg: white;
--cal-bg-subtle: #f3f4f6;
--cal-bg-muted: #f9fafb;
--cal-bg-inverted: #111827;
/* background -> components*/
--cal-bg-info: #dee9fc;
--cal-bg-success: #e2fbe8;
--cal-bg-attention: #fceed8;
--cal-bg-error: #f9e3e2;
--cal-bg-dark-error: #752522;
...
}
.dark {
/* background */
--cal-bg-emphasis: #2b2b2b;
--cal-bg: #101010;
--cal-bg-subtle: #2b2b2b;
--cal-bg-muted: #1c1c1c;
--cal-bg-inverted: #f3f4f6;
/* background -> components*/
--cal-bg-info: #dee9fc;
--cal-bg-success: #e2fbe8;
--cal-bg-attention: #fceed8;
--cal-bg-error: #f9e3e2;
--cal-bg-dark-error: #752522;
...
}
```
To modify the theme of an instance, the hexcodes can easily be replaced here to instantly and seamlessly. This ensures a consistent and harmonious visual experience across all sections of the application, reflecting your desired aesthetic with precision.
To have a better understanding of our design system, you can view our [design documentation here](https://design.cal.com/basics/colors). If it peaks your interest, you can view the [Figma file of our Design Tokens here](https://www.figma.com/file/9MOufQNLtdkpnDucmNX10R/Cal-DS---Components?type=design\&node-id=25883-174646\&mode=design).
By allowing themes to be applied across the entire instance, businesses can ensure that their brand identity remains consistent.
Should there be any rebranding or minor tweaks to the corporate visual identity, instance-wide theming allows for easy and swift updates. With minimal changes, the new theme can be propagated across the entire platform, eliminating the need for manual adjustments on individual sections or components.
# How to add custom CSS
Source: https://cal.com/docs/self-hosting/guides/white-labeling/custom-css
Cal.com uses [TailwindCSS](https://tailwindcss.com/) as a replacement for traditional CSS styling within the application, but some people prefer to add CSS styles themselves.
CSS files should be stored in the `styles` directory within the codebase.
Within the `styles` directory, you will find two CSS files, `fonts.css` and `global.css`. We suggest not to add to these files, and instead create new CSS files and import them into the application. This helps reduce conflicts when pulling in changes that we've made to either of the existing CSS files.
### Adding new stylesheets
Firstly, create the CSS file inside the `styles` directory.
Then, open the `pages/_app.tsx` file, and you will see the following two lines:
```js theme={null}
import "../styles/fonts.css";
import "../styles/globals.css";
```
Duplicate one of these import statements and change the path to link to your new CSS stylesheet, like so:
```js theme={null}
import "../styles/your-new-stylesheet.css";
```
These styles will apply to all pages and components in your application.
Due to the global nature of stylesheets, and to avoid conflicts, you may **only import them inside** **`pages/_app.tsx`**.
# How to white label the self hosted instance
Source: https://cal.com/docs/self-hosting/guides/white-labeling/introduction
We have made it very easy for you to white label your self-hosted instance.
Please update the following env variables to match your desired branding:
```
NEXT_PUBLIC_APP_NAME="acme.com"
NEXT_PUBLIC_SUPPORT_MAIL_ADDRESS="support@acme.com"
NEXT_PUBLIC_COMPANY_NAME="ACME inc."
```
The images for the logo are placed in `/web/public` . You can update the logo by changing the value of the following constants in `/packages/lib/constants.ts` file:
1. LOGO
2. LOGO\_ICON
to the file name of your logo and logo icon, or the paths with file name to your respective logo and logo icon images from `/web/public`.
The instance wide theme can be easily updated thanks to the color tokens we have in place. Simply modify the hex-codes within the `apps/web/styles/globals.css` file against the [color tokens](/docs/self-hosting/guides/white-labeling/color-tokens), and the entire application can be consistently whitelabeled to your brand identity.
If you're using custom SMTP server for emails, you might need to set `NODE_EXTRA_CA_CERTS` env variable equal to the path of your CA certificate so that it can be recognized by the calcom application.
# Installation
Source: https://cal.com/docs/self-hosting/installation
There are multiple ways in which you can deploy Cal.com, providing support for customers who want to implement Cal.com within their existing infrastructure stack. Let's go through them one-by-one. You can find the instructions for deployment in our README file, which is the section you see when you scroll down in our GitHub repository, or if you've got a copy of Cal.com downloaded already, you can open the file contained in the downloaded repository called `README.md`.
## Requirements
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.
## Production Build
1. First, you git clone the repository with the following command, so you have a copy of the code.
```
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 clone -c core.symlinks=true https://github.com/calcom/cal.com.git
```
2. Then, go into the directory you just cloned with
```
cd cal.com
```
and run
```
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.
For preview deployments on **Vercel**, please leave the following environment variables empty:
* **NEXTAUTH\_URL**
* **NEXT\_PUBLIC\_WEBSITE\_URL**
* **NEXT\_PUBLIC\_WEBAPP\_URL**
4. Next, use the command
```
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.
#### Production Build
For a production build, **please make sure** to set up [E2E testing](/docs/developing/local-development#e2e-testing) and [Upgrading](/docs/self-hosting/upgrading) the database from earlier version, and the proceed to build as follows:
```
yarn build
yarn start
```
Please make sure to upgrade your database before you build for production
## Cron Jobs
There are a few features which require cron job setup. When self-hosting, you would probably need to set up cron jobs according to the hosting platform you are using.
For instance, if you are hosting on Vercel, you would need to set up cron jobs by following [this document](https://vercel.com/guides/how-to-setup-cron-jobs-on-vercel).
At cal.com, the cron jobs are found in the following directory:
```
/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:
```
cp apps/api/v2/.env.example apps/api/v2/.env
cp .env.example .env
```
#### Step 2
Install packages with yarn:
```
yarn
```
### Running API server
Build & Run the API v2 with yarn:
```
yarn workspace @calcom/api-v2 build
yarn workspace @calcom/api-v2 start
```
## One Click Deployments
### Azure
## Elestio
### GCP
### Railway
### Render
### Vercel
## Other environments
Cal.com effectively is just a Next.js application, so any possible solution you find online pertaining to Next.js applications should work. One example is Netlify, which is pretty similar to Vercel. It says it supports Next.js, so you can deploy Cal.com on Netlify. Refer to Netlify's docs on Next.js projects for more info. Another example is on a self hosted instance people may want to configure complex reverse proxies, SSL gateways and all sorts of other stuff. We can't officially support every configuration, but for any edge case where you may want to deploy Cal.com with X, just refer to X's docs on Next.js applications and you should be fine.
That's it. Your new self hosted Cal.com instance should now be up and running.
For more details on specific deployment instructions for AWS, Azure, Railway, etc.
# License key
Source: https://cal.com/docs/self-hosting/license-key
If you wish to **self-host** Cal.com with our Commercial License, you need to purchase a License Key to do so.
Please contact our [sales team](https://cal.com/talk-to-sales) to acquire a license key
# SSO setup
Source: https://cal.com/docs/self-hosting/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.
You need an Enterprise License to avail this when self-hosting
### Setting up SAML login
1. Set SAML\_DATABASE\_URL to a Postgres database. Please use a different database than the main Cal instance since the migrations are separate for this database. For example `postgresql://postgres:@localhost:5450/cal-saml`. If you are using a self-signed certificate for Postgres then use the `sslmode=no-verify` query param in the database URL. For example `postgresql://postgres:@localhost:5450/cal-saml?sslmode=no-verify`.
2. Set SAML\_ADMINS to a comma separated list of admin emails from where the SAML metadata can be uploaded and configured.
3. Create a SAML application with your Identity Provider (IdP) using the instructions here - [SAML Setup](/docs/self-hosting/sso-setup#saml-registration-with-identity-providers)
4. Remember to configure access to the IdP SAML app for all your users (who need access to Cal).
5. You will need the XML metadata from your IdP later, so keep it accessible.
6. Log in to one of the admin accounts configured in SAML\_ADMINS and then navigate to Settings -> Security.
7. You should see a SAML configuration section, copy and paste the XML metadata from step 5 and click on Save.
8. Your provisioned users can now log into Cal using SAML.
### SAML Registration with Identity Providers
This guide explains the settings you need to use to configure SAML with your Identity Provider. Once this is set up you should get an XML metadata file that should then be uploaded on your Cal.com self-hosted 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\://\/api/auth/saml/callback \[Replace the placeholder with the URL for your self-hosted Cal instance]
**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
1. Set SAML\_DATABASE\_URL to a Postgres database. Please use a different database than the main Cal instance since the migrations are separate for this database. For example `postgresql://postgres:@localhost:5450/cal-saml`. If you are using a self-signed certificate for Postgres then use the `sslmode=no-verify` query param in the database URL. For example `postgresql://postgres:@localhost:5450/cal-saml?sslmode=no-verify`.
2. Set SAML\_ADMINS to a comma separated list of admin emails who can configure the OIDC.
3. Keep handy the Client Secret, Client ID and Well Known URL with you for the next step.
4. Spin up cal.com on your server and login with the Admin user (the email ID of which was provided in step 2 for SAML\_ADMINS environment variable).
5. Visit `{BASE_URL}/settings/security/sso` and you should see something like this:
6. Click on Configure SSO with OIDC, and then enter the Client Secret, Client ID and Well known URL from the Step 3, and click save.
7. That's it. Now when you try to login with SSO, your OIDC provider will handle the auth.
# Troubleshooting
Source: https://cal.com/docs/self-hosting/troubleshooting
Solutions for common issues when self-hosting Cal.com
This guide covers the most common issues encountered when self-hosting Cal.com, along with their solutions.
## Onboarding / setup issues
### Stripe payment features not working
**Symptom:** Stripe-related features (paid events, app store payment integration) are not available, or you see errors when attempting to use payment features.
**Cause:** The Stripe integration requires several environment variables to be configured. These variables are defined across two files: `.env` (root) and `.env.appStore`. If they are missing or empty, the Stripe app will be marked as "not installed" and payment-related features will be unavailable.
**Solution:** Add the following variables to your `.env` file (root):
```env theme={null}
STRIPE_PRIVATE_KEY=sk_test_...
STRIPE_CLIENT_ID=ca_...
STRIPE_WEBHOOK_SECRET=whsec_...
```
And in your `.env.appStore` file (or `.env` if using a single file):
```env theme={null}
NEXT_PUBLIC_STRIPE_PUBLIC_KEY=pk_test_...
```
Replace these with your actual Stripe API keys from the [Stripe Dashboard](https://dashboard.stripe.com/apikeys). If you don't need payment features, you can safely leave these empty — the app will function without them, but Stripe-related features will be disabled.
***
## URL and redirect issues
### Redirect to `localhost:3000` after deployment
**Symptom:** After deploying Cal.com to a server or domain, login redirects or internal links point back to `http://localhost:3000` instead of your actual domain.
**Cause:** The environment variables `NEXTAUTH_URL` and `NEXT_PUBLIC_WEBAPP_URL` are not set to your production domain. These variables tell Cal.com and NextAuth where the app is hosted.
**Solution:** Update your `.env` file to use your actual domain:
```env theme={null}
# Replace with your actual domain
NEXT_PUBLIC_WEBAPP_URL=https://cal.yourdomain.com
NEXTAUTH_URL=https://cal.yourdomain.com
```
Do **not** include a trailing slash in your URLs.
**Important notes:**
* `NEXTAUTH_URL` is optional if `NEXT_PUBLIC_WEBAPP_URL` is set — NextAuth will infer the base URL from the incoming request's `Host` header when `NEXTAUTH_URL` is not explicitly configured.
* For Docker deployments, these must be set **before** building the image, as `NEXT_PUBLIC_WEBAPP_URL` is a build-time variable (it is inlined by Next.js during the build). Rebuild the image after changing it.
* For Vercel deployments, you do **not** need to set `NEXTAUTH_URL` — Vercel automatically infers it from the deployment URL via the `VERCEL_URL` environment variable.
***
## Docker-specific issues
### API v2 service not starting
**Symptom:** After running `docker compose up -d`, the `calcom-api` service fails to start or immediately exits. The web app works, but API v2 endpoints (`/api/v2/...`) return connection errors.
**Cause:** The API v2 service requires several environment variables that are not in the root `.env.example`. If any required variable is missing, the service will throw a `Missing environment variable` error and exit on startup.
**Solution:** Add the following variables to your root `.env` file (the `docker-compose.yml` passes these to the `calcom-api` service):
```env theme={null}
# Required for API v2 (service will not start without these)
REDIS_URL=redis://redis:6379
JWT_SECRET=your_random_jwt_secret_here
NEXTAUTH_SECRET=your_nextauth_secret_here
CALENDSO_ENCRYPTION_KEY=your_32_character_encryption_key
STRIPE_API_KEY=sk_test_your_stripe_key
STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret
# Optional (have sensible defaults)
WEB_APP_URL=https://cal.yourdomain.com
REDIS_PORT=6379
LOG_LEVEL=warn
```
`STRIPE_API_KEY` is the API v2 equivalent of `STRIPE_PRIVATE_KEY` used by the web app. Both are Stripe secret keys but are consumed by different services. If you don't need Stripe functionality in the API, you can set a placeholder value (e.g., `sk_test_placeholder`), but the variable must be present.
You can generate secure secrets with:
```bash theme={null}
openssl rand -base64 32
```
Then restart the stack:
```bash theme={null}
docker compose down && docker compose up -d
```
Check the API v2 service logs for further details if it still fails:
```bash theme={null}
docker compose logs calcom-api
```
The API v2 service has its own `.env.example` at `apps/api/v2/.env.example` with a complete list of all supported variables. Refer to it for the full configuration reference.
***
### `CLIENT_FETCH_ERROR` in logs
**Symptom:** The following error appears in Docker logs, and login/session fails:
```
[next-auth][error][CLIENT_FETCH_ERROR]
request to http:///api/auth/session failed, reason: getaddrinfo ENOTFOUND
```
**Cause:** The Docker container cannot resolve the external hostname set in `NEXTAUTH_URL` from within the container's network.
**Solution:** Add your domain to the container's `/etc/hosts` so it resolves to itself:
```yaml theme={null}
# docker-compose.yml
services:
calcom:
extra_hosts:
- "cal.yourdomain.com:127.0.0.1"
```
This allows the container to resolve your public domain internally while keeping `NEXTAUTH_URL` set to your public URL (required for OAuth callbacks to work).
This approach works when `NEXTAUTH_URL` uses HTTP (e.g., `http://cal.yourdomain.com:3000`). If your `NEXTAUTH_URL` uses HTTPS, the container will attempt to connect to `127.0.0.1:443`, while the app listens on port 3000 — this will fail. In that case, ensure your reverse proxy is also running inside the Docker network, or see the SSL section below.
Do **not** set `NEXTAUTH_URL` to `localhost` — this would fix the DNS error but **breaks OAuth**. External providers like Google would redirect to `localhost` instead of your domain.
***
### SSL / HTTPS issues behind a reverse proxy
**Symptom:** Requests fail with SSL certificate errors when Cal.com is behind a load balancer or reverse proxy that handles HTTPS termination.
**Solution:** Choose one of the following approaches:
Keep `NEXTAUTH_URL` set to your **public-facing HTTPS URL**:
```env theme={null}
NEXTAUTH_URL=https://cal.yourdomain.com
```
Then configure your reverse proxy to forward the appropriate headers so internal requests work correctly:
```nginx theme={null}
# Nginx example
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
```
If you are using self-signed certificates internally, add your internal CA to Node's trusted certificates:
```env theme={null}
NODE_EXTRA_CA_CERTS=/path/to/your-internal-ca.crt
```
```env theme={null}
NODE_TLS_REJECT_UNAUTHORIZED=0
```
This disables **all** TLS certificate verification globally, including external API calls to Stripe, Google Calendar, and other services. This makes your instance vulnerable to man-in-the-middle attacks. Only use this in isolated development environments where you fully control all network traffic.
***
## Database issues
### First user setup fails
**Symptom:** Attempting to create the first admin user via the `/setup` page results in an error.
**Cause:** This can occur if the database migrations have not been applied, or if the database is in an inconsistent state.
**Solution:**
```bash theme={null}
# For development
yarn workspace @calcom/prisma db-migrate
# For production / Docker
yarn workspace @calcom/prisma db-deploy
```
Confirm that your `DATABASE_URL` in `.env` is correct and the database is accessible.
If the issue persists after migrations, check the application logs for the specific Prisma error message — it will indicate which field or constraint is failing.
The setup endpoint (`/auth/setup`) creates the first user with the `ADMIN` role. It only works when the `User` table is completely empty. If a previous setup attempt partially succeeded, you may need to manually clear the users table before retrying.
***
## Getting further help
If your issue is not listed here:
1. Search the [Cal.com GitHub Issues](https://github.com/calcom/cal.com/issues) — many common problems have documented solutions in issue threads.
2. Check the [Cal.com Community](https://community.cal.com) forum.
3. Review the [Docker configuration](/docs/self-hosting/docker) and [Installation guide](/docs/self-hosting/installation) for any steps you may have missed.
# Upgrading
Source: https://cal.com/docs/self-hosting/upgrading
Pull the current version:
```
git pull
```
Check if dependencies got added/updated/removed
```
yarn
```
Apply database migrations by running **one of** the following commands:
In a development environment, run:
```
yarn workspace @calcom/prisma db-migrate
```
(this can clear your development database in some cases)
In a production environment, run:
```
yarn workspace @calcom/prisma db-deploy
```
Check for `.env` variables changes
```
yarn predev
```
Start the server. In a development environment, just do:
```
yarn dev
```
For a production build, run for example:
```
yarn build
yarn start
```
Enjoy the new version.