{"record":{"id":"8aff6aede9b8bbd9","repo":"calcom/cal.diy","slug":"apiauthstrategy-third-party-token-no-owner-fou","errorCode":null,"errorMessage":"ApiAuthStrategy - third-party token - No owner found for the associated team.","messagePattern":"ApiAuthStrategy - third-party token - No owner found for the associated team\\.","errorType":"http","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"apps/api/v2/src/modules/auth/strategies/api-auth/api-auth.strategy.ts","lineNumber":340,"sourceCode":"    request: ApiAuthGuardRequest\n  ): Promise<{ success: true; data: UserWithProfile } | { success: false }> {\n    const decodedToken = this.tokensService.getDecodedThirdPartyAccessToken(token);\n    if (!decodedToken) {\n      return { success: false };\n    }\n\n    let user: UserWithProfile | null = null;\n    let organizationId: number | null = null;\n\n    if (decodedToken.userId) {\n      user = await this.userRepository.findByIdWithProfile(decodedToken.userId);\n      if (user) {\n        organizationId = this.usersService.getUserMainOrgId(user) as number;\n      }\n    } else if (decodedToken.teamId) {\n      const teamOwner = await this.userRepository.findOwnerByTeamIdWithProfile(decodedToken.teamId);\n      if (!teamOwner) {\n        throw new UnauthorizedException(\n          \"ApiAuthStrategy - third-party token - No owner found for the associated team.\"\n        );\n      }\n      user = teamOwner;\n      organizationId =\n        teamOwner.profiles?.find((p) => p.organizationId === decodedToken.teamId)?.organizationId ?? null;\n    }\n\n    if (!user) {\n      throw new UnauthorizedException(\n        \"ApiAuthStrategy - third-party token - No user or team owner associated with the token.\"\n      );\n    }\n\n    request.organizationId = organizationId;\n    return { success: true, data: user };\n  }\n}","sourceCodeStart":322,"sourceCodeEnd":358,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/apps/api/v2/src/modules/auth/strategies/api-auth/api-auth.strategy.ts#L322-L358","documentation":"Thrown by validateThirdPartyAccessToken when a third-party (decoded) access token carries a teamId but findOwnerByTeamIdWithProfile(teamId) returns no owner. Cal.com resolves a team-scoped third-party token to the team's owner user; if the team has no owner membership, auth cannot proceed.","triggerScenarios":"An integration submits a third-party token whose teamId references a team with no OWNER-role membership (team deleted, ownership transferred and dangling, or a fabricated token with an arbitrary teamId).","commonSituations":"Team was deleted leaving an orphan teamId in the token; an org-restructuring removed the last owner; tokens issued before ownership was assigned.","solutions":["Ensure the team referenced by the token has at least one user with the OWNER role membership.","If the team no longer exists, mint a new third-party token for an existing team or for a specific userId instead.","Prefer userId-scoped third-party tokens over teamId-scoped ones to avoid the owner-resolution step."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const team = await db.team.findUnique({ where: { id: teamId }, include: { members: { where: { role: 'OWNER' } } } });\nif (!team || team.members.length === 0) {\n  throw new Error(`Team ${teamId} has no owner; cannot resolve third-party token`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep at least one OWNER membership on every team that issues third-party tokens.","Prefer userId-scoped third-party tokens to avoid owner resolution.","On team deletion, invalidate tokens carrying its teamId."],"tags":["auth","third-party-token","team","platform"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}