{"record":{"id":"d725a786e35405c3","repo":"calcom/cal.diy","slug":"can-t-reserve-a-slot-if-the-event-is-already-booke","errorCode":null,"errorMessage":"Can't reserve a slot if the event is already booked.","messagePattern":"Can't reserve a slot if the event is already booked\\.","errorType":"http","errorClass":"UnprocessableEntityException","httpStatus":422,"severity":"error","filePath":"apps/api/v2/src/modules/slots/slots-2024-09-04/services/slots.service.ts","lineNumber":155,"sourceCode":"    );\n\n    if (eventType.seatsPerTimeSlot) {\n      const attendeesCount = booking?.attendees?.length;\n      if (attendeesCount) {\n        const seatsLeft = eventType.seatsPerTimeSlot - attendeesCount;\n        if (seatsLeft < 1) {\n          throw new UnprocessableEntityException(\n            `Booking with id=${input.eventTypeId} at ${input.slotStart} has no more seats left.`\n          );\n        }\n      }\n    }\n\n    const nonSeatedEventAlreadyBooked = !eventType.seatsPerTimeSlot && booking;\n    const isRoundRobinEvent = eventType.schedulingType === SchedulingType.ROUND_ROBIN;\n\n    if (nonSeatedEventAlreadyBooked && !isRoundRobinEvent) {\n      throw new UnprocessableEntityException(`Can't reserve a slot if the event is already booked.`);\n    }\n\n    if (isRoundRobinEvent) {\n      try {\n        await validateRoundRobinSlotAvailability(input.eventTypeId, startDate, endDate, eventType.hosts);\n      } catch (error) {\n        if (error instanceof Error) {\n          throw new UnprocessableEntityException(error?.message);\n        }\n        throw error;\n      }\n    } else {\n      await this.checkSlotOverlap(input.eventTypeId, startDate.toISO(), endDate.toISO());\n    }\n\n    const reservationDuration = input.reservationDuration ?? DEFAULT_RESERVATION_DURATION;\n\n    if (eventType.userId) {","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/apps/api/v2/src/modules/slots/slots-2024-09-04/services/slots.service.ts#L137-L173","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before — retry the same slot after a failure\nfetch('/v2/slots/reserve', { body: JSON.stringify({ eventTypeId, slotStart }) });\n\n// after — refresh and pick a free slot on 422\ntry { await reserve(eventTypeId, slotStart); }\ncatch (e) {\n  if (e.status === 422) {\n    const free = await nextFreeSlot(eventTypeId);\n    await reserve(eventTypeId, free.start);\n  } else throw e;\n}","handlingStrategy":"try-catch","validationCode":"async function ensureSlotFree(eventTypeId: number, slotStart: string) {\n  const slots = await cal.slots.list({ eventTypeId, start: slotStart, end: oneDayLater(slotStart) });\n  const hit = Object.values(slots).flat().find(s => s.start === slotStart && !s.away);\n  if (!hit) throw new UserFacingError('That time was just booked — choose another.');\n  return hit;\n}\nawait ensureSlotFree(eventTypeId, slotStart);","typeGuard":"function isFreeSlot(slot: unknown): slot is { start: string; away?: boolean } {\n  return typeof slot === 'object' && slot !== null\n    && typeof (slot as any).start === 'string'\n    && !(slot as any).away;\n}","tryCatchPattern":"try {\n  await cal.slots.reserve({ eventTypeId, slotStart });\n} catch (e) {\n  if (e instanceof HttpError && e.statusCode === 422 && /already booked/i.test(e.message)) {\n    const next = await nextFreeSlot(eventTypeId);\n    return cal.slots.reserve({ eventTypeId, slotStart: next.start });\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["calcom-api","slots","unprocessable","conflict","booking","reservation"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}