calcom/cal.diy · error · UnauthorizedException
Invalid Access token.
Error message
Invalid Access token.
What it means
Thrown by GoogleCalendarService.saveCalendarCredentialsAndRedirect after the OAuth redirect callback returns from Google. The handler calls tokensService.getAccessTokenOwnerId(accessToken) to resolve the Cal API user that owns the supplied platform access token; if no owner row maps to the token, the request is treated as unauthenticated and HTTP 401 UnauthorizedException('Invalid Access token.') is raised. This protects the OAuth-code exchange step (the subsequent oAuth2Client.getToken call and credential persistence) so a credential is only ever stored against a verified user.
Source
Thrown at apps/api/v2/src/platform/calendars/services/gcal.service.ts:151
isDryRun?: boolean
) {
// User chose not to authorize your app or didn't authorize your app
// redirect directly without oauth code
if (!code || code === "undefined") {
return { url: redir || origin };
}
// if isDryRun is true we know its a dry run so we just redirect straight away
if (isDryRun) {
return { url: redir || origin };
}
const parsedCode = z.string().parse(code);
const ownerId = await this.tokensService.getAccessTokenOwnerId(accessToken);
if (!ownerId) {
throw new UnauthorizedException("Invalid Access token.");
}
const oAuth2Client = await this.getOAuthClient(this.redirectUri);
const token = await oAuth2Client.getToken(parsedCode);
// Google oAuth Credentials are stored in token.tokens
const key = token.tokens;
oAuth2Client.setCredentials(key);
const calendar = new calendar_v3.Calendar({
auth: oAuth2Client,
});
const cals = await calendar.calendarList.list({ fields: "items(id,summary,primary,accessRole)" });
const primaryCal = cals.data.items?.find((cal) => cal.primary);
if (primaryCal?.id) {View on GitHub (pinned to 176037d0af)
Solutions
- Re-authenticate via the platform OAuth flow to mint a fresh access token, then retry the calendar callback.
- Verify the token being passed is the Cal.com platform access token (not a Google OAuth code or Cal refresh token) and that it is sent in the header/param the handler expects.
- Confirm the API host issuing the token and the host serving the callback are the same deployment and share the same token-signing secret/database.
- Inspect the tokensService.getAccessTokenOwnerId implementation to confirm the token is being decoded against the correct secret and that the owner row still exists.
Example fix
// before
const res = await fetch(`/v2/calendars/google/connect/callback?code=${code}`, { headers: {} });
// after
const res = await fetch(`/v2/calendars/google/connect/callback?code=${code}`, {
headers: { Authorization: `Bearer ${calAccessToken}` },
}); Defensive patterns
Strategy: validation
Validate before calling
async function ensureTokenOwner(tokensService, accessToken) {
const ownerId = await tokensService.getAccessTokenOwnerId(accessToken);
if (!ownerId) {
return { ok: false, reason: 'Invalid Access token.' };
}
return { ok: true, ownerId };
} Type guard
function isNonEmptyToken(t: unknown): t is string {
return typeof t === 'string' && t.trim().length > 0 && t !== 'undefined';
} Try / catch
try {
await googleCalendarService.saveCalendarCredentialsAndRedirect(code, accessToken, origin);
} catch (e) {
if (e instanceof UnauthorizedException && e.message === 'Invalid Access token.') {
// re-authenticate the user, then retry once
} else throw e;
} Prevention
- Refresh the Cal access token before the OAuth redirect if the flow may outlast its TTL.
- Pass the token in the exact location the handler reads (Authorization header).
- Keep the deployment that issued the token and the deployment serving the callback identical.
When it happens
Trigger: Calling GET /v2/calendars/google/connect/callback (or saveCalendarCredentialsAndRedirect) with an accessToken that is expired, revoked, malformed, or belongs to no user; passing the same token after it was rotated; the Authorization header / query token is missing so accessToken resolves to undefined.
Common situations: Token TTL elapsed between the start of the OAuth flow and the redirect callback; the access token was issued by a different Cal API deployment/environment (env mismatch); the client sent the refresh token or an opaque ID instead of the platform access token; clock skew on the server makes a valid token appear expired.
Related errors
- Invalid Access token.
- CustomThrottlerGuard - Invalid API Key
- ApiKeysService - This endpoint can only be accessed using an
- ApiKeysService - No API key provided
- ApiKeysService - provided api key is not valid.
AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12).
Data as JSON: /api/errors/b7ef55d69e41b1a3.
Report an issue: GitHub.