calcom/cal.diy · error · ForbiddenException
User with ID=${userId} does not own event type with ID=${eve
Error message
User with ID=${userId} does not own event type with ID=${eventType.id} What it means
Thrown by EventTypesService_2024_04_15.checkUserOwnsEventType as ForbiddenException (HTTP 403) when the authenticated userId does not equal eventType.userId. The event type exists and was fetched, but the caller is not its owner. This is the ownership gate shared by update and delete flows in the 2024_04_15 service.
Source
Thrown at apps/api/v2/src/platform/event-types/event-types_2024_04_15/services/event-types.service.ts:201
userLevelSelectedCalendars: selectedCalendars,
allSelectedCalendars: [...eventTypeSelectedCalendars, ...selectedCalendars],
};
}
async deleteEventType(eventTypeId: number, userId: number) {
const existingEventType = await this.eventTypesRepository.getEventTypeById(eventTypeId);
if (!existingEventType) {
throw new NotFoundException(`Event type with ID=${eventTypeId} does not exist.`);
}
this.checkUserOwnsEventType(userId, existingEventType);
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}`);
}
}
}
View on GitHub (pinned to 176037d0af)
Solutions
- Use the event type owner's own access token/API key for PATCH and DELETE.
- For team/org-level management, use the 2024_06_14 endpoints which broaden authorization, or impersonate the owner via an authorized flow.
- Confirm userId in the token matches eventType.userId before attempting the write.
- If delegation is required, implement a server-side owner-impersonation endpoint rather than reusing a teammate's token.
Example fix
// before - org admin token used to delete a teammate's event type
await api.delete(`/v2/event-types/${teammateEventTypeId}`);
// after - obtain the owner's token or use a delegated admin endpoint
await apiAsOwner.delete(`/v2/event-types/${teammateEventTypeId}`); Defensive patterns
Strategy: validation
Validate before calling
// Confirm the token user is the owner before PATCH/DELETE on 2024_04_15 endpoints
const me = await api.get('/v2/me');
const et = await api.get(`/v2/event-types/${id}`);
if (et.userId !== me.id) {
throw new Error(`token user ${me.id} is not the owner of event type ${id}`);
} Type guard
function isOwnerOf(tokenUserId: number, eventType: unknown): boolean {
return typeof eventType === 'object' && eventType !== null && (eventType as any).userId === tokenUserId;
} Try / catch
try {
await api.delete(`/v2/event-types/${id}`);
} catch (e) {
if (e.response?.status === 403) {
// not the owner — switch to the owner's token or use a delegated admin endpoint
} else throw e;
} Prevention
- Use the 2024_06_14 endpoints for broader org/team admin access.
- Store the owner userId alongside each eventTypeId in your client state.
- Never assume team membership implies write access on the 04_15 endpoints.
When it happens
Trigger: PATCH/DELETE /v2/event-types/:eventTypeId where the event type exists but belongs to another user; an org admin (who can read via getUserEventTypeForAtom) attempting to write — the 04_15 endpoints restrict writes to the owner only; userId mismatch due to a token scoped to a different user.
Common situations: An org admin reading a teammate's event type successfully (GET passes) but then attempting PATCH/DELETE (which require ownership); using a service-account token to modify a user-owned resource; assuming team membership implies write access.
Related errors
- Access denied. Either the team with ID=${teamId} does not ow
- Event type with id ${eventTypeId} not found
- User with ID=${userId} does not own schedule with ID=${sched
- User with ID=${userId} does not own schedule with ID=${sched
- Event type with id ${eventTypeId} not found
AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12).
Data as JSON: /api/errors/40f7972f8e00fb70.
Report an issue: GitHub.