{"record":{"id":"c52ecc1d35c52bc5","repo":"calcom/cal.diy","slug":"user-with-id-userid-does-not-own-schedule-with-c52ecc","errorCode":null,"errorMessage":"User with ID=${userId} does not own schedule with ID=${schedule.id}","messagePattern":"User with ID=(.+?) does not own schedule with ID=(.+?)","errorType":"http","errorClass":"ForbiddenException","httpStatus":403,"severity":"error","filePath":"apps/api/v2/src/platform/schedules/schedules_2024_04_15/services/schedules.service.ts","lineNumber":146,"sourceCode":"      prisma: this.dbWrite.prisma as unknown as PrismaClient,\n    });\n  }\n\n  async deleteUserSchedule(userId: number, scheduleId: number) {\n    const existingSchedule = await this.schedulesRepository.getScheduleById(scheduleId);\n\n    if (!existingSchedule) {\n      throw new BadRequestException(`Schedule with ID=${scheduleId} does not exist.`);\n    }\n\n    this.checkUserOwnsSchedule(userId, existingSchedule);\n\n    return this.schedulesRepository.deleteScheduleById(scheduleId);\n  }\n\n  checkUserOwnsSchedule(userId: number, schedule: Pick<Schedule, \"id\" | \"userId\">) {\n    if (userId !== schedule.userId) {\n      throw new ForbiddenException(`User with ID=${userId} does not own schedule with ID=${schedule.id}`);\n    }\n  }\n\n  getDefaultAvailabilityInput(): CreateAvailabilityInput_2024_04_15 {\n    const startTime = new Date(new Date().setUTCHours(9, 0, 0, 0));\n    const endTime = new Date(new Date().setUTCHours(17, 0, 0, 0));\n\n    return {\n      days: [1, 2, 3, 4, 5],\n      startTime,\n      endTime,\n    };\n  }\n}\n","sourceCodeStart":128,"sourceCodeEnd":161,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/apps/api/v2/src/platform/schedules/schedules_2024_04_15/services/schedules.service.ts#L128-L161","documentation":"Thrown by checkUserOwnsSchedule in the 2024-04-15 SchedulesService (also called from the 2024-06-11 service) when the authenticated user's ID does not match the schedule's userId field. This is an authorization guard that prevents users from accessing, updating, or deleting schedules owned by other users. Results in HTTP 403 Forbidden.","triggerScenarios":"Calling any schedule endpoint (GET/PATCH/DELETE) with a scheduleId that belongs to a different user; using a team member's API key to access another member's personal schedule; schedule ownership was transferred but the old owner's integration still references it.","commonSituations":"Integration hardcodes a schedule ID from one user but runs with another user's API key; multi-tenant confusion where IDs are shared across accounts; attempting to access a managed/team schedule through the personal schedule API.","solutions":["List the authenticated user's own schedules via GET /v2/schedules and only use IDs from that response.","Verify the API key belongs to the same user who owns the target schedule.","For team schedules, use the appropriate team scheduling endpoints rather than personal schedule endpoints."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Before calling any schedule mutation endpoint, verify the user owns the schedule\nasync function verifyOwnership(api: ApiClient, scheduleId: number): Promise<void> {\n  const { data } = await api.get(`/v2/schedules`);\n  const owned = data.data.schedules.some((s: { id: number }) => s.id === scheduleId);\n  if (!owned) {\n    throw new Error(`User does not own schedule ${scheduleId}`);\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await api.patch(`/v2/schedules/${scheduleId}`, payload);\n} catch (error) {\n  if (error.response?.status === 403) {\n    // User doesn't own this schedule — use a different API key or schedule ID\n    console.error('Access denied: schedule belongs to another user.');\n  }\n  throw error;\n}","preventionTips":["Only use schedule IDs returned by GET /v2/schedules for the authenticated user.","Never share schedule IDs across different users' API keys.","For team scheduling, use team-specific endpoints instead of personal schedule endpoints.","Treat 403 as a security boundary — never attempt to bypass it."],"tags":["schedules","authorization","forbidden","ownership","nestjs","security"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}