diff --git a/AKModel/forms.py b/AKModel/forms.py
index be4929c4fd8c8af10eaafecd00c2fa9295e07284..78557911335d4d33bd5f9e138ce27a8e3310771f 100644
--- a/AKModel/forms.py
+++ b/AKModel/forms.py
@@ -281,3 +281,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 1b23b8732566652abf022f159bd8dbe063c68bb7..887b720226d34e77f54c408e3fcb5c4df2ddc751 100644
--- a/AKModel/models.py
+++ b/AKModel/models.py
@@ -1,4 +1,5 @@
 import itertools
+import json
 from datetime import datetime, timedelta
 
 from django.core.validators import RegexValidator
@@ -173,6 +174,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.
@@ -566,6 +627,20 @@ class Room(models.Model):
     def __str__(self):
         return self.title
 
+    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 AKSlot(models.Model):
     """ An AK Mapping matches an AK to a room during a certain time.
@@ -662,6 +737,28 @@ class AKSlot(models.Model):
         super().save(*args,
                      force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
 
+    def as_json(self) -> str:
+        data = {
+            "id": self.pk,
+            "duration": int(self.duration * self.slots_in_an_hour),
+            "properties": {},
+            "room_constraints": [constraint.name
+                                 for constraint in self.ak.requirements.all()],
+            "time_constraints": ["resolution"] if self.ak.reso else [],
+            "info": {
+                "name": self.ak.name,
+                "head": ", ".join([str(owner)
+                                   for owner in self.ak.owners.all()]),
+                "description": self.ak.description,
+                "reso": self.ak.reso,
+                },
+            }
+
+        data["time_constraints"].append(f"availability-ak-{self.pk}")
+        data["time_constraints"] += [f"availability-person-{owner.pk}"
+                                     for owner in self.ak.owners.all()]
+
+        return json.dumps(data)
 
 class AKOrgaMessage(models.Model):
     """
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..88fc6bf4c767ac32cbeb6aee37d3d009c0b9538b 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 _
@@ -5,7 +8,7 @@ from django.views.generic import ListView, DetailView
 
 from AKModel.metaviews.admin import AdminViewMixin, FilterByEventSlugMixin, EventSlugMixin, IntermediateAdminView, \
     IntermediateAdminActionView
-from AKModel.models import AKRequirement, AKSlot, Event, AKOrgaMessage, AK
+from AKModel.models import AKRequirement, AKSlot, Event, AKOrgaMessage, AK, Room, AKOwner
 
 
 class AKRequirementOverview(AdminViewMixin, FilterByEventSlugMixin, ListView):
@@ -37,6 +40,86 @@ 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..f4564f0903f22d0345d04f6deffa21a67d28585b 100644
--- a/AKModel/views/manage.py
+++ b/AKModel/views/manage.py
@@ -4,15 +4,17 @@ import os
 import tempfile
 from itertools import zip_longest
 
+
 from django.contrib import messages
 from django.db.models.functions import Now
+from django.shortcuts import redirect
 from django.utils.dateparse import parse_datetime
 from django.utils.translation import gettext_lazy as _
 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 +247,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 233d4cc9be571db5b4a1ee31a607d1fa04b9305e..451d8b27bcdcb98b56b8e5a456fb93c12c1f2924 100644
--- a/AKModel/views/status.py
+++ b/AKModel/views/status.py
@@ -138,10 +138,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}),