calcom/cal.diy · error · ForbiddenException

User with ID=${userId} does not own event type with ID=${eve

Error message

User with ID=${userId} does not own event type with ID=${eventType.id}

What it means

Thrown by EventTypesService_2024_04_15.checkUserOwnsEventType as ForbiddenException (HTTP 403) when the authenticated userId does not equal eventType.userId. The event type exists and was fetched, but the caller is not its owner. This is the ownership gate shared by update and delete flows in the 2024_04_15 service.

Source

Thrown at apps/api/v2/src/platform/event-types/event-types_2024_04_15/services/event-types.service.ts:201

      userLevelSelectedCalendars: selectedCalendars,
      allSelectedCalendars: [...eventTypeSelectedCalendars, ...selectedCalendars],
    };
  }

  async deleteEventType(eventTypeId: number, userId: number) {
    const existingEventType = await this.eventTypesRepository.getEventTypeById(eventTypeId);
    if (!existingEventType) {
      throw new NotFoundException(`Event type with ID=${eventTypeId} does not exist.`);
    }

    this.checkUserOwnsEventType(userId, existingEventType);

    return this.eventTypesRepository.deleteEventType(eventTypeId);
  }

  checkUserOwnsEventType(userId: number, eventType: Pick<EventType, "id" | "userId">) {
    if (userId !== eventType.userId) {
      throw new ForbiddenException(`User with ID=${userId} does not own event type with ID=${eventType.id}`);
    }
  }
}

View on GitHub (pinned to 176037d0af)

Solutions

  1. Use the event type owner's own access token/API key for PATCH and DELETE.
  2. For team/org-level management, use the 2024_06_14 endpoints which broaden authorization, or impersonate the owner via an authorized flow.
  3. Confirm userId in the token matches eventType.userId before attempting the write.
  4. If delegation is required, implement a server-side owner-impersonation endpoint rather than reusing a teammate's token.

Example fix

// before - org admin token used to delete a teammate's event type
await api.delete(`/v2/event-types/${teammateEventTypeId}`);
// after - obtain the owner's token or use a delegated admin endpoint
await apiAsOwner.delete(`/v2/event-types/${teammateEventTypeId}`);
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the token user is the owner before PATCH/DELETE on 2024_04_15 endpoints
const me = await api.get('/v2/me');
const et = await api.get(`/v2/event-types/${id}`);
if (et.userId !== me.id) {
  throw new Error(`token user ${me.id} is not the owner of event type ${id}`);
}

Type guard

function isOwnerOf(tokenUserId: number, eventType: unknown): boolean {
  return typeof eventType === 'object' && eventType !== null && (eventType as any).userId === tokenUserId;
}

Try / catch

try {
  await api.delete(`/v2/event-types/${id}`);
} catch (e) {
  if (e.response?.status === 403) {
    // not the owner — switch to the owner's token or use a delegated admin endpoint
  } else throw e;
}

Prevention

When it happens

Trigger: PATCH/DELETE /v2/event-types/:eventTypeId where the event type exists but belongs to another user; an org admin (who can read via getUserEventTypeForAtom) attempting to write — the 04_15 endpoints restrict writes to the owner only; userId mismatch due to a token scoped to a different user.

Common situations: An org admin reading a teammate's event type successfully (GET passes) but then attempting PATCH/DELETE (which require ownership); using a service-account token to modify a user-owned resource; assuming team membership implies write access.

Related errors


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