{"record":{"id":"df593d969afe98f5","repo":"calcom/cal.diy","slug":"can-t-cancel-booking-with-uid-bookinguid-becaus","errorCode":null,"errorMessage":"Can't cancel booking with uid=${bookingUid} because it has been cancelled already. Please provide uid of a booking that is not cancelled.","messagePattern":"Can't cancel booking with uid=(.+?) because it has been cancelled already\\. Please provide uid of a booking that is not cancelled\\.","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"apps/api/v2/src/platform/bookings/2024-04-15/controllers/bookings.controller.ts","lineNumber":255,"sourceCode":"    @Param(\"bookingUid\") bookingUid: string,\n    @Body() _body: CancelBookingInput_2024_04_15,\n    @Headers(X_CAL_CLIENT_ID) clientId?: string,\n    @Headers(X_CAL_PLATFORM_EMBED) isEmbed?: string\n  ): Promise<ApiResponse<{ bookingId: number; bookingUid: string; onlyRemovedAttendee: boolean }>> {\n    const oAuthClientId = clientId?.toString();\n    const isUidNumber = !Number.isNaN(Number(bookingUid));\n\n    if (isUidNumber) {\n      throw new BadRequestException(\"Please provide booking uid instead of booking id.\");\n    }\n\n    if (bookingUid) {\n      const { bookingInfo } = await getBookingInfo(bookingUid);\n      if (!bookingInfo) {\n        throw new NotFoundException(`Booking with UID=${bookingUid} does not exist.`);\n      }\n      if (bookingInfo.status === \"CANCELLED\") {\n        throw new BadRequestException(\n          `Can't cancel booking with uid=${bookingUid} because it has been cancelled already. Please provide uid of a booking that is not cancelled.`\n        );\n      }\n      try {\n        req.body.uid = bookingUid;\n        const bookingRequest = await this.createNextApiBookingRequest(req, oAuthClientId, undefined, isEmbed);\n        const res = await handleCancelBooking({\n          bookingData: bookingRequest.body,\n          userId: bookingRequest.userId,\n          userUuid: bookingRequest.userUuid,\n          arePlatformEmailsEnabled: bookingRequest.arePlatformEmailsEnabled,\n          platformClientId: bookingRequest.platformClientId,\n          platformCancelUrl: bookingRequest.platformCancelUrl,\n          platformRescheduleUrl: bookingRequest.platformRescheduleUrl,\n          platformBookingUrl: bookingRequest.platformBookingUrl,\n          actionSource: \"API_V2\",\n        });\n        return {","sourceCodeStart":237,"sourceCodeEnd":273,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/apps/api/v2/src/platform/bookings/2024-04-15/controllers/bookings.controller.ts#L237-L273","documentation":"cancelBooking checks bookingInfo.status === 'CANCELLED' and throws BadRequestException (HTTP 400). Cancelling an already-cancelled booking is not permitted; the caller must supply the uid of an active booking.","triggerScenarios":"POST /:bookingUid/cancel for a booking whose status is already 'CANCELLED'.","commonSituations":"Double-submit; retry after a successful cancel; UI not refreshing booking state; concurrent cancel requests.","solutions":["Fetch the booking status first and skip cancel if already CANCELLED.","Make cancel idempotent client-side by treating 'already cancelled' (HTTP 400 with this message) as success.","Disable the cancel action in the UI once status is CANCELLED."],"exampleFix":"// before\nawait api.cancelBooking(uid);\n// after\nconst b = await api.getBooking(uid);\nif (b.status === 'CANCELLED') return; // idempotent\nawait api.cancelBooking(uid);","handlingStrategy":"validation","validationCode":"async function cancelIfActive(api, uid) {\n  const b = await api.getBooking(uid);\n  if (b.status === 'CANCELLED') return; // idempotent\n  return api.cancelBooking(uid);\n}","typeGuard":"const isAlreadyCancelled = (b: { status: string }): boolean =>\n  b.status === 'CANCELLED';","tryCatchPattern":"try { await api.cancelBooking(uid); }\ncatch (e) {\n  if (e.status === 400 && /has been cancelled already/.test(e.message)) return; // idempotent\n  throw e;\n}","preventionTips":["Check booking status before cancelling and skip when CANCELLED.","Treat the 'already cancelled' message as success in retry logic.","Disable the cancel action once status flips to CANCELLED."],"tags":["bookings","bad-request","state","cancel","idempotency"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}