{"record":{"id":"e8e1cb841dc6d971","repo":"calcom/cal.diy","slug":"reservationduration-can-only-be-used-for-authentic","errorCode":null,"errorMessage":"reservationDuration can only be used for authenticated requests - use access token, api key or OAuth credentials","messagePattern":"reservationDuration can only be used for authenticated requests - use access token, api key or OAuth credentials","errorType":"http","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"apps/api/v2/src/modules/slots/slots-2024-09-04/services/slots.service.ts","lineNumber":97,"sourceCode":"        }\n      }\n      throw error;\n    }\n  }\n\n  async getAvailableSlots(query: GetSlotsInput_2024_09_04) {\n    const queryTransformed = await this.slotsInputService.transformGetSlotsQuery(query);\n    return this.fetchAndFormatSlots(queryTransformed, query.format);\n  }\n\n  async getAvailableSlotsWithRouting(query: GetSlotsInputWithRouting_2024_09_04) {\n    const queryTransformed = await this.slotsInputService.transformRoutingGetSlotsQuery(query);\n    return this.fetchAndFormatSlots(queryTransformed, query.format);\n  }\n\n  async reserveSlot(input: ReserveSlotInput_2024_09_04, authUserId?: number) {\n    if (input.reservationDuration && !authUserId) {\n      throw new UnauthorizedException(\n        \"reservationDuration can only be used for authenticated requests - use access token, api key or OAuth credentials\"\n      );\n    }\n\n    const eventType = await this.eventTypeRepository.getEventTypeWithHosts(input.eventTypeId);\n    if (!eventType) {\n      throw new NotFoundException(`Event Type with ID=${input.eventTypeId} not found`);\n    }\n\n    if (input.reservationDuration && authUserId) {\n      const canSpecifyCustomReservationDuration = await this.canSpecifyCustomReservationDuration(\n        authUserId,\n        eventType\n      );\n      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        );","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/apps/api/v2/src/modules/slots/slots-2024-09-04/services/slots.service.ts#L79-L115","documentation":"A NestJS UnauthorizedException (HTTP 401) from SlotsService_2024_09_04.reserveSlot. The request included a `reservationDuration` (custom hold time) but no authenticated user was resolved (authUserId is undefined). Custom reservation duration is a privileged feature that requires an API key, OAuth access token, or OAuth client credentials.","triggerScenarios":"Calling POST /v2/slots/2024-09-04/reserve (or the booking-flow reserve) with a reservationDuration field while unauthenticated — i.e. no cal-api-key header, no Authorization: Bearer, and no OAuth client-credentials grant.","commonSituations":"Embed booking widget calling reserve without passing the API key; using the public endpoint by mistake; OAuth token expired and was silently dropped; reservationDuration field left over from a copy-paste in an anonymous flow.","solutions":["Send a cal-api-key header, an Authorization: Bearer <accessToken>, or OAuth client credentials with the request.","If the caller is anonymous, remove the reservationDuration field and accept the default 5-minute hold.","Refresh expired OAuth tokens before retrying.","Verify the API key is valid and belongs to the event type owner's app."],"exampleFix":"// before\nfetch('/v2/slots/reserve', { method:'POST', body: JSON.stringify({ eventTypeId, slotStart, reservationDuration: 10 }) });\n\n// after — authenticate, or drop reservationDuration\nfetch('/v2/slots/reserve', {\n  method: 'POST',\n  headers: { 'cal-api-key': apiKey, 'content-type': 'application/json' },\n  body: JSON.stringify({ eventTypeId, slotStart, reservationDuration: 10 }),\n});","handlingStrategy":"validation","validationCode":"function assertAuthForReservationDuration(input: { reservationDuration?: number }, auth: { apiKey?: string; accessToken?: string }) {\n  if (input.reservationDuration != null && !auth.apiKey && !auth.accessToken) {\n    throw new Error('reservationDuration requires an API key or OAuth access token');\n  }\n}\nassertAuthForReservationDuration(input, { apiKey, accessToken });","typeGuard":"function isAuthorized(auth: { apiKey?: string; accessToken?: string }): boolean {\n  return typeof auth.apiKey === 'string' && auth.apiKey.length > 0\n      || typeof auth.accessToken === 'string' && auth.accessToken.length > 0;\n}","tryCatchPattern":"try {\n  await cal.slots.reserve({ eventTypeId, slotStart, reservationDuration });\n} catch (e) {\n  if (e instanceof HttpError && e.statusCode === 401) {\n    // either authenticate or drop reservationDuration and use default hold\n    return cal.slots.reserve({ eventTypeId, slotStart });\n  }\n  throw e;\n}","preventionTips":["Only set reservationDuration when you will also send credentials.","Send cal-api-key or Authorization: Bearer on every privileged reserve call.","Refresh OAuth tokens proactively before they expire.","Strip reservationDuration from anonymous/public booking flows."],"tags":["calcom-api","slots","unauthorized","auth","reservation"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}