diff --git a/program/models.py b/program/models.py
index f11245b284d1a878abd38bb890dfbb08241cc019..ac7677b65c830e680e80c91c0dc23277d8962d4d 100644
--- a/program/models.py
+++ b/program/models.py
@@ -41,6 +41,12 @@ from steering.settings import (
 )
 
 
+class ScheduleConflictError(ValidationError):
+    def __init__(self, *args, conflicts=None, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.conflicts = conflicts
+
+
 class Type(models.Model):
     name = models.CharField(max_length=32)
     slug = models.SlugField(max_length=32, unique=True)
@@ -732,7 +738,7 @@ class Schedule(models.Model):
         """
 
         sdl = data["schedule"]
-        solutions = data["solutions"]
+        solutions = data.get("solutions", [])
 
         # Regenerate conflicts
         schedule = Schedule.instantiate_upcoming(sdl, show_pk, schedule_pk)
@@ -756,9 +762,10 @@ class Schedule(models.Model):
         )
 
         if len(solutions) != num_conflicts:
-            raise ValidationError(
+            raise ScheduleConflictError(
                 _("Numbers of conflicts and solutions don't match."),
                 code="one-solution-per-conflict",
+                conflicts=conflicts,
             )
 
         # Projected timeslots to create
@@ -963,7 +970,11 @@ class Schedule(models.Model):
             conflicts["notes"] = data.get("notes")
             conflicts["playlists"] = data.get("playlists")
 
-            return conflicts
+            raise ScheduleConflictError(
+                _("Not all conflicts have been resolved."),
+                code="unresolved-conflicts",
+                conflicts=conflicts,
+            )
 
         # Collect upcoming timeslots to delete which might still remain
         del_timeslots = TimeSlot.objects.filter(
diff --git a/program/views.py b/program/views.py
index 8d18a2db210c52fb59f1199458faf268c2e3d351..c000b10be91cc6f379342a7f3531dd02807c00e1 100644
--- a/program/views.py
+++ b/program/views.py
@@ -42,6 +42,7 @@ from program.models import (
     MusicFocus,
     Note,
     Schedule,
+    ScheduleConflictError,
     Show,
     TimeSlot,
     Topic,
@@ -483,28 +484,17 @@ class APIScheduleViewSet(
         if show_pk is None or "schedule" not in request.data:
             return Response(status=status.HTTP_400_BAD_REQUEST)
 
-        # First create submit -> return projected timeslots and collisions
-        # TODO: Perhaps directly insert into database if no conflicts found
-        if "solutions" not in request.data:
+        try:
+            resolution = Schedule.resolve_conflicts(request.data, pk, show_pk)
+        except ScheduleConflictError as exc:
             # TODO: respond with status.HTTP_409_CONFLICT when the dashboard can handle it
-            return Response(
-                Schedule.make_conflicts(request.data["schedule"], pk, show_pk),
-            )
-
-        # Otherwise try to resolve
-        resolution = Schedule.resolve_conflicts(request.data, pk, show_pk)
+            return Response(exc.conflicts)
 
         if all(key in resolution for key in ["create", "update", "delete"]):
             # this is a dry-run
             return Response(resolution, status=status.HTTP_202_ACCEPTED)
 
-        # If resolution went well
-        if "projected" not in resolution:
-            return Response(resolution, status=status.HTTP_201_CREATED)
-
-        # Otherwise return conflicts
-        # TODO: respond with status.HTTP_409_CONFLICT when the dashboard can handle it
-        return Response(resolution)
+        return Response(resolution, status=status.HTTP_201_CREATED)
 
     def update(self, request, *args, **kwargs):
         """
@@ -540,26 +530,14 @@ class APIScheduleViewSet(
             serializer = ScheduleSerializer(schedule)
             return Response(serializer.data)
 
-        # First update submit -> return projected timeslots and collisions
-        if "solutions" not in request.data:
-            # TODO: respond with status.HTTP_409_CONFLICT when the dashboard can handle it
-            return Response(
-                Schedule.make_conflicts(
-                    request.data["schedule"], schedule.pk, schedule.show.pk
-                )
+        try:
+            resolution = Schedule.resolve_conflicts(
+                request.data, schedule.pk, schedule.show.pk
             )
+        except ScheduleConflictError as exc:
+            # TODO: respond with status.HTTP_409_CONFLICT when the dashboard can handle it
+            return Response(exc.conflicts)
 
-        # Otherwise try to resolve
-        resolution = Schedule.resolve_conflicts(
-            request.data, schedule.pk, schedule.show.pk
-        )
-
-        # If resolution went well
-        if "projected" not in resolution:
-            return Response(resolution)
-
-        # Otherwise return conflicts
-        # TODO: respond with status.HTTP_409_CONFLICT when the dashboard can handle it
         return Response(resolution)
 
     def destroy(self, request, *args, **kwargs):