calcom/cal.diy · error · NotFoundException

User with ID=${userId} does not own schedule with ID=${sched

Error message

User with ID=${userId} does not own schedule with ID=${scheduleId}

What it means

checkUserOwnsSchedule calls getScheduleByIdAndUserId(scheduleId, userId) and throws NotFoundException when null. Note two quirks: it throws NotFound (not Forbidden) for a schedule that exists but is not owned by the user, which is inconsistent with 424's Forbidden for event types; and it skips validation entirely when scheduleId is falsy (null/undefined/0), so a 0 scheduleId bypasses the check.

Source

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

    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}`);
    }
  }

  async checkUserOwnsSchedule(userId: number, scheduleId: number | null | undefined) {
    if (!scheduleId) {
      return;
    }

    const schedule = await this.schedulesRepository.getScheduleByIdAndUserId(scheduleId, userId);

    if (!schedule) {
      throw new NotFoundException(`User with ID=${userId} does not own schedule with ID=${scheduleId}`);
    }
  }
}

View on GitHub (pinned to 176037d0af)

Solutions

  1. Use a scheduleId obtained from the caller's own schedules list (GET /schedules filtered to the user).
  2. Pass null/undefined to clear the schedule rather than guessing an id.
  3. If the caller should own it, reconcile ownership in the data first.

Example fix

// before
await api.patchEventType({ id, scheduleId: otherUserScheduleId });
// after
const mine = await api.listMySchedules();
const scheduleId = mine.find(s => s.id === requestedId)?.id ?? null;
await api.patchEventType({ id, scheduleId });
Defensive patterns

Strategy: validation

Validate before calling

const mine = await api.listMySchedules();
const scheduleId = mine.some(s => s.id === requestedId) ? requestedId : null;

Type guard

null

Try / catch

try { await api.patchEventType({ id, scheduleId }); }
catch (e) {
  if (e.status === 404) throw new AccessError('schedule not found or not owned');
  throw e;
}

Prevention

When it happens

Trigger: PATCH an event type and assign a scheduleId belonging to another user; pass a deleted schedule's id; pass a team schedule the caller does not own.

Common situations: Copying a schedule id from another workspace; team schedules that the caller can see but does not own; a schedule deleted between GET and PATCH.

Related errors


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