calcom/cal.diy · error · UnauthorizedException

ApiAuthStrategy - third-party token - No owner found for the

Error message

ApiAuthStrategy - third-party token - No owner found for the associated team.

What it means

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.

Source

Thrown at apps/api/v2/src/modules/auth/strategies/api-auth/api-auth.strategy.ts:340

    request: ApiAuthGuardRequest
  ): Promise<{ success: true; data: UserWithProfile } | { success: false }> {
    const decodedToken = this.tokensService.getDecodedThirdPartyAccessToken(token);
    if (!decodedToken) {
      return { success: false };
    }

    let user: UserWithProfile | null = null;
    let organizationId: number | null = null;

    if (decodedToken.userId) {
      user = await this.userRepository.findByIdWithProfile(decodedToken.userId);
      if (user) {
        organizationId = this.usersService.getUserMainOrgId(user) as number;
      }
    } else if (decodedToken.teamId) {
      const teamOwner = await this.userRepository.findOwnerByTeamIdWithProfile(decodedToken.teamId);
      if (!teamOwner) {
        throw new UnauthorizedException(
          "ApiAuthStrategy - third-party token - No owner found for the associated team."
        );
      }
      user = teamOwner;
      organizationId =
        teamOwner.profiles?.find((p) => p.organizationId === decodedToken.teamId)?.organizationId ?? null;
    }

    if (!user) {
      throw new UnauthorizedException(
        "ApiAuthStrategy - third-party token - No user or team owner associated with the token."
      );
    }

    request.organizationId = organizationId;
    return { success: true, data: user };
  }
}

View on GitHub (pinned to 176037d0af)

Solutions

  1. Ensure the team referenced by the token has at least one user with the OWNER role membership.
  2. If the team no longer exists, mint a new third-party token for an existing team or for a specific userId instead.
  3. Prefer userId-scoped third-party tokens over teamId-scoped ones to avoid the owner-resolution step.
Defensive patterns

Strategy: validation

Validate before calling

const team = await db.team.findUnique({ where: { id: teamId }, include: { members: { where: { role: 'OWNER' } } } });
if (!team || team.members.length === 0) {
  throw new Error(`Team ${teamId} has no owner; cannot resolve third-party token`);
}

Prevention

When it happens

Trigger: 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).

Common situations: Team was deleted leaving an orphan teamId in the token; an org-restructuring removed the last owner; tokens issued before ownership was assigned.

Related errors


AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12). Data as JSON: /api/errors/8aff6aede9b8bbd9. Report an issue: GitHub.