docs/api-reference/v2/oauth.mdx
As an example, you can view our OAuth flow in action on Zapier. Try to connect your Cal.com account here. To enable OAuth in one of your apps, you will need a Client ID, Client Secret, Authorization URL, Access Token Request URL, and Refresh Token Request URL.
<Frame>  </Frame>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.
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.
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
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 |
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
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_id.redirect_uri does not match the one registered for the OAuth client.If an error occurs after the client is validated (e.g., the user denies access or has insufficient permissions), the user is redirected to the redirect_uri with an error:
https://your-app.com/callback?error=access_denied&error_description=team_not_found_or_no_access&state=YOUR_STATE
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
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 |
const tokens = await response.json();
```
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",
}
);
```
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 |
const tokens = await response.json();
```
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",
}
);
```
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer",
"expires_in": 1800
}
Error responses include error and error_description fields.
Refresh an expired access token using a refresh token.
Endpoint: POST https://api.cal.com/v2/auth/oauth2/token
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 |
const tokens = await response.json();
```
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",
}
);
```
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 |
const tokens = await response.json();
```
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",
}
);
```
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer",
"expires_in": 1800
}
To verify the correct setup and functionality of OAuth credentials, use the following endpoint:
Endpoint: GET https://api.cal.com/v2/me
const user = await response.json();
```
const { data } = await axios.get("https://api.cal.com/v2/me", {
headers: { Authorization: "Bearer YOUR_ACCESS_TOKEN" },
});
```