{"record":{"id":"59cc616afc006b8e","repo":"calcom/cal.diy","slug":"this-time-slot-is-already-reserved-by-another-user","errorCode":null,"errorMessage":"This time slot is already reserved by another user. Please choose a different time.","messagePattern":"This time slot is already reserved by another user\\. Please choose a different time\\.","errorType":"http","errorClass":"UnprocessableEntityException","httpStatus":422,"severity":"error","filePath":"apps/api/v2/src/modules/slots/slots-2024-09-04/services/slots.service.ts","lineNumber":210,"sourceCode":"      eventType.id,\n      startDate.toISO(),\n      endDate.toISO(),\n      eventType.seatsPerTimeSlot !== null,\n      reservationDuration\n    );\n\n    return this.slotsOutputService.getReservationSlotCreated(slot, reservationDuration);\n  }\n\n  private async checkSlotOverlap(eventTypeId: number, startDate: string, endDate: string) {\n    const overlappingReservation = await this.slotsRepository.getOverlappingSlotReservation(\n      eventTypeId,\n      startDate,\n      endDate\n    );\n\n    if (overlappingReservation) {\n      throw new UnprocessableEntityException(\n        `This time slot is already reserved by another user. Please choose a different time.`\n      );\n    }\n  }\n\n  validateSlotDuration(eventType: EventType, inputSlotDuration: number) {\n    const eventTypeMetadata = eventTypeMetadataSchema.parse(eventType.metadata);\n    if (!eventTypeMetadata?.multipleDuration) {\n      throw new BadRequestException(\n        \"You passed 'slotDuration' but this event type is not a variable length event type.\"\n      );\n    }\n\n    if (!eventTypeMetadata.multipleDuration.includes(inputSlotDuration)) {\n      throw new BadRequestException(\n        `Provided 'slotDuration' is not one of the possible lengths for the event type. The possible lengths for this variable length event type are: ${eventTypeMetadata.multipleDuration.join(\n          \", \"\n        )}`","sourceCodeStart":192,"sourceCodeEnd":228,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/apps/api/v2/src/modules/slots/slots-2024-09-04/services/slots.service.ts#L192-L228","documentation":"A NestJS UnprocessableEntityException (HTTP 422) from SlotsService_2024_09_04.checkSlotOverlap. For non-round-robin events, the slotsRepository.getOverlappingSlotReservation returned an existing reservation for the same window. Another user is already holding the slot within the reservation window; only one active reservation is allowed.","triggerScenarios":"POST /v2/slots/reserve where another reservation (a SelectedSlots row still within its releaseAt) overlaps the requested start/end. Common right after a popular slot opens or when two clients reserve concurrently.","commonSituations":"End user clicks the same time as another user; previous reservation not yet expired (default 5 min); retrying reserve after a network blip while the first reservation is still live; reservationDuration too long, blocking others.","solutions":["Offer the user the next available slot from a freshly-fetched availability list.","Wait for the existing reservation to expire (releaseAt) and retry if appropriate.","Keep reservationDuration short to reduce overlap windows.","Surface a 'slot just taken / held' message and refresh."],"exampleFix":"// before\nawait reserve(eventTypeId, slotStart);\n\n// after — refresh on 422 overlap and pick a different slot\ntry { await reserve(eventTypeId, slotStart); }\ncatch (e) {\n  if (e.status === 422 && /already reserved/i.test(e.message)) {\n    const next = await nextFreeSlot(eventTypeId);\n    await reserve(eventTypeId, next.start);\n  } else throw e;\n}","handlingStrategy":"try-catch","validationCode":"// You cannot fully prevent a race; minimize the window by keeping reservationDuration short\n// and re-checking availability right before reserve.\nasync function ensureSlotNotReserved(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('This time is being held by someone else.');\n}\nawait ensureSlotNotReserved(eventTypeId, slotStart);","typeGuard":"function isReservableSlot(slot: unknown): slot is { start: string } {\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 reserved/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":["Keep reservationDuration short to shrink overlap windows.","Re-fetch availability immediately before reserve in popular slots.","On 422 overlap, pick a different slot rather than retrying.","Expire stale client-side holds before retrying."],"tags":["calcom-api","slots","unprocessable","conflict","reservation","concurrency"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}