calcom/cal.diy · error · ForbiddenException
User with ID=${userId} does not own schedule with ID=${sched
Error message
User with ID=${userId} does not own schedule with ID=${schedule.id} What it means
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.
Source
Thrown at apps/api/v2/src/platform/schedules/schedules_2024_04_15/services/schedules.service.ts:146
prisma: this.dbWrite.prisma as unknown as PrismaClient,
});
}
async deleteUserSchedule(userId: number, scheduleId: number) {
const existingSchedule = await this.schedulesRepository.getScheduleById(scheduleId);
if (!existingSchedule) {
throw new BadRequestException(`Schedule with ID=${scheduleId} does not exist.`);
}
this.checkUserOwnsSchedule(userId, existingSchedule);
return this.schedulesRepository.deleteScheduleById(scheduleId);
}
checkUserOwnsSchedule(userId: number, schedule: Pick<Schedule, "id" | "userId">) {
if (userId !== schedule.userId) {
throw new ForbiddenException(`User with ID=${userId} does not own schedule with ID=${schedule.id}`);
}
}
getDefaultAvailabilityInput(): CreateAvailabilityInput_2024_04_15 {
const startTime = new Date(new Date().setUTCHours(9, 0, 0, 0));
const endTime = new Date(new Date().setUTCHours(17, 0, 0, 0));
return {
days: [1, 2, 3, 4, 5],
startTime,
endTime,
};
}
}
View on GitHub (pinned to 176037d0af)
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.
Defensive patterns
Strategy: validation
Validate before calling
// Before calling any schedule mutation endpoint, verify the user owns the schedule
async function verifyOwnership(api: ApiClient, scheduleId: number): Promise<void> {
const { data } = await api.get(`/v2/schedules`);
const owned = data.data.schedules.some((s: { id: number }) => s.id === scheduleId);
if (!owned) {
throw new Error(`User does not own schedule ${scheduleId}`);
}
} Try / catch
try {
await api.patch(`/v2/schedules/${scheduleId}`, payload);
} catch (error) {
if (error.response?.status === 403) {
// User doesn't own this schedule — use a different API key or schedule ID
console.error('Access denied: schedule belongs to another user.');
}
throw error;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- User with ID=${userId} does not own schedule with ID=${sched
- User with ID=${userId} does not own event type with ID=${eve
- Access denied. Either the team with ID=${teamId} does not ow
- Event type with id ${eventTypeId} not found
- User with ID ${userId} is not part of this OAuth client.
AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12).
Data as JSON: /api/errors/c52ecc1d35c52bc5.
Report an issue: GitHub.