diff --git a/AKModel/forms.py b/AKModel/forms.py
index 4d1fe7ef7bfa45d41b377fa4c9815e951e35cf19..f74dc39e5a73ed7d8bb248e3071300c093ebff74 100644
--- a/AKModel/forms.py
+++ b/AKModel/forms.py
@@ -272,3 +272,12 @@ class RoomFormWithAvailabilities(AvailabilitiesFormMixin, RoomForm):
         # Filter possible values for m2m when event is specified
         if hasattr(self.instance, "event") and self.instance.event is not None:
             self.fields["properties"].queryset = AKRequirement.objects.filter(event=self.instance.event)
+
+
+class JSONImportForm(AdminIntermediateForm):
+    json_data = forms.CharField(
+        required=True,
+        widget=forms.Textarea,
+        label=_("JSON data"),
+        help_text=_("JSON data from the scheduling solver"),
+    )
diff --git a/AKModel/models.py b/AKModel/models.py
index 22586a57380b4ba368c440726ecccc3f6d73b1d0..c4c6f804508afa56b05501a0cf1c746a82182788 100644
--- a/AKModel/models.py
+++ b/AKModel/models.py
@@ -1,4 +1,5 @@
 import itertools
+import json
 from datetime import timedelta
 
 from django.db import models
@@ -162,6 +163,66 @@ class Event(models.Model):
                 .filter(availabilities__count=0, owners__count__gt=0)
                 )
 
+    def time_slots(self, *, slots_in_an_hour=1.0):
+        from AKModel.availability.models import Availability
+
+        rooms = Room.objects.filter(event=self)
+        slot_duration = timedelta(hours=(1.0 / slots_in_an_hour))
+        slot_index = 0
+        current_slot = self.start
+        current_block = []
+        previous_slot = None
+
+        room_availabilities = list({availability
+                               for room in rooms
+                               for availability in room.availabilities.all()})
+
+        while current_slot < self.end:
+            slot = Availability(event=self,
+                                start=current_slot,
+                                end=current_slot + slot_duration)
+
+            if any((availability.contains(slot)
+                    for availability in room_availabilities)):
+                if previous_slot is not None and previous_slot + slot_duration < current_slot:
+                    yield current_block
+                    current_block = []
+
+                current_block.append(slot_index)
+                previous_slot = current_slot
+
+            slot_index += 1
+            current_slot += slot_duration
+
+        yield current_block
+
+    def time_slot(self, *, time_slot_index, slots_in_an_hour=1.0):
+        from AKModel.availability.models import Availability
+        slot_duration = timedelta(hours=(1.0 / slots_in_an_hour))
+
+        start = self.start + time_slot_index * slot_duration
+
+        return Availability(event=self,
+                            start=start,
+                            end=start + slot_duration)
+
+    def schedule_from_json(self, schedule):
+        schedule = json.loads(schedule)
+
+        slots_in_an_hour = schedule["input"]["timeslots"]["info"]["duration"]
+
+        for scheduled_slot in schedule["scheduled_aks"]:
+            slot = AKSlot.objects.get(scheduled_slot["ak_id"])
+            slot.room = scheduled_slot["room_id"]
+
+            start = min(scheduled_slot["time_slot_ids"])
+            end = max(scheduled_slot["time_slot_ids"])
+
+            slot.start = self.time_slot(time_slot_index=start,
+                                        slots_in_an_hour=slots_in_an_hour)
+            slot.end = self.time_slot(time_slot_index=end + 1,
+                                      slots_in_an_hour=slots_in_an_hour)
+
 
 class AKOwner(models.Model):
     """ An AKOwner describes the person organizing/holding an AK.
@@ -236,6 +297,22 @@ class AKOwner(models.Model):
         """
         return AKOwner.objects.get(event=event, slug=slug)
 
