
Get your OAuth “Continue with Cal.com” Badge
- 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-squared.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-neutral-rounded.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. 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. 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:Some endpoints like
POST /v2/bookings (create), POST /v2/bookings/:bookingUid/cancel (cancel), and POST /v2/bookings/:bookingUid/reschedule (reschedule) are public endpoints that do not require any scope.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 /v2/teams/:teamId/event-types, GET /v2/teams/:teamId/event-types/:eventTypeId, GET /v2/organizations/:orgId/teams/:teamId/event-types, GET /v2/organizations/:orgId/teams/:teamId/event-types/:eventTypeId |
TEAM_EVENT_TYPE_WRITE | Create, edit, and delete team event types | POST /v2/teams/:teamId/event-types, PATCH /v2/teams/:teamId/event-types/:eventTypeId, DELETE /v2/teams/:teamId/event-types/:eventTypeId, POST /v2/teams/:teamId/event-types/:eventTypeId/create-phone-call, POST /v2/organizations/:orgId/teams/:teamId/event-types, PATCH /v2/organizations/:orgId/teams/:teamId/event-types/:eventTypeId, DELETE /v2/organizations/:orgId/teams/:teamId/event-types/:eventTypeId, POST /v2/organizations/:orgId/teams/:teamId/event-types/:eventTypeId/create-phone-call |
TEAM_BOOKING_READ | View team bookings | GET /v2/teams/:teamId/bookings, GET /v2/organizations/:orgId/teams/:teamId/bookings, GET /v2/organizations/:orgId/teams/:teamId/bookings/:bookingUid/references |
TEAM_BOOKING_WRITE | Create, edit, and delete team bookings | No endpoints currently use this scope |
TEAM_SCHEDULE_READ | View team schedules | GET /v2/teams/:teamId/schedules, GET /v2/organizations/:orgId/teams/:teamId/schedules, GET /v2/organizations/:orgId/teams/:teamId/users/:userId/schedules |
TEAM_SCHEDULE_WRITE | Create, edit, and delete team schedules | No endpoints currently use this scope |
TEAM_PROFILE_READ | View team profiles | GET /v2/teams, GET /v2/teams/:teamId, GET /v2/organizations/:orgId/teams/:teamId |
TEAM_PROFILE_WRITE | Create, edit, and delete teams | POST /v2/teams, PATCH /v2/teams/:teamId, DELETE /v2/teams/:teamId |
TEAM_MEMBERSHIP_READ | View team memberships | GET /v2/teams/:teamId/memberships, GET /v2/teams/:teamId/memberships/:membershipId, GET /v2/organizations/:orgId/teams/:teamId/memberships, GET /v2/organizations/:orgId/teams/:teamId/memberships/:membershipId |
TEAM_MEMBERSHIP_WRITE | Create, edit, and delete team memberships | POST /v2/teams/:teamId/memberships, PATCH /v2/teams/:teamId/memberships/:membershipId, DELETE /v2/teams/:teamId/memberships/:membershipId, POST /v2/teams/:teamId/invite, POST /v2/organizations/:orgId/teams/:teamId/memberships, PATCH /v2/organizations/:orgId/teams/:teamId/memberships/:membershipId, DELETE /v2/organizations/:orgId/teams/:teamId/memberships/:membershipId, POST /v2/organizations/:orgId/teams/:teamId/invite |
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 /v2/organizations/:orgId/teams/event-types |
ORG_EVENT_TYPE_WRITE | Create, edit, and delete event types across the organization | No endpoints currently use this scope |
ORG_BOOKING_READ | View all bookings across the organization | GET /v2/organizations/:orgId/bookings |
ORG_BOOKING_WRITE | Create, edit, and delete bookings across the organization | No endpoints currently use this scope |
ORG_SCHEDULE_READ | View schedules across the organization | GET /v2/organizations/:orgId/schedules, GET /v2/organizations/:orgId/users/:userId/schedules, GET /v2/organizations/:orgId/users/:userId/schedules/:scheduleId |
ORG_SCHEDULE_WRITE | Create, edit, and delete schedules across the organization | POST /v2/organizations/:orgId/users/:userId/schedules, PATCH /v2/organizations/:orgId/users/:userId/schedules/:scheduleId, DELETE /v2/organizations/:orgId/users/:userId/schedules/:scheduleId |
ORG_PROFILE_READ | View organization teams | GET /v2/organizations/:orgId/teams, GET /v2/organizations/:orgId/teams/me |
ORG_PROFILE_WRITE | Create, edit, and delete organization teams | POST /v2/organizations/:orgId/teams, PATCH /v2/organizations/:orgId/teams/:teamId, DELETE /v2/organizations/:orgId/teams/:teamId |
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 the registered redirect URI. |
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) |
redirect_uri with code (authorization code) and state as URL parameters:
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_uridoes not match the one registered for the OAuth client.
redirect_uri with an error:
- Scope required: If the
scopeparameter is missing, the errorscope parameter is required for this OAuth clientis displayed on the authorization page. - Unknown scope: If the
scopeparameter includes scope values that do not exist, the user is redirected witherror=invalid_scopeanderror_description=Requested scope is not a recognized scope. This applies to both regular and legacy clients. - Invalid scope: If the
scopeparameter includes scopes not enabled on the OAuth client, the user is redirected witherror=invalid_requestanderror_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.
3. Exchange Token
Exchange an authorization code for access and refresh tokens. The token endpoint also acceptsapplication/x-www-form-urlencoded content type.
Endpoint: POST https://api.cal.com/v2/auth/oauth2/token
3.1 Confidential Clients
Confidential clients authenticate with aclient_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 |
- cURL
- fetch
- axios
3.2 Public Clients (PKCE)
Public clients (e.g. single-page apps, mobile apps) use PKCE instead of aclient_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 |
- cURL
- fetch
- axios
Success Response (200)
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 includeerror and error_description fields.
Invalid or expired authorization code (400)
Invalid or expired authorization code (400)
Invalid client credentials (401)
Invalid client credentials (401)
client_secret does not match the client_id. Verify your credentials.Client not found (401)
Client not found (401)
client_id.Missing client_id (400)
Missing client_id (400)
client_id field is missing from the request body.Invalid grant_type (400)
Invalid grant_type (400)
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 aclient_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 |
- cURL
- fetch
- axios
4.2 Public Clients
Public clients do not use aclient_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 |
- cURL
- fetch
- axios
Success Response (200)
Scopes are preserved from the original authorization. You do not need to re-request scopes when refreshing tokens.
Error Responses
Invalid refresh token (400)
Invalid refresh token (400)
Invalid client credentials (401)
Invalid client credentials (401)
client_secret does not match the client_id.Client not found (401)
Client not found (401)
client_id.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 ascope 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.
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.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)
- Adding a
_READscope when the corresponding_WRITEscope is already granted (e.g. addingBOOKING_READwhenBOOKING_WRITEis already present) - Removing scopes
- Purpose description change
- Updating scopes on a legacy client, as long as only user-level scopes are added — adding
TEAM_orORG_scopes to a legacy client will trigger re-approval. See Legacy Client Migration for details.
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.5. 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
- cURL
- fetch
- axios