calcom/cal.diy · error · BadRequestException

Invalid end date

Error message

Invalid end date

What it means

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.

Source

Thrown at apps/api/v2/src/modules/slots/slots-2024-09-04/services/slots.service.ts:130

      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."
        );
      }
    }

    const startDate = DateTime.fromISO(input.slotStart, { zone: "utc" });
    if (!startDate.isValid) {
      throw new BadRequestException("Invalid start date");
    }

    if (input.slotDuration) {
      this.validateSlotDuration(eventType, input.slotDuration);
    }

    const endDate = startDate.plus({ minutes: input.slotDuration ?? eventType.length });
    if (!endDate.isValid) {
      throw new BadRequestException("Invalid end date");
    }

    const booking = await this.slotsRepository.findActiveOverlappingBooking(
      input.eventTypeId,
      startDate.toJSDate(),
      endDate.toJSDate()
    );

    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.`
          );
        }
      }

View on GitHub (pinned to 176037d0af)

Solutions

  1. Validate slotDuration is a finite positive integer within a sane range (e.g. 1–480 minutes) before sending.
  2. For fixed-length event types, omit slotDuration and let eventType.length drive the end.
  3. Confirm the event type has a valid length before relying on the default.
  4. Reject NaN/Infinity/negative values client-side.

Example fix

// before
fetch('/v2/slots/reserve', { body: JSON.stringify({ eventTypeId, slotStart, slotDuration: NaN }) });

// after
const slotDuration = Number(raw);
if (!Number.isFinite(slotDuration) || slotDuration <= 0) throw new ClientError('bad duration');
fetch('/v2/slots/reserve', { body: JSON.stringify({ eventTypeId, slotStart, slotDuration }) });
Defensive patterns

Strategy: validation

Validate before calling

function toValidDuration(slotDuration: unknown): number | undefined {
  if (slotDuration == null) return undefined;
  const d = Number(slotDuration);
  if (!Number.isInteger(d) || d <= 0 || d > 1440) {
    throw new RangeError('slotDuration must be a positive integer of minutes (<= 1440)');
  }
  return d;
}
const slotDuration = toValidDuration(input.slotDuration);

Type guard

function isPositiveMinutes(v: unknown): v is number {
  return typeof v === 'number' && Number.isInteger(v) && v > 0;
}

Try / catch

try {
  await cal.slots.reserve({ eventTypeId, slotStart, slotDuration });
} catch (e) {
  if (e instanceof HttpError && e.statusCode === 400 && /end date/i.test(e.message)) {
    // retry without slotDuration, letting the event type length apply
    return cal.slots.reserve({ eventTypeId, slotStart });
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12). Data as JSON: /api/errors/8f3a7483ed42e9bd. Report an issue: GitHub.