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
- Use a scheduleId obtained from the caller's own schedules list (GET /schedules filtered to the user).
- Pass null/undefined to clear the schedule rather than guessing an id.
- 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
- Source schedule ids from the caller's own schedules list.
- Pass null to clear a schedule instead of guessing.
- Remember a falsy scheduleId bypasses the check entirely.
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
- Event type with id ${eventTypeId} not found
- User with ID ${userId} is not part of this OAuth client.
- Event type with id ${eventTypeId} not found
- Event type with id ${eventTypeId} not found
- IsUserWebhookGuard - user with id=(${user.id}) is not the ow
AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12).
Data as JSON: /api/errors/bf94a2e89b60960c.
Report an issue: GitHub.