{"record":{"id":"8f3a7483ed42e9bd","repo":"calcom/cal.diy","slug":"invalid-end-date-8f3a74","errorCode":null,"errorMessage":"Invalid end date","messagePattern":"Invalid end date","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"apps/api/v2/src/modules/slots/slots-2024-09-04/services/slots.service.ts","lineNumber":130,"sourceCode":"      if (!canSpecifyCustomReservationDuration) {\n        throw new ForbiddenException(\n          \"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.\"\n        );\n      }\n    }\n\n    const startDate = DateTime.fromISO(input.slotStart, { zone: \"utc\" });\n    if (!startDate.isValid) {\n      throw new BadRequestException(\"Invalid start date\");\n    }\n\n    if (input.slotDuration) {\n      this.validateSlotDuration(eventType, input.slotDuration);\n    }\n\n    const endDate = startDate.plus({ minutes: input.slotDuration ?? eventType.length });\n    if (!endDate.isValid) {\n      throw new BadRequestException(\"Invalid end date\");\n    }\n\n    const booking = await this.slotsRepository.findActiveOverlappingBooking(\n      input.eventTypeId,\n      startDate.toJSDate(),\n      endDate.toJSDate()\n    );\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      }","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/apps/api/v2/src/modules/slots/slots-2024-09-04/services/slots.service.ts#L112-L148","documentation":"A NestJS BadRequestException (HTTP 400) from SlotsService_2024_09_04.reserveSlot. The computed endDate (startDate.plus({minutes: slotDuration ?? eventType.length})) is invalid. Because startDate was already validated, this fires when slotDuration or eventType.length is undefined/NaN/negative or so extreme that adding it overflows luxon's range.","triggerScenarios":"POST /v2/slots/reserve with slotDuration as a non-integer or extremely large value, or with a non-variable event type whose length is null/undefined so plus(undefined) yields an invalid DateTime.","commonSituations":"Passing slotDuration for a fixed-length event type where length metadata is missing; sending duration as a string '30' that bypasses @IsInt; negative durations; copying slotDuration from user input without coercion.","solutions":["Validate slotDuration is a finite positive integer within a sane range (e.g. 1–480 minutes) before sending.","For fixed-length event types, omit slotDuration and let eventType.length drive the end.","Confirm the event type has a valid length before relying on the default.","Reject NaN/Infinity/negative values client-side."],"exampleFix":"// before\nfetch('/v2/slots/reserve', { body: JSON.stringify({ eventTypeId, slotStart, slotDuration: NaN }) });\n\n// after\nconst slotDuration = Number(raw);\nif (!Number.isFinite(slotDuration) || slotDuration <= 0) throw new ClientError('bad duration');\nfetch('/v2/slots/reserve', { body: JSON.stringify({ eventTypeId, slotStart, slotDuration }) });","handlingStrategy":"validation","validationCode":"function toValidDuration(slotDuration: unknown): number | undefined {\n  if (slotDuration == null) return undefined;\n  const d = Number(slotDuration);\n  if (!Number.isInteger(d) || d <= 0 || d > 1440) {\n    throw new RangeError('slotDuration must be a positive integer of minutes (<= 1440)');\n  }\n  return d;\n}\nconst slotDuration = toValidDuration(input.slotDuration);","typeGuard":"function isPositiveMinutes(v: unknown): v is number {\n  return typeof v === 'number' && Number.isInteger(v) && v > 0;\n}","tryCatchPattern":"try {\n  await cal.slots.reserve({ eventTypeId, slotStart, slotDuration });\n} catch (e) {\n  if (e instanceof HttpError && e.statusCode === 400 && /end date/i.test(e.message)) {\n    // retry without slotDuration, letting the event type length apply\n    return cal.slots.reserve({ eventTypeId, slotStart });\n  }\n  throw e;\n}","preventionTips":["Validate slotDuration is a positive integer within a sane range.","Omit slotDuration for fixed-length event types.","Confirm eventType.length is set before relying on the default.","Never pass NaN/Infinity/negative values."],"tags":["calcom-api","slots","bad-request","luxon","datetime","duration","reservation"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}