> ## Documentation Index
> Fetch the complete documentation index at: https://cal.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.
# OAuth
> Authorize apps with Cal.com accounts using OAuth
As an example, you can view our OAuth flow in action on Zapier. Try to connect your Cal.com account [here](https://zapier.com/apps/calcom/integrations). 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.
#### 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.
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.
## 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`
**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
```
#### 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 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
```
## 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
}
```
Access tokens expire after 30 minutes (`expires_in: 1800`). Use the refresh token to obtain a new access token.
#### 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
}
```
#### 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. 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" },
});
```