+    def as_json(self) -> str:
+        data = {
+            "id": self.pk,
+            "info": {
+                "name": self.name,
+            },
+            "capacity": self.capacity,
+            "fulfilled_room_constraints": [constraint.name
+                                           for constraint in self.properties.all()],
+            "time_constraints": [f"availability-room-{self.pk}"]
+        }
+
+        return json.dumps(data)
+
+
+
 
 class AKCategory(models.Model):
     """ An AKCategory describes the characteristics of an AK, e.g. content vs. recreational.
diff --git a/AKModel/templates/admin/AKModel/ak_json_export.html b/AKModel/templates/admin/AKModel/ak_json_export.html
new file mode 100644
index 0000000000000000000000000000000000000000..da5825044bf0300c61de979c929c4694b56327af
--- /dev/null
+++ b/AKModel/templates/admin/AKModel/ak_json_export.html
@@ -0,0 +1,19 @@
+{% extends "admin/base_site.html" %}
+
+{% load tz %}
+
+{% block content %}
+<pre>
+  {"aks": [
+      {% for slot in slots %}{{ slot.as_json }}{% if not forloop.last %},
+      {% endif %}{% endfor %}
+    ],
+  "rooms": [
+      {% for room in rooms %}{{ room.as_json }}{% if not forloop.last %},
+      {% endif %}{% endfor %}
+    ],
+  "participants": {{ participants }},
+  "timeslots": {{ timeslots }}
+  }
+</pre>
+{% endblock %}
diff --git a/AKModel/urls.py b/AKModel/urls.py
index 3abf646058afdf71c7938032236942e7bb0ed995..9871b4119949d31350ecc64db568d515b61eb3cb 100644
--- a/AKModel/urls.py
+++ b/AKModel/urls.py
@@ -5,8 +5,9 @@ from rest_framework.routers import DefaultRouter
 
 import AKModel.views.api
 from AKModel.views.manage import ExportSlidesView, PlanPublishView, PlanUnpublishView, DefaultSlotEditorView, \
-    AKsByUserView
-from AKModel.views.ak import AKRequirementOverview, AKCSVExportView, AKWikiExportView, AKMessageDeleteView
+    AKsByUserView, AKJSONImportView
+from AKModel.views.ak import AKRequirementOverview, AKCSVExportView, AKJSONExportView, AKWikiExportView, \
+     AKMessageDeleteView
 from AKModel.views.event_wizard import NewEventWizardStartView, NewEventWizardPrepareImportView, \
     NewEventWizardImportView, NewEventWizardActivateView, NewEventWizardFinishView, NewEventWizardSettingsView
 from AKModel.views.room import RoomBatchCreationView
@@ -96,6 +97,10 @@ def get_admin_urls_event(admin_site):
              name="aks_by_owner"),
         path('<slug:event_slug>/ak-csv-export/', admin_site.admin_view(AKCSVExportView.as_view()),
              name="ak_csv_export"),
+        path('<slug:event_slug>/ak-json-export/', admin_site.admin_view(AKJSONExportView.as_view()),
+             name="ak_json_export"),
+        path('<slug:event_slug>/ak-json-import/', admin_site.admin_view(AKJSONImportView.as_view()),
+             name="ak_json_import"),
         path('<slug:slug>/ak-wiki-export/', admin_site.admin_view(AKWikiExportView.as_view()),
              name="ak_wiki_export"),
         path('<slug:event_slug>/delete-orga-messages/', admin_site.admin_view(AKMessageDeleteView.as_view()),
diff --git a/AKModel/views/ak.py b/AKModel/views/ak.py
index 3afec5ac2de03a62ad3d103d17062927c3b97026..547f3606614d341c889d7b8e9064cc3fb3ee15be 100644
--- a/AKModel/views/ak.py
+++ b/AKModel/views/ak.py
@@ -1,3 +1,6 @@
+import json
+from datetime import timedelta
+
 from django.contrib import messages
 from django.urls import reverse_lazy
 from django.utils.translation import gettext_lazy as _
@@ -37,6 +40,88 @@ class AKCSVExportView(AdminViewMixin, FilterByEventSlugMixin, ListView):
         return super().get_queryset().order_by("ak__track")
 
 
+class AKJSONExportView(AdminViewMixin, FilterByEventSlugMixin, ListView):
+    """
+    View: Export all AK slots of this event in JSON format ordered by tracks
+    """
+    template_name = "admin/AKModel/ak_json_export.html"
+    model = AKSlot
+    context_object_name = "slots"
+    title = _("AK JSON Export")
+
+    def get_queryset(self):
+        return super().get_queryset().order_by("ak__track")
+
+    def get_context_data(self, **kwargs):
+        from AKModel.availability.models import Availability
+
+        SLOTS_IN_AN_HOUR = 1
+
+        rooms = Room.objects.filter(event=self.event)
+        participants = []
+        timeslots = {
+            "info": {"duration": (1.0 / SLOTS_IN_AN_HOUR), },
+            "blocks": [],
+            }
+
+        context = super().get_context_data(**kwargs)
+        context["rooms"] = rooms
+        context["participants"] = json.dumps(participants)
+
+        for slot in context["slots"]:
+            slot.slots_in_an_hour = SLOTS_IN_AN_HOUR
+
+        ak_availabilities = {slot.ak.pk: availability
+                             for slot in context["slots"]
+                             for availability in slot.ak.availabilities.all()}
+        room_availabilities = {room.pk: availability
+                                    for room in rooms
+                                    for availability in room.availabilities.all()}
+        person_availabilities = {person.pk: availability
+                                      for person in AKOwner.objects.filter(event=self.event)
+                                      for availability in person.availabilities.all()}
+
+
+
+        for block in self.event.time_slots(slots_in_an_hour=SLOTS_IN_AN_HOUR):
+            current_block = []
+
+            for slot_index in block:
+                slot = self.event.time_slot(time_slot_index=slot_index,
+                                            slots_in_an_hour=SLOTS_IN_AN_HOUR)
+                constraints = []
+
+                if slot.end < self.event.reso_deadline:
+                    constraints.append("resolution")
+
+                for (ak, availability) in ak_availabilities.items():
+                    if availability.contains(slot):
+                        constraints.append(f"availability-ak-{ak}")
+
+                for (person, availability) in person_availabilities.items():
+                    if availability.contains(slot):
+                        constraints.append(f"availability-person-{person}")
+
+                for (room, availability) in room_availabilities.items():
+                    if availability.contains(slot):
+                        constraints.append(f"availability-room-{room}")
+
+                current_block.append({
+                    "id": slot_index,
+                    "info": {
+                        "start": slot.simplified,
+                    },
+                    "fulfilled_time_constraints": constraints,
+                    })
+
+            timeslots["blocks"].append(current_block)
+
+        context["timeslots"] = json.dumps(timeslots)
+
+        return context
+
+
+
 class AKWikiExportView(AdminViewMixin, DetailView):
     """
     View: Export AKs of this event in wiki syntax
