{"record":{"id":"42442c674f0cc634","repo":"calcom/cal.diy","slug":"apiauthstrategy-access-token-invalid-access-to","errorCode":null,"errorMessage":"ApiAuthStrategy - access token - Invalid Access Token.","messagePattern":"ApiAuthStrategy - access token - Invalid Access Token\\.","errorType":"http","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"apps/api/v2/src/modules/auth/strategies/api-auth/api-auth.strategy.ts","lineNumber":264,"sourceCode":"    if (isKeyExpired) {\n      throw new UnauthorizedException(\"ApiAuthStrategy - api key - Your api key is expired\");\n    }\n\n    const apiKeyOwnerId = keyData.userId;\n    if (!apiKeyOwnerId) {\n      throw new UnauthorizedException(\"ApiAuthStrategy - api key - No user tied to this apiKey\");\n    }\n\n    const user: UserWithProfile | null = await this.userRepository.findByIdWithProfile(apiKeyOwnerId);\n    request.organizationId = keyData.teamId;\n\n    return user;\n  }\n\n  async accessTokenStrategy(accessToken: string, request: ApiAuthGuardRequest, origin?: string) {\n    const accessTokenValid = await this.oauthFlowService.validateAccessToken(accessToken);\n    if (!accessTokenValid) {\n      throw new UnauthorizedException(`ApiAuthStrategy - access token - ${INVALID_ACCESS_TOKEN}`);\n    }\n\n    const client = await this.tokensRepository.getAccessTokenClient(accessToken);\n    if (!client) {\n      throw new UnauthorizedException(\n        \"ApiAuthStrategy - access token - OAuth client not found given the access token\"\n      );\n    }\n\n    if (origin && !isOriginAllowed(origin, client.redirectUris)) {\n      throw new UnauthorizedException(\n        `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}'`\n      );\n    }\n\n    const ownerId = await this.tokensRepository.getAccessTokenOwnerId(accessToken);\n\n    if (!ownerId) {","sourceCodeStart":246,"sourceCodeEnd":282,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/apps/api/v2/src/modules/auth/strategies/api-auth/api-auth.strategy.ts#L246-L282","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nfetch(`${API}/v2/...`, { headers: { Authorization: `Bearer ${storedAccessToken}` } });\n\n// after\nif (isExpired(storedAccessToken)) {\n  storedAccessToken = await refreshToken(clientId, clientSecret, refreshToken);\n}\nfetch(`${API}/v2/...`, { headers: { Authorization: `Bearer ${storedAccessToken}` } });","handlingStrategy":"try-catch","validationCode":"// before each batch, ensure the access token is still valid\nfunction isAccessTokenLikelyValid(token: string): boolean {\n  try {\n    const [, payload] = token.split('.');\n    const { exp } = JSON.parse(Buffer.from(payload, 'base64').toString());\n    return typeof exp === 'number' && exp * 1000 > Date.now() + 30_000;\n  } catch {\n    return false;\n  }\n}\nif (!isAccessTokenLikelyValid(accessToken)) {\n  accessToken = await refreshAccessToken(clientId, clientSecret, refreshToken);\n}","typeGuard":"function isOAuthAccessToken(v: unknown): v is string {\n  return typeof v === 'string' && v.split('.').length === 3 && v !== '';\n}","tryCatchPattern":"try {\n  await api.v2.someEndpoint();\n} catch (err) {\n  if (err?.statusCode === 401 && /Invalid Access Token/i.test(err?.message)) {\n    accessToken = await refreshAccessToken(clientId, clientSecret, refreshToken);\n    return api.v2.someEndpoint(); // one retry with fresh token\n  }\n  throw err;\n}","preventionTips":["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."],"tags":["auth","oauth","access-token","api-v2","platform"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}