calcom/cal.diy · error · UnprocessableEntityException
Can't reserve a slot if the event is already booked.
Error message
Can't reserve a slot if the event is already booked.
What it means
A NestJS UnprocessableEntityException (HTTP 422) from SlotsService_2024_09_04.reserveSlot. For a non-seated, non-round-robin event type, an active overlapping booking already exists. Since non-RR events host a single booking per slot, a second reservation is refused. Round-robin events skip this branch.
Source
Thrown at apps/api/v2/src/modules/slots/slots-2024-09-04/services/slots.service.ts:155
);
if (eventType.seatsPerTimeSlot) {
const attendeesCount = booking?.attendees?.length;
if (attendeesCount) {
const seatsLeft = eventType.seatsPerTimeSlot - attendeesCount;
if (seatsLeft < 1) {
throw new UnprocessableEntityException(
`Booking with id=${input.eventTypeId} at ${input.slotStart} has no more seats left.`
);
}
}
}
const nonSeatedEventAlreadyBooked = !eventType.seatsPerTimeSlot && booking;
const isRoundRobinEvent = eventType.schedulingType === SchedulingType.ROUND_ROBIN;
if (nonSeatedEventAlreadyBooked && !isRoundRobinEvent) {
throw new UnprocessableEntityException(`Can't reserve a slot if the event is already booked.`);
}
if (isRoundRobinEvent) {
try {
await validateRoundRobinSlotAvailability(input.eventTypeId, startDate, endDate, eventType.hosts);
} catch (error) {
if (error instanceof Error) {
throw new UnprocessableEntityException(error?.message);
}
throw error;
}
} else {
await this.checkSlotOverlap(input.eventTypeId, startDate.toISO(), endDate.toISO());
}
const reservationDuration = input.reservationDuration ?? DEFAULT_RESERVATION_DURATION;
if (eventType.userId) {View on GitHub (pinned to 176037d0af)
Solutions
- Re-fetch available slots and pick a free time before reserving.
- Treat 422 as a stale-availability signal and refresh the picker.
- For high-concurrency scenarios, consider a seated or round-robin event type.
- Implement optimistic retry: on 422, re-query once and offer the next slot.
Example fix
// before — retry the same slot after a failure
fetch('/v2/slots/reserve', { body: JSON.stringify({ eventTypeId, slotStart }) });
// after — refresh and pick a free slot on 422
try { await reserve(eventTypeId, slotStart); }
catch (e) {
if (e.status === 422) {
const free = await nextFreeSlot(eventTypeId);
await reserve(eventTypeId, free.start);
} else throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
async function ensureSlotFree(eventTypeId: number, slotStart: string) {
const slots = await cal.slots.list({ eventTypeId, start: slotStart, end: oneDayLater(slotStart) });
const hit = Object.values(slots).flat().find(s => s.start === slotStart && !s.away);
if (!hit) throw new UserFacingError('That time was just booked — choose another.');
return hit;
}
await ensureSlotFree(eventTypeId, slotStart); Type guard
function isFreeSlot(slot: unknown): slot is { start: string; away?: boolean } {
return typeof slot === 'object' && slot !== null
&& typeof (slot as any).start === 'string'
&& !(slot as any).away;
} Try / catch
try {
await cal.slots.reserve({ eventTypeId, slotStart });
} catch (e) {
if (e instanceof HttpError && e.statusCode === 422 && /already booked/i.test(e.message)) {
const next = await nextFreeSlot(eventTypeId);
return cal.slots.reserve({ eventTypeId, slotStart: next.start });
}
throw e;
} Prevention
- Re-fetch availability immediately before reserve in concurrent flows.
- Treat 422 'already booked' as stale-availability and refresh the picker.
- Consider seated or round-robin event types for high concurrency.
- Do not retry the identical slot — it will keep failing.
When it happens
Trigger: POST /v2/slots/reserve for a standard (non-seated, non-round-robin) event type where the host already has an active booking overlapping the requested start.
Common situations: A booking was created between the time the slot list was fetched and the reserve call; the client retries a reserve after the booking completed; concurrent reservation attempts for the same slot.
Related errors
- This time slot is already reserved by another user. Please c
- Booking with id=${input.eventTypeId} at ${input.slotStart} h
- reservationDuration can only be used for authenticated reque
- Event Type with ID=${input.eventTypeId} not found
- authenticated user is not owner of event type, does not have
AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12).
Data as JSON: /api/errors/d725a786e35405c3.
Report an issue: GitHub.