calcom/cal.diy · error · BadRequestException

Invalid time range given - check the 'start' and 'end' query

Error message

Invalid time range given - check the 'start' and 'end' query parameters.

What it means

A NestJS BadRequestException (HTTP 400) from SlotsService_2024_09_04.fetchAndFormatSlots. It wraps any error from AvailableSlotsService whose message includes 'Invalid time range given' into a stable client-facing message pointing at the start/end query params. The underlying cause is that the availability engine rejected the window — typically start is on or after end, or the range exceeds the allowed lookup span.

Source

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

    try {
      const availableSlots: TimeSlots = await this.availableSlotsService.getAvailableSlots({
        input: queryTransformed,
        ctx: {},
      });

      const formatted = await this.slotsOutputService.getAvailableSlots(
        availableSlots,
        queryTransformed.eventTypeId,
        queryTransformed.duration,
        format,
        queryTransformed.timeZone
      );

      return formatted;
    } catch (error) {
      if (error instanceof Error) {
        if (error.message.includes("Invalid time range given")) {
          throw new BadRequestException(
            "Invalid time range given - check the 'start' and 'end' query parameters."
          );
        }
      }
      throw error;
    }
  }

  async getAvailableSlots(query: GetSlotsInput_2024_09_04) {
    const queryTransformed = await this.slotsInputService.transformGetSlotsQuery(query);
    return this.fetchAndFormatSlots(queryTransformed, query.format);
  }

  async getAvailableSlotsWithRouting(query: GetSlotsInputWithRouting_2024_09_04) {
    const queryTransformed = await this.slotsInputService.transformRoutingGetSlotsQuery(query);
    return this.fetchAndFormatSlots(queryTransformed, query.format);
  }

View on GitHub (pinned to 176037d0af)

Solutions

  1. Ensure end is strictly greater than start before the request.
  2. Cap the window to the platform's allowed lookup span (default is typically 30 days; trim end accordingly).
  3. When offering a date range UI, enforce start <= end and a max span client-side.
  4. If you need a longer view, page through multiple requests with offsets.

Example fix

// before
const start = DateTime.now().toISO();
const end = DateTime.now().toISO(); // same → invalid

// after
const start = DateTime.now().toISO()!;
const end = DateTime.now().plus({ days: 30 }).toISO()!;
fetch(`/v2/slots?start=${encodeURIComponent(start)}&end=${encodeURIComponent(end)}`);
Defensive patterns

Strategy: validation

Validate before calling

import { DateTime } from 'luxon';

const MAX_LOOKUP_DAYS = 30;
function assertValidRange(startISO: unknown, endISO: unknown): { start: string; end: string } {
  const start = DateTime.fromISO(String(startISO), { zone: 'utc' });
  const end = DateTime.fromISO(String(endISO), { zone: 'utc' });
  if (!start.isValid || !end.isValid) throw new RangeError('start/end must be valid ISO');
  if (end.toMillis() <= start.toMillis()) throw new RangeError('end must be strictly after start');
  if (end.diff(start, 'days').days > MAX_LOOKUP_DAYS) throw new RangeError(`window must be <= ${MAX_LOOKUP_DAYS} days`);
  return { start: start.toISO()!, end: end.toISO()! };
}

Type guard

function isValidRange(startISO: unknown, endISO: unknown): startISO is string {
  if (typeof startISO !== 'string' || typeof endISO !== 'string') return false;
  const s = DateTime.fromISO(startISO, { zone: 'utc' });
  const e = DateTime.fromISO(endISO, { zone: 'utc' });
  return s.isValid && e.isValid && e.toMillis() > s.toMillis();
}

Try / catch

try {
  await cal.slots.list({ start, end, ... });
} catch (e) {
  if (e instanceof HttpError && e.statusCode === 400 && /Invalid time range/i.test(e.message)) {
    // correct the window and retry once
    const { start: s, end: en } = clampWindow(start, end);
    return cal.slots.list({ start: s, end: en, ... });
  }
  throw e;
}

Prevention

When it happens

Trigger: GET /v2/slots where start >= end; start and end are identical; the window (end - start) exceeds the configured maximum slot lookup range (often 30/45/180 days); start/end are valid ISO but semantically inverted.

Common situations: Defaulting end to the same value as start; flipping date pickers; requesting a full year of slots; timezone conversion on the client pushing start past end; reusing a cached end that is now in the past.

Related errors


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