calcom/cal.diy · error · UnauthorizedException
reservationDuration can only be used for authenticated reque
Error message
reservationDuration can only be used for authenticated requests - use access token, api key or OAuth credentials
What it means
A NestJS UnauthorizedException (HTTP 401) from SlotsService_2024_09_04.reserveSlot. The request included a `reservationDuration` (custom hold time) but no authenticated user was resolved (authUserId is undefined). Custom reservation duration is a privileged feature that requires an API key, OAuth access token, or OAuth client credentials.
Source
Thrown at apps/api/v2/src/modules/slots/slots-2024-09-04/services/slots.service.ts:97
}
}
throw error;
}
}
async getAvailableSlots(query: GetSlotsInput_2024_09_04) {
const queryTransformed = await this.slotsInputService.transformGetSlotsQuery(query);
return this.fetchAndFormatSlots(queryTransformed, query.format);
}
async getAvailableSlotsWithRouting(query: GetSlotsInputWithRouting_2024_09_04) {
const queryTransformed = await this.slotsInputService.transformRoutingGetSlotsQuery(query);
return this.fetchAndFormatSlots(queryTransformed, query.format);
}
async reserveSlot(input: ReserveSlotInput_2024_09_04, authUserId?: number) {
if (input.reservationDuration && !authUserId) {
throw new UnauthorizedException(
"reservationDuration can only be used for authenticated requests - use access token, api key or OAuth credentials"
);
}
const eventType = await this.eventTypeRepository.getEventTypeWithHosts(input.eventTypeId);
if (!eventType) {
throw new NotFoundException(`Event Type with ID=${input.eventTypeId} not found`);
}
if (input.reservationDuration && authUserId) {
const canSpecifyCustomReservationDuration = await this.canSpecifyCustomReservationDuration(
authUserId,
eventType
);
if (!canSpecifyCustomReservationDuration) {
throw new ForbiddenException(
"authenticated user is not owner of event type, does not have memberships in common with owner of the event type, nor does belong to event type's team or org."
);View on GitHub (pinned to 176037d0af)
Solutions
- Send a cal-api-key header, an Authorization: Bearer <accessToken>, or OAuth client credentials with the request.
- If the caller is anonymous, remove the reservationDuration field and accept the default 5-minute hold.
- Refresh expired OAuth tokens before retrying.
- Verify the API key is valid and belongs to the event type owner's app.
Example fix
// before
fetch('/v2/slots/reserve', { method:'POST', body: JSON.stringify({ eventTypeId, slotStart, reservationDuration: 10 }) });
// after — authenticate, or drop reservationDuration
fetch('/v2/slots/reserve', {
method: 'POST',
headers: { 'cal-api-key': apiKey, 'content-type': 'application/json' },
body: JSON.stringify({ eventTypeId, slotStart, reservationDuration: 10 }),
}); Defensive patterns
Strategy: validation
Validate before calling
function assertAuthForReservationDuration(input: { reservationDuration?: number }, auth: { apiKey?: string; accessToken?: string }) {
if (input.reservationDuration != null && !auth.apiKey && !auth.accessToken) {
throw new Error('reservationDuration requires an API key or OAuth access token');
}
}
assertAuthForReservationDuration(input, { apiKey, accessToken }); Type guard
function isAuthorized(auth: { apiKey?: string; accessToken?: string }): boolean {
return typeof auth.apiKey === 'string' && auth.apiKey.length > 0
|| typeof auth.accessToken === 'string' && auth.accessToken.length > 0;
} Try / catch
try {
await cal.slots.reserve({ eventTypeId, slotStart, reservationDuration });
} catch (e) {
if (e instanceof HttpError && e.statusCode === 401) {
// either authenticate or drop reservationDuration and use default hold
return cal.slots.reserve({ eventTypeId, slotStart });
}
throw e;
} Prevention
- Only set reservationDuration when you will also send credentials.
- Send cal-api-key or Authorization: Bearer on every privileged reserve call.
- Refresh OAuth tokens proactively before they expire.
- Strip reservationDuration from anonymous/public booking flows.
When it happens
Trigger: Calling POST /v2/slots/2024-09-04/reserve (or the booking-flow reserve) with a reservationDuration field while unauthenticated — i.e. no cal-api-key header, no Authorization: Bearer, and no OAuth client-credentials grant.
Common situations: Embed booking widget calling reserve without passing the API key; using the public endpoint by mistake; OAuth token expired and was silently dropped; reservationDuration field left over from a copy-paste in an anonymous flow.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Event Type with ID=${input.eventTypeId} not found
- authenticated user is not owner of event type, does not have
- Invalid start date
- Invalid end date
- Booking with id=${input.eventTypeId} at ${input.slotStart} h
AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12).
Data as JSON: /api/errors/e8e1cb841dc6d971.
Report an issue: GitHub.