diff --git a/program/views.py b/program/views.py
index 01245a7fc6d5382f37fef288d64042b13815af41..dfd25ac3f3040fade3a0d3d0fde099d2df390c49 100644
--- a/program/views.py
+++ b/program/views.py
@@ -143,34 +143,65 @@ def gap_entry(*, gap_start: datetime, gap_end: datetime) -> dict:
     }
 
 
-def json_day_schedule(request, year=None, month=None, day=None):
-    # datetime.combine returns a timezone naive datetime object
-    if year is None and month is None and day is None:
-        start = timezone.make_aware(datetime.combine(timezone.now(), time(0, 0)))
-    else:
-        start = timezone.make_aware(datetime.combine(date(year, month, day), time(0, 0)))
-
-    end = start + timedelta(hours=24)
-
-    timeslots = get_timerange_timeslots(start, end).select_related("schedule")
-    schedule = []
-
-    for ts in timeslots:
-        entry = {
-            "start": ts.start.strftime("%Y-%m-%d_%H:%M:%S"),
-            "end": ts.end.strftime("%Y-%m-%d_%H:%M:%S"),
-            "title": ts.show.name,
-            "id": ts.show.id,
-        }
-
-        schedule.append(entry)
-
-    return HttpResponse(
-        json.dumps(schedule, ensure_ascii=False).encode("utf8"),
-        content_type="application/json; charset=utf-8",
-    )
+@extend_schema_view(
+    create=extend_schema(summary="List schedule for a specific date."),
+    list=extend_schema(
+        summary="List schedule for a specific date.",
+        description=(
+            "Returns a list of the schedule for a specific date."
+            "Expects parameters `year` (int), `month` (int), and `day` (int) as url components."
+            "e.g. /program/2024/01/31/"
+        ),
+    ),
+)
+class APIDayScheduleViewSet(
+    mixins.ListModelMixin,
+    viewsets.GenericViewSet,
+):
+    queryset = TimeSlot.objects.all()
+
+    def list(self, request, year=None, month=None, day=None):
+        # datetime.combine returns a timezone naive datetime object
+        if year is None and month is None and day is None:
+            start = timezone.make_aware(datetime.combine(timezone.now(), time(0, 0)))
+        else:
+            start = timezone.make_aware(datetime.combine(date(year, month, day), time(0, 0)))
+
+        end = start + timedelta(hours=24)
+
+        timeslots = get_timerange_timeslots(start, end).select_related("schedule")
+        schedule = []
+
+        for ts in timeslots:
+            entry = {
+                "start": ts.start.strftime("%Y-%m-%d_%H:%M:%S"),
+                "end": ts.end.strftime("%Y-%m-%d_%H:%M:%S"),
+                "title": ts.show.name,
+                "id": ts.show.id,
+            }
 
+            schedule.append(entry)
 
+        return HttpResponse(
+            json.dumps(schedule, ensure_ascii=False).encode("utf8"),
+            content_type="application/json; charset=utf-8",
+        )
+
+
+@extend_schema_view(
+    create=extend_schema(summary="List playout."),
+    list=extend_schema(
+        summary="List scheduled playout.",
+        description=(
+            "Returns a list of the scheduled playout."
+            "Expects parameters `start` (date), `end` (date), and `includeVirtual` (boolean)."
+            "- `start` is today by default."
+            "- `end` is one week after the start date by default."
+            "- `includeVirtual` is false by default."
+            "The schedule will include virtual timeslots to fill unscheduled gaps if requested."
+        ),
+    ),
+)
 class APIPlayoutViewSet(
     mixins.ListModelMixin,
     viewsets.GenericViewSet,
@@ -180,15 +211,6 @@ class APIPlayoutViewSet(
     def list(self, request, *args, **kwargs):
         """
         Return a JSON representation of the scheduled playout.
-
-        Expects GET parameters `start` (date), `end` (date), and `includeVirtual` (boolean).
-
-        - `start` is today by default.
-        - `end` is one week after the start date by default.
-        - `includeVirtual` is false by default.
-
-        The schedule will include virtual timeslots to fill unscheduled gaps if requested.
-
         Called by
         - engine (playout) to retrieve timeslots within a given timerange
         - internal calendar to retrieve all timeslots for a week
diff --git a/steering/urls.py b/steering/urls.py
index aa1ca7e693b77f9a4bb4bb12bf90dd5e88586cdb..92baba9daa51aa62db29dfd4db508d29b340232d 100644
--- a/steering/urls.py
+++ b/steering/urls.py
@@ -43,7 +43,7 @@ from program.views import (
     APITopicViewSet,
     APITypeViewSet,
     APIUserViewSet,
-    json_day_schedule,
+    APIDayScheduleViewSet,
     APIPlayoutViewSet
 )
 
@@ -73,7 +73,7 @@ router.register(r"program/week", APIPlayoutViewSet, basename="program/week")
 urlpatterns = [
     path("openid/", include("oidc_provider.urls", namespace="oidc_provider")),
     path("api/v1/", include(router.urls)),
-    path("api/v1/program/<int:year>/<int:month>/<int:day>/", json_day_schedule),
+    path("api/v1/program/<int:year>/<int:month>/<int:day>/", APIDayScheduleViewSet.as_view({'get': 'list'}), name="program"),
     path("api/v1/schema/", SpectacularAPIView.as_view(), name="schema"),
     path(
         "api/v1/schema/swagger-ui/",