diff --git a/AKModel/views/manage.py b/AKModel/views/manage.py
index 64443cb8f7de27832f518df75378f1fc2ea59571..0ed330447930914797526a1315db713a41c06bd8 100644
--- a/AKModel/views/manage.py
+++ b/AKModel/views/manage.py
@@ -12,7 +12,7 @@ from django.views.generic import TemplateView, DetailView
 from django_tex.core import render_template_with_context, run_tex_in_directory
 from django_tex.response import PDFResponse
 
-from AKModel.forms import SlideExportForm, DefaultSlotEditorForm
+from AKModel.forms import SlideExportForm, DefaultSlotEditorForm, JSONImportForm
 from AKModel.metaviews.admin import EventSlugMixin, IntermediateAdminView, IntermediateAdminActionView, AdminViewMixin
 from AKModel.models import ConstraintViolation, Event, DefaultSlot, AKOwner
 
@@ -245,3 +245,13 @@ class AKsByUserView(AdminViewMixin, EventSlugMixin, DetailView):
     model = AKOwner
     context_object_name = 'owner'
     template_name = "admin/AKModel/aks_by_user.html"
+
+
+class AKJSONImportView(EventSlugMixin, IntermediateAdminView):
+    form_class = JSONImportForm
+    title = _("AK JSON Import")
+
+    def form_valid(self, form):
+        self.event.schedule_from_json(form.data["json_data"])
+
+        return redirect("admin:event_status", self.event.slug)
diff --git a/AKModel/views/status.py b/AKModel/views/status.py
index e14ce2fbbcc7c662f71d1711a6fa14b372fb3595..0c12b30348c63d6178f0b5c38d2fcec6cfebf664 100644
--- a/AKModel/views/status.py
+++ b/AKModel/views/status.py
@@ -133,10 +133,18 @@ class EventAKsWidget(TemplateStatusWidget):
                     "text": _("Manage ak tracks"),
                     "url": reverse_lazy("admin:tracks_manage", kwargs={"event_slug": context["event"].slug}),
                 },
+                {
+                    "text": _("Import AK schedule from JSON"),
+                    "url": reverse_lazy("admin:ak_json_import", kwargs={"event_slug": context["event"].slug}),
+                },
                 {
                     "text": _("Export AKs as CSV"),
                     "url": reverse_lazy("admin:ak_csv_export", kwargs={"event_slug": context["event"].slug}),
                 },
+                {
+                    "text": _("Export AKs as JSON"),
+                    "url": reverse_lazy("admin:ak_json_export", kwargs={"event_slug": context["event"].slug}),
+                },
                 {
                     "text": _("Export AKs for Wiki"),
                     "url": reverse_lazy("admin:ak_wiki_export", kwargs={"slug": context["event"].slug}),