diff --git a/AKModel/urls.py b/AKModel/urls.py
index 19e0083d3242a4bb6899579b9695cb1df3f1fed4..9b1f7591ee877043baca3cb63aac843dcb64ac4d 100644
--- a/AKModel/urls.py
+++ b/AKModel/urls.py
@@ -15,9 +15,10 @@ api_router.register('akslot', views.AKSlotViewSet, basename='AKSlot')
 
 extra_paths = []
 if apps.is_installed("AKScheduling"):
-    from AKScheduling.api import ResourcesViewSet, RoomAvailabilitiesView, EventsView
+    from AKScheduling.api import ResourcesViewSet, RoomAvailabilitiesView, EventsView, EventsViewSet
 
     api_router.register('scheduling-resources', ResourcesViewSet, basename='scheduling-resources')
+    api_router.register('scheduling-event', EventsViewSet, basename='scheduling-event')
 
     extra_paths = [
         path('api/scheduling-events/', EventsView.as_view(), name='scheduling-events'),
diff --git a/AKScheduling/api.py b/AKScheduling/api.py
index 110b50fa250c52cffb118f8085aed74b659393d9..e54e75fd0ea36eb07f2dbe581f2761d5dcae7278 100644
--- a/AKScheduling/api.py
+++ b/AKScheduling/api.py
@@ -1,6 +1,13 @@
-from rest_framework import viewsets, permissions, mixins, serializers
+from django.contrib.auth.mixins import LoginRequiredMixin
+from django.http import JsonResponse
+from django.shortcuts import get_object_or_404
+from django.urls import reverse
+from django.utils import timezone
+from django.views.generic import ListView
+from rest_framework import viewsets, mixins, serializers, permissions
 
-from AKModel.models import Room
+from AKModel.availability.models import Availability
+from AKModel.models import Room, AKSlot
 from AKModel.views import EventSlugMixin
 
 
@@ -23,3 +30,83 @@ class ResourcesViewSet(EventSlugMixin, mixins.RetrieveModelMixin, mixins.ListMod
 
     def get_queryset(self):
         return Room.objects.filter(event=self.event)
+
+
+class EventsView(LoginRequiredMixin, EventSlugMixin, ListView):
+    model = AKSlot
+
+    def get_queryset(self):
+        return super().get_queryset().filter(event=self.event, room__isnull=False)
+
+    def render_to_response(self, context, **response_kwargs):
+        return JsonResponse(
+            [{
+                "slotID": slot.pk,
+                "title": slot.ak.short_name,
+                "description": slot.ak.name,
+                "resourceId": slot.room.id,
+                "start": timezone.localtime(slot.start, self.event.timezone).strftime("%Y-%m-%d %H:%M:%S"),
+                "end": timezone.localtime(slot.end, self.event.timezone).strftime("%Y-%m-%d %H:%M:%S"),
+                "backgroundColor": slot.ak.category.color,
+                # TODO Mark conflicts here?
+                "borderColor": slot.ak.category.color,
+                "constraint": 'roomAvailable',
+                'url': str(reverse('submit:ak_detail', kwargs={"event_slug": self.event.slug, "pk": slot.ak.pk})),
+            } for slot in context["object_list"]],
+            safe=False,
+            **response_kwargs
+        )
+
+
+class RoomAvailabilitiesView(LoginRequiredMixin, EventSlugMixin, ListView):
+    model = Availability
+    context_object_name = "availabilities"
+
+    def get_queryset(self):
+        return super().get_queryset().filter(event=self.event, room__isnull=False)
+
+    def render_to_response(self, context, **response_kwargs):
+        return JsonResponse(
+            [{
+                "title": "",
+                "resourceId": a.room.id,
+                "start": timezone.localtime(a.start, self.event.timezone).strftime("%Y-%m-%d %H:%M:%S"),
+                "end": timezone.localtime(a.end, self.event.timezone).strftime("%Y-%m-%d %H:%M:%S"),
+                "backgroundColor": "#28B62C",
+                "display": 'background',
+                "groupId": 'roomAvailable',
+            } for a in context["availabilities"]],
+            safe=False,
+            **response_kwargs
+        )
+
+
+class EventSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = AKSlot
+        fields = ['id', 'start', 'end', 'roomId']
+
+    start = serializers.DateTimeField()
+    end = serializers.DateTimeField()
+    roomId = serializers.IntegerField(source='room.pk')
+
+    def update(self, instance, validated_data):
+        start = timezone.make_aware(timezone.make_naive(validated_data.get('start', instance.start)), instance.event.timezone)
+        end = timezone.make_aware(timezone.make_naive(validated_data.get('end', instance.end)), instance.event.timezone)
+        instance.start = start
+        instance.room = get_object_or_404(Room, pk=validated_data.get('room')["pk"])
+        diff = end - start
+        instance.duration = round(diff.days * 24 + (diff.seconds / 3600), 2)
+        instance.save()
+        return instance
+
+
+class EventsViewSet(EventSlugMixin, viewsets.ModelViewSet):
+    permission_classes = (permissions.DjangoModelPermissions,)
+    serializer_class = EventSerializer
+
+    def get_object(self):
+        return get_object_or_404(AKSlot, pk=self.kwargs["pk"])
+
+    def get_queryset(self):
+        return AKSlot.objects.filter(event=self.event)
diff --git a/AKScheduling/templates/admin/AKScheduling/scheduling.html b/AKScheduling/templates/admin/AKScheduling/scheduling.html
index e87aab7aa987a80d65fe0e36c2910cea61ba2ee2..a17cb88b4c9910a3753b384230d60112f28c730b 100644
--- a/AKScheduling/templates/admin/AKScheduling/scheduling.html
+++ b/AKScheduling/templates/admin/AKScheduling/scheduling.html
@@ -21,6 +21,45 @@
 
     <script>
         document.addEventListener('DOMContentLoaded', function () {
+            // CSRF Protection/Authentication
+            function getCookie(name) {
+                let cookieValue = null;
+                if (document.cookie && document.cookie !== '') {
+                    const cookies = document.cookie.split(';');
+                    for (let i = 0; i < cookies.length; i++) {
+                        const cookie = cookies[i].trim();
+                        // Does this cookie string begin with the name we want?
+                        if (cookie.substring(0, name.length + 1) === (name + '=')) {
+                            cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
+                            break;
+                        }
+                    }
+                }
+                return cookieValue;
+            }
+            const csrftoken = getCookie('csrftoken');
+
+            function csrfSafeMethod(method) {
+                // these HTTP methods do not require CSRF protection
+                return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
+            }
+            $.ajaxSetup({
+                beforeSend: function(xhr, settings) {
+                    if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
+                        xhr.setRequestHeader("X-CSRFToken", csrftoken);
+                    }
+                }
+            });
+
+
+            // Place slots by dropping placeholders on calendar
+            var containerEl = document.getElementById('unscheduled-slots');
+            new FullCalendar.Draggable(containerEl, {
+                itemSelector: '.unscheduled-slot',
+              });
+
+
+            // Calendar
             var planEl = document.getElementById('planCalendar');
 
             var plan = new FullCalendar.Calendar(planEl, {
@@ -67,6 +106,7 @@
                         slotDuration: '00:30',
                     }
                 },
+                // Show full AK title as tooltip for each AK (needs to be removed and newly placed when AK is moved)
                 eventDidMount : function(info) {
                     if(info.event.extendedProps.description !== undefined) {
                         $(info.el).tooltip({title: info.event.extendedProps.description, trigger: 'hover'});
@@ -76,6 +116,8 @@
                     $(info.el).tooltip('dispose');
                 },
 
+                // React to event changes (moving or change of duration)
+                eventChange: updateEvent,
                 editable: true,
                 allDaySlot: false,
                 nowIndicator: true,
@@ -94,6 +136,24 @@
 
             plan.setOption('contentHeight', $(window).height() - $('#header').height() * 11);
             plan.render();
+
+            function updateEvent(changeInfo) {
+                room = changeInfo.event.getResources()[0];
+                $.ajax({
+                    url: '{% url "model:scheduling-event-list" event_slug=event.slug %}' + changeInfo.event.extendedProps.slotID + "/",
+                    type: 'PUT',
+                    data: {
+                        start: plan.formatIso(changeInfo.event.start),
+                        end: plan.formatIso(changeInfo.event.end),
+                        roomId: room.id,
+                    },
+                    success: function(response) {},
+                    error: function(response) {
+                        changeInfo.revert();
+                        alert("ERROR. Did not update "+changeInfo.event.title)
+                    }
+                });
+            }
         });
     </script>
 {% endblock extrahead %}
diff --git a/templates/admin_base.html b/templates/admin_base.html
index cecf50f3694855670212591a35db6791be08c152..1022bb45582e4d5b8d201d7d0a60991ee6fb2887 100644
--- a/templates/admin_base.html
+++ b/templates/admin_base.html
@@ -5,7 +5,7 @@
 
 {% block extrahead %}
     {% bootstrap_css %}
-    {% bootstrap_javascript jquery='slim' %}
+    {% bootstrap_javascript jquery='full' %}
     {% fontawesome_5_static %}
 
     <style>