{"record":{"id":"6ce9861d68f8fbdf","repo":"instructure/canvas-lms","slug":"not-an-appointment","errorCode":null,"errorMessage":"not an appointment","messagePattern":"not an appointment","errorType":"exception","errorClass":"CalendarEvent::ReservationError","httpStatus":null,"severity":"error","filePath":"app/models/calendar_event.rb","lineNumber":592,"sourceCode":"    context_type == \"AppointmentGroup\" || parent_event.try(:context_type) == \"AppointmentGroup\"\n  end\n\n  def appointment_group\n    if parent_event.try(:context).is_a?(AppointmentGroup)\n      parent_event.context\n    elsif context_type == \"AppointmentGroup\"\n      context\n    end\n  end\n\n  def account\n    (context_type == \"Account\") ? context : nil\n  end\n\n  class ReservationError < StandardError; end\n\n  def reserve_for(participant, user, options = {})\n    raise ReservationError, \"not an appointment\" unless context_type == \"AppointmentGroup\"\n    raise ReservationError, \"ineligible participant\" unless context.eligible_participant?(participant)\n\n    transaction do\n      lock! # in case two people two participants try to grab the same slot\n      participant.lock! # in case two people try to make a reservation for the same participant\n\n      if options[:cancel_existing]\n        context.reservations_for(participant).lock.each do |reservation|\n          raise ReservationError, \"cannot cancel past reservation\" if reservation.end_at < Time.now.utc\n\n          reservation.updating_user = user\n          reservation.destroy\n        end\n      end\n\n      raise ReservationError, \"participant has met per-participant limit\" if context.max_appointments_per_participant && context.reservations_for(participant).size >= context.max_appointments_per_participant\n      raise ReservationError, \"all slots filled\" if participants_per_appointment && child_events.size >= participants_per_appointment\n      raise ReservationError, \"participant has already reserved this appointment\" if child_events_for(participant).present?","sourceCodeStart":574,"sourceCodeEnd":610,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/calendar_event.rb#L574-L610","documentation":"Raised by CalendarEvent#reserve_for when someone tries to make a reservation on a calendar event whose context_type is not 'AppointmentGroup'. Only appointment-group time slots can be reserved; regular course/user/account calendar events cannot. It is a Canvas ReservationError, typically surfaced through the appointments API.","triggerScenarios":"An API client or controller calls event.reserve_for(participant, user) on a CalendarEvent loaded by ID where context_type is 'Course', 'User', or 'Account' instead of 'AppointmentGroup' — e.g. the client passed a normal calendar event's ID to the appointment reservation endpoint.","commonSituations":"Frontend cached an event ID from an appointment group that was later converted/deleted and replaced by a regular event; API consumer confuses calendar event IDs with appointment group slot IDs; importing/copying courses turns appointment slots into plain events.","solutions":["Verify the event is a time slot of an AppointmentGroup before reserving: check event.context_type == 'AppointmentGroup' (or fetch the slot via the appointment group's time slots API).","Use the appointment group's slot IDs (appointment_group.time_slots / appointment groups API) rather than arbitrary calendar event IDs.","Refresh the client's data if the appointment group was recently deleted or modified so stale IDs aren't reused.","Catch ReservationError in the calling code and return a 400-style response to the user."],"exampleFix":"# before\nslot = CalendarEvent.find(params[:event_id])\nslot.reserve_for(participant, user)\n# after\nslot = CalendarEvent.find(params[:event_id])\nraise ReservationError, \"not an appointment\" unless slot.context_type == \"AppointmentGroup\"\nslot.reserve_for(participant, user)","handlingStrategy":"validation","validationCode":"slot = CalendarEvent.find(event_id)\nraise ReservationError, \"not an appointment\" unless slot.context_type == \"AppointmentGroup\"","typeGuard":null,"tryCatchPattern":"begin\n  slot.reserve_for(participant, user)\nrescue CalendarEvent::ReservationError => e\n  render json: { error: e.message }, status: :bad_request\nend","preventionTips":["Reserve only via appointment group slot IDs from the appointments API","Check context_type == 'AppointmentGroup' before calling reserve_for","Refresh client state after appointment groups are edited/deleted"],"tags":["calendar","appointments","api","invalid-state"],"backgroundTag":"invalid-state-transition","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}