calcom/cal.diy · error · UnauthorizedException
ApiAuthStrategy - access token - Invalid Access Token.
Error message
ApiAuthStrategy - access token - Invalid Access Token.
What it means
Thrown by ApiAuthStrategy.accessTokenStrategy when an OAuth access token presented in the Authorization header fails validation via oauthFlowService.validateAccessToken. This is the API v2 platform auth guard: it accepts an OAuth2 access token issued by Cal.com's own OAuth server, and this branch means the token was rejected as invalid (malformed, revoked, expired, or never issued). The constant message comes from INVALID_ACCESS_TOKEN.
Source
Thrown at apps/api/v2/src/modules/auth/strategies/api-auth/api-auth.strategy.ts:264
if (isKeyExpired) {
throw new UnauthorizedException("ApiAuthStrategy - api key - Your api key is expired");
}
const apiKeyOwnerId = keyData.userId;
if (!apiKeyOwnerId) {
throw new UnauthorizedException("ApiAuthStrategy - api key - No user tied to this apiKey");
}
const user: UserWithProfile | null = await this.userRepository.findByIdWithProfile(apiKeyOwnerId);
request.organizationId = keyData.teamId;
return user;
}
async accessTokenStrategy(accessToken: string, request: ApiAuthGuardRequest, origin?: string) {
const accessTokenValid = await this.oauthFlowService.validateAccessToken(accessToken);
if (!accessTokenValid) {
throw new UnauthorizedException(`ApiAuthStrategy - access token - ${INVALID_ACCESS_TOKEN}`);
}
const client = await this.tokensRepository.getAccessTokenClient(accessToken);
if (!client) {
throw new UnauthorizedException(
"ApiAuthStrategy - access token - OAuth client not found given the access token"
);
}
if (origin && !isOriginAllowed(origin, client.redirectUris)) {
throw new UnauthorizedException(
`ApiAuthStrategy - access token - Invalid request origin - please open https://app.cal.com/settings/platform and add the origin '${origin}' to the 'Redirect uris' of your OAuth client with ID '${client.id}'`
);
}
const ownerId = await this.tokensRepository.getAccessTokenOwnerId(accessToken);
if (!ownerId) {View on GitHub (pinned to 176037d0af)
Solutions
- Re-obtain a fresh access token via the OAuth flow (POST /v2/oauth/:clientId/token with your client id/secret) and resend the request with the new token.
- Confirm you are sending the token as `Authorization: Bearer <access_token>` and not in x-cal-client-id/x-cal-secret-key (those are for the OAuth client credentials path).
- If you hold a static API key instead, switch to the API-key auth path (api-auth.strategy checks isApiKey first) rather than passing it as a Bearer token.
- Implement token refresh using the refresh_token so long-lived clients never send an expired access token.
Example fix
// before
fetch(`${API}/v2/...`, { headers: { Authorization: `Bearer ${storedAccessToken}` } });
// after
if (isExpired(storedAccessToken)) {
storedAccessToken = await refreshToken(clientId, clientSecret, refreshToken);
}
fetch(`${API}/v2/...`, { headers: { Authorization: `Bearer ${storedAccessToken}` } }); Defensive patterns
Strategy: try-catch
Validate before calling
// before each batch, ensure the access token is still valid
function isAccessTokenLikelyValid(token: string): boolean {
try {
const [, payload] = token.split('.');
const { exp } = JSON.parse(Buffer.from(payload, 'base64').toString());
return typeof exp === 'number' && exp * 1000 > Date.now() + 30_000;
} catch {
return false;
}
}
if (!isAccessTokenLikelyValid(accessToken)) {
accessToken = await refreshAccessToken(clientId, clientSecret, refreshToken);
} Type guard
function isOAuthAccessToken(v: unknown): v is string {
return typeof v === 'string' && v.split('.').length === 3 && v !== '';
} Try / catch
try {
await api.v2.someEndpoint();
} catch (err) {
if (err?.statusCode === 401 && /Invalid Access Token/i.test(err?.message)) {
accessToken = await refreshAccessToken(clientId, clientSecret, refreshToken);
return api.v2.someEndpoint(); // one retry with fresh token
}
throw err;
} Prevention
- Store the access token with its expiry and refresh preemptively before it lapses.
- Never confuse the platform OAuth access token with the static API key — they use different header paths.
- Centralize all /v2 calls behind a client wrapper that auto-refreshes on 401.
When it happens
Trigger: Calling any /v2/* platform endpoint with an Authorization: Bearer <token> whose token is not a currently-valid Cal.com OAuth access token. Specific triggers: token revoked via the platform settings UI, token past its expiry, token truncated/mistyped, or sending a NextAuth session JWT or a raw API key in the Bearer slot.
Common situations: Confusing the platform OAuth access token with the static API key (the static key uses a different header path); using a token from a stale/other environment (dev token against prod); the access token expired between issue and use because the client never refreshed it.
Related errors
- ApiAuthStrategy - access token - OAuth client not found give
- ApiAuthStrategy - access token - Invalid Access Token.. No o
- Invalid Access token.
- PermissionsGuard - no oAuth client found for access token=${
- ApiAuthStrategy - access token - Invalid request origin - pl
AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12).
Data as JSON: /api/errors/42442c674f0cc634.
Report an issue: GitHub.