{"record":{"id":"bf94a2e89b60960c","repo":"calcom/cal.diy","slug":"user-with-id-userid-does-not-own-schedule-with","errorCode":null,"errorMessage":"User with ID=${userId} does not own schedule with ID=${scheduleId}","messagePattern":"User with ID=(.+?) does not own schedule with ID=(.+?)","errorType":"exception","errorClass":"NotFoundException","httpStatus":404,"severity":"error","filePath":"apps/api/v2/src/platform/event-types/event-types_2024_06_14/services/event-types.service.ts","lineNumber":376,"sourceCode":"\n    return this.eventTypesRepository.deleteEventType(eventTypeId);\n  }\n\n  checkUserOwnsEventType(userId: number, eventType: Pick<EventType, \"id\" | \"userId\">) {\n    if (userId !== eventType.userId) {\n      throw new ForbiddenException(`User with ID=${userId} does not own event type with ID=${eventType.id}`);\n    }\n  }\n\n  async checkUserOwnsSchedule(userId: number, scheduleId: number | null | undefined) {\n    if (!scheduleId) {\n      return;\n    }\n\n    const schedule = await this.schedulesRepository.getScheduleByIdAndUserId(scheduleId, userId);\n\n    if (!schedule) {\n      throw new NotFoundException(`User with ID=${userId} does not own schedule with ID=${scheduleId}`);\n    }\n  }\n}\n","sourceCodeStart":358,"sourceCodeEnd":380,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/apps/api/v2/src/platform/event-types/event-types_2024_06_14/services/event-types.service.ts#L358-L380","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait api.patchEventType({ id, scheduleId: otherUserScheduleId });\n// after\nconst mine = await api.listMySchedules();\nconst scheduleId = mine.find(s => s.id === requestedId)?.id ?? null;\nawait api.patchEventType({ id, scheduleId });","handlingStrategy":"validation","validationCode":"const mine = await api.listMySchedules();\nconst scheduleId = mine.some(s => s.id === requestedId) ? requestedId : null;","typeGuard":"null","tryCatchPattern":"try { await api.patchEventType({ id, scheduleId }); }\ncatch (e) {\n  if (e.status === 404) throw new AccessError('schedule not found or not owned');\n  throw e;\n}","preventionTips":["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."],"tags":["not-found","ownership","schedule","authorization","inconsistent-status"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}