calcom/cal.diy · error · ForbiddenException

You do not have permission to reassign this booking

Error message

You do not have permission to reassign this booking

What it means

Thrown by BookingsService.reassignBooking when EventTypeAccessService.userIsEventTypeAdminOrOwner(reassignedByUser, booking.eventType) returns false. It is a NestJS ForbiddenException (HTTP 403) carrying the BOOKING_REASSIGN_PERMISSION_ERROR constant message. Reassignment is restricted to users who admin or own the event type that the booking belongs to.

Source

Thrown at apps/api/v2/src/platform/bookings/2024-08-13/services/bookings.service.ts:1011

  async reassignBooking(bookingUid: string, reassignedByUser: ApiAuthGuardUser) {
    const booking = await this.bookingsRepository.getByUidWithEventType(bookingUid);
    if (!booking) {
      throw new NotFoundException(`Booking with uid=${bookingUid} was not found in the database`);
    }

    if (!booking.eventType) {
      throw new BadRequestException(
        `Event type with id=${booking.eventTypeId} was not found in the database`
      );
    }

    const isAllowed = await this.eventTypeAccessService.userIsEventTypeAdminOrOwner(
      reassignedByUser,
      booking.eventType
    );

    if (!isAllowed) {
      throw new ForbiddenException(BOOKING_REASSIGN_PERMISSION_ERROR);
    }

    const platformClientParams = booking.eventTypeId
      ? await this.platformBookingsService.getOAuthClientParams(booking.eventTypeId)
      : undefined;

    const emailsEnabled = platformClientParams ? platformClientParams.arePlatformEmailsEnabled : true;

    const profile = this.usersService.getUserMainProfile(reassignedByUser);

    try {
      await roundRobinReassignment({
        bookingId: booking.id,
        orgId: profile?.organizationId || null,
        emailsEnabled,
        platformClientParams,
        reassignedById: reassignedByUser.id,
        actionSource: "API_V2",

View on GitHub (pinned to 176037d0af)

Solutions

  1. Authenticate the request with a user (or OAuth credential) that is the owner or an admin of the event type.
  2. Confirm team membership / org assignment of the authenticated user covers the event type.
  3. If using an OAuth client, ensure the authorized user is the event-type owner.
  4. Fall back to an account admin performing the reassign, then rotate access if misuse is suspected.

Example fix

// before
await apiClient.post(`/v2/bookings/${uid}/reassign`); // 403 for non-owners

// after
const me = await apiClient.get('/v2/me').then(r => r.data);
const eventType = await apiClient.get(`/v2/event-types/${booking.eventTypeId}`).then(r => r.data);
const isOwnerOrAdmin = eventType.ownerId === me.id || (eventType.team?.members ?? []).some(m => m.id === me.id && m.isAdmin);
if (!isOwnerOrAdmin) throw new Error('Authenticated user cannot reassign this booking');
await apiClient.post(`/v2/bookings/${uid}/reassign`);
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the caller is owner/admin of the event type before reassign
const me = await apiClient.get('/v2/me').then(r => r.data);
const et = await apiClient.get(`/v2/event-types/${booking.eventTypeId}`).then(r => r.data);
const canReassign = et.ownerId === me.id || (et.team?.members ?? []).some(m => m.id === me.id && (m.role === 'ADMIN' || m.role === 'OWNER'));
if (!canReassign) throw new Error('Caller is not event-type owner/admin');

Type guard

function isEventTypeManager(userId: number, et: { ownerId?: number | null; team?: { members?: Array<{ id: number; role?: string }> } | null }): boolean {
  return et.ownerId === userId || (et.team?.members ?? []).some(m => m.id === userId && (m.role === 'ADMIN' || m.role === 'OWNER'));
}

Try / catch

try {
  await apiClient.post(`/v2/bookings/${uid}/reassign`);
} catch (err) {
  if (err.response?.status === 403 && /permission to reassign/i.test(err.response?.data?.message ?? '')) {
    throw new InsufficientPrivilegesError('Use an event-type owner/admin credential');
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling the reassign endpoint authenticated as a user who is neither the owner nor an admin/member of the team that owns the event type; using an OAuth client whose linked user lacks event-type ownership; cross-org access where the caller belongs to a different organization.

Common situations: Service-account / OAuth tokens scoped to a non-owner user; team memberships not yet propagated; attempting reassign as a plain member of a team event type that requires admin rights; wrong api key reused across environments.

Related errors


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