{"record":{"id":"9008626c91980058","repo":"calcom/cal.diy","slug":"attempting-to-book-a-meeting-in-the-past","errorCode":null,"errorMessage":"Attempting to book a meeting in the past.","messagePattern":"Attempting to book a meeting in the past\\.","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"apps/api/v2/src/platform/bookings/2024-08-13/services/errors.service.ts","lineNumber":48,"sourceCode":"    }\n    throw new NotFoundException(`Event type with id ${body.eventTypeId} not found.`);\n  }\n\n  handleBookingError(error: unknown, bookingTeamEventType: boolean): never {\n    const hostsUnavaile = \"One of the hosts either already has booking at this time or is not available\";\n\n    if (error instanceof Error) {\n      if (error.message === \"no_available_users_found_error\") {\n        if (bookingTeamEventType) {\n          throw new BadRequestException(hostsUnavaile);\n        }\n        throw new BadRequestException(\"User either already has booking at this time or is not available\");\n      } else if (error.message === \"booking_time_out_of_bounds_error\") {\n        throw new BadRequestException(\n          `The event type can't be booked at the \"start\" time provided. This could be because it's too soon (violating the minimum booking notice) or too far in the future (outside the event's scheduling window). Try fetching available slots first using the GET /v2/slots endpoint and then make a booking with \"start\" time equal to one of the available slots.`\n        );\n      } else if (error.message === \"Attempting to book a meeting in the past.\") {\n        throw new BadRequestException(\"Attempting to book a meeting in the past.\");\n      } else if (error.message === \"hosts_unavailable_for_booking\") {\n        throw new BadRequestException(hostsUnavaile);\n      } else if (error.message === \"booker_limit_exceeded_error\") {\n        throw new BadRequestException(\n          \"Attendee with this email can't book because the maximum number of active bookings has been reached.\"\n        );\n      } else if (error.message === \"booker_limit_exceeded_error_reschedule\") {\n        const errorData =\n          \"data\" in error ? (error.data as { rescheduleUid: string }) : { rescheduleUid: undefined };\n        let message =\n          \"Attendee with this email can't book because the maximum number of active bookings has been reached.\";\n        if (errorData?.rescheduleUid) {\n          message += ` You can reschedule your existing booking (${errorData.rescheduleUid}) to a new timeslot instead.`;\n        }\n        throw new BadRequestException(message);\n      }\n    }\n    throw error;","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/apps/api/v2/src/platform/bookings/2024-08-13/services/errors.service.ts#L30-L66","documentation":"Thrown by handleBookingError when the booking engine's error.message is exactly 'Attempting to book a meeting in the past.' BadRequestException (HTTP 400). The requested start time is before now (clock skew, stale slot, or a client bug sending an unadjusted past timestamp).","triggerScenarios":"Posting a start time that has already elapsed — a slot fetched long ago, a clock skewed client, a retry of a request queued overnight, or a hardcoded timestamp. The engine rejects any start < now.","commonSituations":"Long latency between GET /v2/slots and POST /v2/bookings letting the slot slip into the past; client clock behind server time; retry of a queued request the next day; daylight-saving transition producing a past-equivalent timestamp.","solutions":["Refresh the slot list immediately before booking if more than a few minutes have passed.","Synchronize the client clock (NTP) and validate start > now() before sending.","Discard and re-fetch on retry rather than reusing the original body.","Account for DST transitions when computing start times."],"exampleFix":"// before\nstart = cachedSlot.start  // fetched hours ago, now in the past\n\n// after\nif (dayjs(start).isBefore(dayjs())) {\n  const slots = await api.get(`/v2/slots?...`);\n  start = slots.data.slots[0].start;\n}","handlingStrategy":"validation","validationCode":"const startMs = new Date(body.start).getTime();\nif (!Number.isFinite(startMs) || startMs <= Date.now()) throw new Error('start must be in the future');","typeGuard":"const isFutureStart = (start: string): boolean =>\n  Number.isFinite(new Date(start).getTime()) && new Date(start).getTime() > Date.now();","tryCatchPattern":"try { await api.post('/v2/bookings', body); }\ncatch (e) {\n  if (e.response?.status === 400 && /in the past/.test(e.response.data.message)) {\n    /* refresh slot list, pick a future start, retry */\n  } else throw e;\n}","preventionTips":["Synchronize client clock via NTP.","Refresh GET /v2/slots immediately before booking if any latency occurred.","Discard and re-fetch on retry rather than reusing a stale body."],"tags":["api","bookings","time","validation","nestjs"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}