diff --git a/CHANGELOG.md b/CHANGELOG.md
index e712db67a15f6cde24b3b5a86af6eb23e850bc33..c3d7d04e0e9ebdc3df2cb9b83c5bb1a9c7c28c49 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -29,6 +29,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - The `last_date` field in the `Show` model is nullable.
 - The `CharField`s and `TextFields` in the models are not nullable.
 - The `subtitle` field of th `Category` model is now a `CharField`.
+- Provide properties in API schemas in CamelCase notation (aura#141)
+- Use _id suffix for all object reference in REST APIs (aura#166)
 
 ### Deprecated
 
diff --git a/conftest.py b/conftest.py
index 6768306045361ca3165846f277e3ae8a1b34ade0..d97fe16afb2b1cbc0e41e294958b4ed9fc016a09 100644
--- a/conftest.py
+++ b/conftest.py
@@ -14,17 +14,15 @@ from program.tests.factories import (
     ScheduleFactory,
     ShowFactory,
     TimeslotFactory,
-    TypeFactory, ImageFactory,
+    TypeFactory,
+    ImageFactory,
 )
 
 
 def assert_data(response, data) -> None:
     if "schedule" in data:
         for key, value in data["schedule"].items():
-            if key == "rrule":
-                assert response.data[key] == value
-            else:
-                assert str(response.data[key]) == value
+            assert response.data[key] == value
     else:
         for key, value in data.items():
             if key == "password":
diff --git a/poetry.lock b/poetry.lock
index 569b2508c34c89ebc3e32fab0bf56494cbbcdd2d..26742ee8c645d5c76fffe3d9e8eb0615fdbf608c 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,17 +1,19 @@
-# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.5.0 and should not be changed by hand.
 
 [[package]]
 name = "asgiref"
-version = "3.6.0"
+version = "3.7.0"
 description = "ASGI specs, helper code, and adapters"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "asgiref-3.6.0-py3-none-any.whl", hash = "sha256:71e68008da809b957b7ee4b43dbccff33d1b23519fb8344e33f049897077afac"},
-    {file = "asgiref-3.6.0.tar.gz", hash = "sha256:9567dfe7bd8d3c8c892227827c41cce860b368104c3431da67a0c5a65a949506"},
+    {file = "asgiref-3.7.0-py3-none-any.whl", hash = "sha256:14087924af5be5d8103d6f2edffe45a0bf7ab1b2a771b6f00a6db8c302f21f34"},
+    {file = "asgiref-3.7.0.tar.gz", hash = "sha256:5d6c4a8a1c99f58eaa3bc392ee04e3587b693f09e3af1f3f16a09094f334eb52"},
 ]
 
+[package.dependencies]
+typing-extensions = {version = "*", markers = "python_version < \"3.11\""}
+
 [package.extras]
 tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
 
@@ -19,7 +21,6 @@ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
 name = "attrs"
 version = "23.1.0"
 description = "Classes Without Boilerplate"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -38,7 +39,6 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte
 name = "black"
 version = "22.12.0"
 description = "The uncompromising code formatter."
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -73,7 +73,6 @@ uvloop = ["uvloop (>=0.15.2)"]
 name = "certifi"
 version = "2023.5.7"
 description = "Python package for providing Mozilla's CA Bundle."
-category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -85,7 +84,6 @@ files = [
 name = "cfgv"
 version = "3.3.1"
 description = "Validate configuration and produce human readable error messages."
-category = "dev"
 optional = false
 python-versions = ">=3.6.1"
 files = [
@@ -97,7 +95,6 @@ files = [
 name = "charset-normalizer"
 version = "3.1.0"
 description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
-category = "main"
 optional = false
 python-versions = ">=3.7.0"
 files = [
@@ -182,7 +179,6 @@ files = [
 name = "click"
 version = "8.1.3"
 description = "Composable command line interface toolkit"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -197,7 +193,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""}
 name = "colorama"
 version = "0.4.6"
 description = "Cross-platform colored terminal text."
-category = "dev"
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
 files = [
@@ -209,7 +204,6 @@ files = [
 name = "distlib"
 version = "0.3.6"
 description = "Distribution utilities"
-category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -221,7 +215,6 @@ files = [
 name = "django"
 version = "3.2.19"
 description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design."
-category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -242,7 +235,6 @@ bcrypt = ["bcrypt"]
 name = "django-auth-ldap"
 version = "4.3.0"
 description = "Django LDAP authentication backend."
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -258,7 +250,6 @@ python-ldap = ">=3.1"
 name = "django-cors-headers"
 version = "3.14.0"
 description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)."
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -273,7 +264,6 @@ Django = ">=3.2"
 name = "django-extensions"
 version = "3.2.1"
 description = "Extensions for Django"
-category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -288,7 +278,6 @@ Django = ">=3.2"
 name = "django-filter"
 version = "22.1"
 description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically."
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -303,7 +292,6 @@ Django = ">=3.2"
 name = "django-oidc-provider"
 version = "0.7.0"
 description = "OpenID Connect Provider implementation for Django."
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -317,7 +305,6 @@ pyjwkest = ">=1.3.0"
 name = "django-versatileimagefield"
 version = "2.2"
 description = "A drop-in replacement for django's ImageField that provides a flexible, intuitive and easily-extensible interface for creating new images from the one assigned to the field."
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -333,7 +320,6 @@ python-magic = ">=0.4.15,<1.0.0"
 name = "djangorestframework"
 version = "3.14.0"
 description = "Web APIs for Django, made easy."
-category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -345,11 +331,20 @@ files = [
 django = ">=3.0"
 pytz = "*"
 
+[[package]]
+name = "djangorestframework-camel-case"
+version = "1.4.2"
+description = "Camel case JSON support for Django REST framework."
+optional = false
+python-versions = ">=3.5"
+files = [
+    {file = "djangorestframework-camel-case-1.4.2.tar.gz", hash = "sha256:cdae75846648abb6585c7470639a1d2fb064dc45f8e8b62aaa50be7f1a7a61f4"},
+]
+
 [[package]]
 name = "drf-nested-routers"
 version = "0.93.4"
 description = "Nested resources for the Django Rest Framework"
-category = "main"
 optional = false
 python-versions = ">=3.5"
 files = [
@@ -365,7 +360,6 @@ djangorestframework = ">=3.6.0"
 name = "drf-spectacular"
 version = "0.24.2"
 description = "Sane and flexible OpenAPI 3 schema generation for Django REST framework"
-category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -389,7 +383,6 @@ sidecar = ["drf-spectacular-sidecar"]
 name = "exceptiongroup"
 version = "1.1.1"
 description = "Backport of PEP 654 (exception groups)"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -404,7 +397,6 @@ test = ["pytest (>=6)"]
 name = "factory-boy"
 version = "3.2.1"
 description = "A versatile test fixtures replacement based on thoughtbot's factory_bot for Ruby."
-category = "dev"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -421,14 +413,13 @@ doc = ["Sphinx", "sphinx-rtd-theme", "sphinxcontrib-spelling"]
 
 [[package]]
 name = "faker"
-version = "18.7.0"
+version = "18.9.0"
 description = "Faker is a Python package that generates fake data for you."
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "Faker-18.7.0-py3-none-any.whl", hash = "sha256:38dbc3b80e655d7301e190426ab30f04b6b7f6ca4764c5dd02772ffde0fa6dcd"},
-    {file = "Faker-18.7.0.tar.gz", hash = "sha256:f02c6d3fdb5bc781f80b440cf2bdec336ed47ecfb8d620b20c3d4188ed051831"},
+    {file = "Faker-18.9.0-py3-none-any.whl", hash = "sha256:defe9ed618a67ebf0f3eb1895e198c2355a7128a09087a6dce342ef2253263ea"},
+    {file = "Faker-18.9.0.tar.gz", hash = "sha256:80a5ea1464556c06b98bf47ea3adc7f33811a1182518d847860b1874080bd3c9"},
 ]
 
 [package.dependencies]
@@ -438,7 +429,6 @@ python-dateutil = ">=2.4"
 name = "filelock"
 version = "3.12.0"
 description = "A platform independent file lock."
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -454,7 +444,6 @@ testing = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "diff-cover (>=7.5)", "p
 name = "flake8"
 version = "6.0.0"
 description = "the modular source code checker: pep8 pyflakes and co"
-category = "dev"
 optional = false
 python-versions = ">=3.8.1"
 files = [
@@ -471,7 +460,6 @@ pyflakes = ">=3.0.0,<3.1.0"
 name = "future"
 version = "0.18.3"
 description = "Clean single-source support for Python 3 and 2"
-category = "main"
 optional = false
 python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
 files = [
@@ -482,7 +470,6 @@ files = [
 name = "gunicorn"
 version = "20.1.0"
 description = "WSGI HTTP Server for UNIX"
-category = "main"
 optional = false
 python-versions = ">=3.5"
 files = [
@@ -503,7 +490,6 @@ tornado = ["tornado (>=0.2)"]
 name = "identify"
 version = "2.5.24"
 description = "File identification library for Python"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -518,7 +504,6 @@ license = ["ukkonen"]
 name = "idna"
 version = "3.4"
 description = "Internationalized Domain Names in Applications (IDNA)"
-category = "main"
 optional = false
 python-versions = ">=3.5"
 files = [
@@ -530,7 +515,6 @@ files = [
 name = "inflection"
 version = "0.5.1"
 description = "A port of Ruby on Rails inflector to Python"
-category = "main"
 optional = false
 python-versions = ">=3.5"
 files = [
@@ -542,7 +526,6 @@ files = [
 name = "iniconfig"
 version = "2.0.0"
 description = "brain-dead simple config-ini parsing"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -554,7 +537,6 @@ files = [
 name = "isort"
 version = "5.12.0"
 description = "A Python utility / library to sort Python imports."
-category = "dev"
 optional = false
 python-versions = ">=3.8.0"
 files = [
@@ -572,7 +554,6 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"]
 name = "jsonschema"
 version = "4.17.3"
 description = "An implementation of JSON Schema validation for Python"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -592,7 +573,6 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-
 name = "markupsafe"
 version = "2.1.2"
 description = "Safely add untrusted strings to HTML/XML markup."
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -652,7 +632,6 @@ files = [
 name = "mccabe"
 version = "0.7.0"
 description = "McCabe checker, plugin for flake8"
-category = "dev"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -664,7 +643,6 @@ files = [
 name = "mypy-extensions"
 version = "1.0.0"
 description = "Type system extensions for programs checked with the mypy type checker."
-category = "dev"
 optional = false
 python-versions = ">=3.5"
 files = [
@@ -674,14 +652,13 @@ files = [
 
 [[package]]
 name = "nodeenv"
-version = "1.7.0"
+version = "1.8.0"
 description = "Node.js virtual environment builder"
-category = "dev"
 optional = false
 python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
 files = [
-    {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"},
-    {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"},
+    {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"},
+    {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"},
 ]
 
 [package.dependencies]
@@ -691,7 +668,6 @@ setuptools = "*"
 name = "packaging"
 version = "23.1"
 description = "Core utilities for Python packages"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -703,7 +679,6 @@ files = [
 name = "pathspec"
 version = "0.11.1"
 description = "Utility library for gitignore style pattern matching of file paths."
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -715,7 +690,6 @@ files = [
 name = "pillow"
 version = "9.5.0"
 description = "Python Imaging Library (Fork)"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -793,25 +767,23 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa
 
 [[package]]
 name = "platformdirs"
-version = "3.5.0"
+version = "3.5.1"
 description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "platformdirs-3.5.0-py3-none-any.whl", hash = "sha256:47692bc24c1958e8b0f13dd727307cff1db103fca36399f457da8e05f222fdc4"},
-    {file = "platformdirs-3.5.0.tar.gz", hash = "sha256:7954a68d0ba23558d753f73437c55f89027cf8f5108c19844d4b82e5af396335"},
+    {file = "platformdirs-3.5.1-py3-none-any.whl", hash = "sha256:e2378146f1964972c03c085bb5662ae80b2b8c06226c54b2ff4aa9483e8a13a5"},
+    {file = "platformdirs-3.5.1.tar.gz", hash = "sha256:412dae91f52a6f84830f39a8078cecd0e866cb72294a5c66808e74d5e88d251f"},
 ]
 
 [package.extras]
-docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"]
+docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.2.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"]
 test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
 
 [[package]]
 name = "pluggy"
 version = "1.0.0"
 description = "plugin and hook calling mechanisms for python"
-category = "dev"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -827,7 +799,6 @@ testing = ["pytest", "pytest-benchmark"]
 name = "pre-commit"
 version = "2.21.0"
 description = "A framework for managing and maintaining multi-language pre-commit hooks."
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -846,7 +817,6 @@ virtualenv = ">=20.10.0"
 name = "psycopg2-binary"
 version = "2.9.6"
 description = "psycopg2 - Python-PostgreSQL Database Adapter"
-category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -918,7 +888,6 @@ files = [
 name = "pyasn1"
 version = "0.5.0"
 description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)"
-category = "main"
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
 files = [
@@ -930,7 +899,6 @@ files = [
 name = "pyasn1-modules"
 version = "0.3.0"
 description = "A collection of ASN.1-based protocols modules"
-category = "main"
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
 files = [
@@ -945,7 +913,6 @@ pyasn1 = ">=0.4.6,<0.6.0"
 name = "pycodestyle"
 version = "2.10.0"
 description = "Python style guide checker"
-category = "dev"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -955,52 +922,49 @@ files = [
 
 [[package]]
 name = "pycryptodomex"
-version = "3.17"
+version = "3.18.0"
 description = "Cryptographic library for Python"
-category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 files = [
-    {file = "pycryptodomex-3.17-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:12056c38e49d972f9c553a3d598425f8a1c1d35b2e4330f89d5ff1ffb70de041"},
-    {file = "pycryptodomex-3.17-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ab33c2d9f275e05e235dbca1063753b5346af4a5cac34a51fa0da0d4edfb21d7"},
-    {file = "pycryptodomex-3.17-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:caa937ff29d07a665dfcfd7a84f0d4207b2ebf483362fa9054041d67fdfacc20"},
-    {file = "pycryptodomex-3.17-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:db23d7341e21b273d2440ec6faf6c8b1ca95c8894da612e165be0b89a8688340"},
-    {file = "pycryptodomex-3.17-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:f854c8476512cebe6a8681cc4789e4fcff6019c17baa0fd72b459155dc605ab4"},
-    {file = "pycryptodomex-3.17-cp27-cp27m-win32.whl", hash = "sha256:a57e3257bacd719769110f1f70dd901c5b6955e9596ad403af11a3e6e7e3311c"},
-    {file = "pycryptodomex-3.17-cp27-cp27m-win_amd64.whl", hash = "sha256:d38ab9e53b1c09608ba2d9b8b888f1e75d6f66e2787e437adb1fecbffec6b112"},
-    {file = "pycryptodomex-3.17-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:3c2516b42437ae6c7a29ef3ddc73c8d4714e7b6df995b76be4695bbe4b3b5cd2"},
-    {file = "pycryptodomex-3.17-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:5c23482860302d0d9883404eaaa54b0615eefa5274f70529703e2c43cc571827"},
-    {file = "pycryptodomex-3.17-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:7a8dc3ee7a99aae202a4db52de5a08aa4d01831eb403c4d21da04ec2f79810db"},
-    {file = "pycryptodomex-3.17-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:7cc28dd33f1f3662d6da28ead4f9891035f63f49d30267d3b41194c8778997c8"},
-    {file = "pycryptodomex-3.17-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:2d4d395f109faba34067a08de36304e846c791808524614c731431ee048fe70a"},
-    {file = "pycryptodomex-3.17-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:55eed98b4150a744920597c81b3965b632038781bab8a08a12ea1d004213c600"},
-    {file = "pycryptodomex-3.17-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:7fa0b52df90343fafe319257b31d909be1d2e8852277fb0376ba89d26d2921db"},
-    {file = "pycryptodomex-3.17-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78f0ddd4adc64baa39b416f3637aaf99f45acb0bcdc16706f0cc7ebfc6f10109"},
-    {file = "pycryptodomex-3.17-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4fa037078e92c7cc49f6789a8bac3de06856740bb2038d05f2d9a2e4b165d59"},
-    {file = "pycryptodomex-3.17-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:88b0d5bb87eaf2a31e8a759302b89cf30c97f2f8ca7d83b8c9208abe8acb447a"},
-    {file = "pycryptodomex-3.17-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:6feedf4b0e36b395329b4186a805f60f900129cdf0170e120ecabbfcb763995d"},
-    {file = "pycryptodomex-3.17-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7a6651a07f67c28b6e978d63aa3a3fccea0feefed9a8453af3f7421a758461b7"},
-    {file = "pycryptodomex-3.17-cp35-abi3-win32.whl", hash = "sha256:32e764322e902bbfac49ca1446604d2839381bbbdd5a57920c9daaf2e0b778df"},
-    {file = "pycryptodomex-3.17-cp35-abi3-win_amd64.whl", hash = "sha256:4b51e826f0a04d832eda0790bbd0665d9bfe73e5a4d8ea93b6a9b38beeebe935"},
-    {file = "pycryptodomex-3.17-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:d4cf0128da167562c49b0e034f09e9cedd733997354f2314837c2fa461c87bb1"},
-    {file = "pycryptodomex-3.17-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:c92537b596bd5bffb82f8964cabb9fef1bca8a28a9e0a69ffd3ec92a4a7ad41b"},
-    {file = "pycryptodomex-3.17-pp27-pypy_73-win32.whl", hash = "sha256:599bb4ae4bbd614ca05f49bd4e672b7a250b80b13ae1238f05fd0f09d87ed80a"},
-    {file = "pycryptodomex-3.17-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4c4674f4b040321055c596aac926d12f7f6859dfe98cd12f4d9453b43ab6adc8"},
-    {file = "pycryptodomex-3.17-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67a3648025e4ddb72d43addab764336ba2e670c8377dba5dd752e42285440d31"},
-    {file = "pycryptodomex-3.17-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40e8a11f578bd0851b02719c862d55d3ee18d906c8b68a9c09f8c564d6bb5b92"},
-    {file = "pycryptodomex-3.17-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:23d83b610bd97704f0cd3acc48d99b76a15c8c1540d8665c94d514a49905bad7"},
-    {file = "pycryptodomex-3.17-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd29d35ac80755e5c0a99d96b44fb9abbd7e871849581ea6a4cb826d24267537"},
-    {file = "pycryptodomex-3.17-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64b876d57cb894b31056ad8dd6a6ae1099b117ae07a3d39707221133490e5715"},
-    {file = "pycryptodomex-3.17-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee8bf4fdcad7d66beb744957db8717afc12d176e3fd9c5d106835133881a049b"},
-    {file = "pycryptodomex-3.17-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c84689c73358dfc23f9fdcff2cb9e7856e65e2ce3b5ed8ff630d4c9bdeb1867b"},
-    {file = "pycryptodomex-3.17.tar.gz", hash = "sha256:0af93aad8d62e810247beedef0261c148790c52f3cd33643791cc6396dd217c1"},
+    {file = "pycryptodomex-3.18.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:160a39a708c36fa0b168ab79386dede588e62aec06eb505add870739329aecc6"},
+    {file = "pycryptodomex-3.18.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c2953afebf282a444c51bf4effe751706b4d0d63d7ca2cc51db21f902aa5b84e"},
+    {file = "pycryptodomex-3.18.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:ba95abd563b0d1b88401658665a260852a8e6c647026ee6a0a65589287681df8"},
+    {file = "pycryptodomex-3.18.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:192306cf881fe3467dda0e174a4f47bb3a8bb24b90c9cdfbdc248eec5fc0578c"},
+    {file = "pycryptodomex-3.18.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:f9ab5ef0718f6a8716695dea16d83b671b22c45e9c0c78fd807c32c0192e54b5"},
+    {file = "pycryptodomex-3.18.0-cp27-cp27m-win32.whl", hash = "sha256:50308fcdbf8345e5ec224a5502b4215178bdb5e95456ead8ab1a69ffd94779cb"},
+    {file = "pycryptodomex-3.18.0-cp27-cp27m-win_amd64.whl", hash = "sha256:4d9379c684efea80fdab02a3eb0169372bca7db13f9332cb67483b8dc8b67c37"},
+    {file = "pycryptodomex-3.18.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5594a125dae30d60e94f37797fc67ce3c744522de7992c7c360d02fdb34918f8"},
+    {file = "pycryptodomex-3.18.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:8ff129a5a0eb5ff16e45ca4fa70a6051da7f3de303c33b259063c19be0c43d35"},
+    {file = "pycryptodomex-3.18.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:3d9314ac785a5b75d5aaf924c5f21d6ca7e8df442e5cf4f0fefad4f6e284d422"},
+    {file = "pycryptodomex-3.18.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:f237278836dda412a325e9340ba2e6a84cb0f56b9244781e5b61f10b3905de88"},
+    {file = "pycryptodomex-3.18.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac614363a86cc53d8ba44b6c469831d1555947e69ab3276ae8d6edc219f570f7"},
+    {file = "pycryptodomex-3.18.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:302a8f37c224e7b5d72017d462a2be058e28f7be627bdd854066e16722d0fc0c"},
+    {file = "pycryptodomex-3.18.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:6421d23d6a648e83ba2670a352bcd978542dad86829209f59d17a3f087f4afef"},
+    {file = "pycryptodomex-3.18.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84e105787f5e5d36ec6a581ff37a1048d12e638688074b2a00bcf402f9aa1c2"},
+    {file = "pycryptodomex-3.18.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6875eb8666f68ddbd39097867325bd22771f595b4e2b0149739b5623c8bf899b"},
+    {file = "pycryptodomex-3.18.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:27072a494ce621cc7a9096bbf60ed66826bb94db24b49b7359509e7951033e74"},
+    {file = "pycryptodomex-3.18.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:1949e09ea49b09c36d11a951b16ff2a05a0ffe969dda1846e4686ee342fe8646"},
+    {file = "pycryptodomex-3.18.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6ed3606832987018615f68e8ed716a7065c09a0fe94afd7c9ca1b6777f0ac6eb"},
+    {file = "pycryptodomex-3.18.0-cp35-abi3-win32.whl", hash = "sha256:d56c9ec41258fd3734db9f5e4d2faeabe48644ba9ca23b18e1839b3bdf093222"},
+    {file = "pycryptodomex-3.18.0-cp35-abi3-win_amd64.whl", hash = "sha256:e00a4bacb83a2627e8210cb353a2e31f04befc1155db2976e5e239dd66482278"},
+    {file = "pycryptodomex-3.18.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:2dc4eab20f4f04a2d00220fdc9258717b82d31913552e766d5f00282c031b70a"},
+    {file = "pycryptodomex-3.18.0-pp27-pypy_73-win32.whl", hash = "sha256:75672205148bdea34669173366df005dbd52be05115e919551ee97171083423d"},
+    {file = "pycryptodomex-3.18.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bec6c80994d4e7a38312072f89458903b65ec99bed2d65aa4de96d997a53ea7a"},
+    {file = "pycryptodomex-3.18.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d35a8ffdc8b05e4b353ba281217c8437f02c57d7233363824e9d794cf753c419"},
+    {file = "pycryptodomex-3.18.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76f0a46bee539dae4b3dfe37216f678769349576b0080fdbe431d19a02da42ff"},
+    {file = "pycryptodomex-3.18.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:71687eed47df7e965f6e0bf3cadef98f368d5221f0fb89d2132effe1a3e6a194"},
+    {file = "pycryptodomex-3.18.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:73d64b32d84cf48d9ec62106aa277dbe99ab5fbfd38c5100bc7bddd3beb569f7"},
+    {file = "pycryptodomex-3.18.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbdcce0a226d9205560a5936b05208c709b01d493ed8307792075dedfaaffa5f"},
+    {file = "pycryptodomex-3.18.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58fc0aceb9c961b9897facec9da24c6a94c5db04597ec832060f53d4d6a07196"},
+    {file = "pycryptodomex-3.18.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:215be2980a6b70704c10796dd7003eb4390e7be138ac6fb8344bf47e71a8d470"},
+    {file = "pycryptodomex-3.18.0.tar.gz", hash = "sha256:3e3ecb5fe979e7c1bb0027e518340acf7ee60415d79295e5251d13c68dde576e"},
 ]
 
 [[package]]
 name = "pydot"
 version = "1.4.2"
 description = "Python interface to Graphviz's Dot"
-category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 files = [
@@ -1015,7 +979,6 @@ pyparsing = ">=2.1.4"
 name = "pyflakes"
 version = "3.0.1"
 description = "passive checker of Python programs"
-category = "dev"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -1027,7 +990,6 @@ files = [
 name = "pyjwkest"
 version = "1.4.2"
 description = "Python implementation of JWT, JWE, JWS and JWK"
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -1044,7 +1006,6 @@ six = "*"
 name = "pyparsing"
 version = "3.0.9"
 description = "pyparsing module - Classes and methods to define and execute parsing grammars"
-category = "main"
 optional = false
 python-versions = ">=3.6.8"
 files = [
@@ -1059,7 +1020,6 @@ diagrams = ["jinja2", "railroad-diagrams"]
 name = "pyrsistent"
 version = "0.19.3"
 description = "Persistent/Functional/Immutable data structures"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1096,7 +1056,6 @@ files = [
 name = "pytest"
 version = "7.3.1"
 description = "pytest: simple powerful testing with Python"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1119,7 +1078,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no
 name = "pytest-django"
 version = "4.5.2"
 description = "A Django plugin for pytest."
-category = "dev"
 optional = false
 python-versions = ">=3.5"
 files = [
@@ -1138,7 +1096,6 @@ testing = ["Django", "django-configurations (>=2.0)"]
 name = "pytest-factoryboy"
 version = "2.5.1"
 description = "Factory Boy support for pytest."
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1156,7 +1113,6 @@ typing_extensions = "*"
 name = "python-dateutil"
 version = "2.8.2"
 description = "Extensions to the standard Python datetime module"
-category = "main"
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
 files = [
@@ -1171,7 +1127,6 @@ six = ">=1.5"
 name = "python-ldap"
 version = "3.4.3"
 description = "Python modules for implementing LDAP clients"
-category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -1186,7 +1141,6 @@ pyasn1_modules = ">=0.1.5"
 name = "python-magic"
 version = "0.4.27"
 description = "File type identification using libmagic"
-category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 files = [
@@ -1198,7 +1152,6 @@ files = [
 name = "pytz"
 version = "2022.7.1"
 description = "World timezone definitions, modern and historical"
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -1210,7 +1163,6 @@ files = [
 name = "pyyaml"
 version = "6.0"
 description = "YAML parser and emitter for Python"
-category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -1258,14 +1210,13 @@ files = [
 
 [[package]]
 name = "requests"
-version = "2.30.0"
+version = "2.31.0"
 description = "Python HTTP for Humans."
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "requests-2.30.0-py3-none-any.whl", hash = "sha256:10e94cc4f3121ee6da529d358cdaeaff2f1c409cd377dbc72b825852f2f7e294"},
-    {file = "requests-2.30.0.tar.gz", hash = "sha256:239d7d4458afcb28a692cdd298d87542235f4ca8d36d03a15bfc128a6559a2f4"},
+    {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
+    {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
 ]
 
 [package.dependencies]
@@ -1280,26 +1231,24 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
 
 [[package]]
 name = "setuptools"
-version = "67.7.2"
+version = "67.8.0"
 description = "Easily download, build, install, upgrade, and uninstall Python packages"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "setuptools-67.7.2-py3-none-any.whl", hash = "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b"},
-    {file = "setuptools-67.7.2.tar.gz", hash = "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990"},
+    {file = "setuptools-67.8.0-py3-none-any.whl", hash = "sha256:5df61bf30bb10c6f756eb19e7c9f3b473051f48db77fddbe06ff2ca307df9a6f"},
+    {file = "setuptools-67.8.0.tar.gz", hash = "sha256:62642358adc77ffa87233bc4d2354c4b2682d214048f500964dbe760ccedf102"},
 ]
 
 [package.extras]
 docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
-testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
+testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
 testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
 
 [[package]]
 name = "six"
 version = "1.16.0"
 description = "Python 2 and 3 compatibility utilities"
-category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
 files = [
@@ -1311,7 +1260,6 @@ files = [
 name = "sqlparse"
 version = "0.4.4"
 description = "A non-validating SQL parser."
-category = "main"
 optional = false
 python-versions = ">=3.5"
 files = [
@@ -1328,7 +1276,6 @@ test = ["pytest", "pytest-cov"]
 name = "tomli"
 version = "2.0.1"
 description = "A lil' TOML parser"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1338,21 +1285,19 @@ files = [
 
 [[package]]
 name = "typing-extensions"
-version = "4.5.0"
+version = "4.6.0"
 description = "Backported and Experimental Type Hints for Python 3.7+"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"},
-    {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"},
+    {file = "typing_extensions-4.6.0-py3-none-any.whl", hash = "sha256:6ad00b63f849b7dcc313b70b6b304ed67b2b2963b3098a33efe18056b1a9a223"},
+    {file = "typing_extensions-4.6.0.tar.gz", hash = "sha256:ff6b238610c747e44c268aa4bb23c8c735d665a63726df3f9431ce707f2aa768"},
 ]
 
 [[package]]
 name = "uritemplate"
 version = "4.1.1"
 description = "Implementation of RFC 6570 URI Templates"
-category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -1364,7 +1309,6 @@ files = [
 name = "urllib3"
 version = "2.0.2"
 description = "HTTP library with thread-safe connection pooling, file post, and more."
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1382,7 +1326,6 @@ zstd = ["zstandard (>=0.18.0)"]
 name = "virtualenv"
 version = "20.23.0"
 description = "Virtual Python Environment builder"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1401,14 +1344,13 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "coverage-enable-subprocess
 
 [[package]]
 name = "werkzeug"
-version = "2.3.3"
+version = "2.3.4"
 description = "The comprehensive WSGI web application library."
-category = "dev"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "Werkzeug-2.3.3-py3-none-any.whl", hash = "sha256:4866679a0722de00796a74086238bb3b98d90f423f05de039abb09315487254a"},
-    {file = "Werkzeug-2.3.3.tar.gz", hash = "sha256:a987caf1092edc7523edb139edb20c70571c4a8d5eed02e0b547b4739174d091"},
+    {file = "Werkzeug-2.3.4-py3-none-any.whl", hash = "sha256:48e5e61472fee0ddee27ebad085614ebedb7af41e88f687aaf881afb723a162f"},
+    {file = "Werkzeug-2.3.4.tar.gz", hash = "sha256:1d5a58e0377d1fe39d061a5de4469e414e78ccb1e1e59c0f5ad6fa1c36c52b76"},
 ]
 
 [package.dependencies]
@@ -1420,4 +1362,4 @@ watchdog = ["watchdog (>=2.3)"]
 [metadata]
 lock-version = "2.0"
 python-versions = "^3.10"
-content-hash = "388d326d4aaf7eb6b8939fa5cb537305f7918725103bb49435c4ad04422f77ef"
+content-hash = "209a80ebcfe5925dfe8ef5a19db817c73ae1e45a41479b575e1be21c9af66253"
diff --git a/program/filters.py b/program/filters.py
index 298111c8b04302ad5ba4ca41981af0b80e6e563f..ca507c827a6d70580ecc078e5e68545a06cc9ffb 100644
--- a/program/filters.py
+++ b/program/filters.py
@@ -39,7 +39,17 @@ class IntegerInFilter(filters.BaseInFilter):
 
 
 class ShowFilterSet(StaticFilterHelpTextMixin, filters.FilterSet):
-    active = filters.BooleanFilter(
+    categoryIds = IntegerInFilter(
+        help_text="Return only shows of the given category or categories.",
+    )
+    categorySlug = filters.CharFilter(
+        field_name="category", help_text="Return only shows of the given category slug."
+    )
+    hostIds = IntegerInFilter(
+        field_name="hosts",
+        help_text="Return only shows assigned to the given host(s).",
+    )
+    isActive = filters.BooleanFilter(
         field_name="is_active",
         method="filter_active",
         help_text=(
@@ -47,46 +57,36 @@ class ShowFilterSet(StaticFilterHelpTextMixin, filters.FilterSet):
             "or past or upcoming shows if false."
         ),
     )
-    host = IntegerInFilter(
-        field_name="hosts",
-        help_text="Return only shows assigned to the given host(s).",
+    isPublic = filters.BooleanFilter(
+        field_name="is_public",
+        help_text="Return only shows that are public/non-public.",
+    )
+    languageIds = IntegerInFilter(
+        help_text="Return only shows of the given language(s).",
     )
-    music_focus = IntegerInFilter(
+    musicFocusIds = IntegerInFilter(
         field_name="music_focus",
         help_text="Return only shows with given music focus(es).",
     )
-    music_focus__slug = filters.CharFilter(
+    musicFocusSlug = filters.CharFilter(
         field_name="music_focus", help_text="Return only shows with the give music focus slug."
     )
-    owner = IntegerInFilter(
+    ownerIds = IntegerInFilter(
         field_name="owners",
         help_text="Return only shows that belong to the given owner(s).",
     )
-    category = IntegerInFilter(
-        help_text="Return only shows of the given category or categories.",
-    )
-    category__slug = filters.CharFilter(
-        field_name="category", help_text="Return only shows of the given category slug."
-    )
-    language = IntegerInFilter(
-        help_text="Return only shows of the given language(s).",
-    )
-    topic = IntegerInFilter(
+    topicIds = IntegerInFilter(
         help_text="Return only shows of the given topic(s).",
     )
-    topic__slug = filters.CharFilter(
+    topicSlug = filters.CharFilter(
         field_name="topic", help_text="Return only shows of the given topic slug."
     )
-    type = IntegerInFilter(
+    typeId = IntegerInFilter(
         help_text="Return only shows of a given type.",
     )
-    type__slug = filters.CharFilter(
+    typeSlug = filters.CharFilter(
         field_name="type", help_text="Return only shows of the given type slug."
     )
-    public = filters.BooleanFilter(
-        field_name="is_public",
-        help_text="Return only shows that are public/non-public.",
-    )
 
     def filter_active(self, queryset: QuerySet, name: str, value: bool):
         # Filter currently running shows
@@ -128,15 +128,19 @@ class ShowFilterSet(StaticFilterHelpTextMixin, filters.FilterSet):
     class Meta:
         model = models.Show
         fields = [
-            "active",
-            "category",
-            "host",
-            "language",
-            "music_focus",
-            "owner",
-            "public",
-            "topic",
-            "type",
+            "categoryIds",
+            "categorySlug",
+            "hostIds",
+            "isActive",
+            "isPublic",
+            "languageIds",
+            "musicFocusIds",
+            "musicFocusSlug",
+            "ownerIds",
+            "topicIds",
+            "topicSlug",
+            "typeId",
+            "typeSlug",
         ]
 
 
@@ -231,35 +235,45 @@ class TimeSlotFilterSet(filters.FilterSet):
 
 
 class NoteFilterSet(StaticFilterHelpTextMixin, filters.FilterSet):
-    show = IntegerInFilter(
-        field_name="timeslot__show",
-        help_text="Return only notes that belong to the specified show(s).",
-    )
-    timeslot = IntegerInFilter(
-        field_name="timeslot",
-        help_text="Return only notes that belong to the specified timeslot(s).",
-    )
     ids = IntegerInFilter(
         field_name="id",
         help_text="Return only notes matching the specified id(s).",
     )
-    show_owner = IntegerInFilter(
+    ownerIds = IntegerInFilter(
+        field_name="owner",
+        help_text="Return only notes that belong to the specified owner(s).",
+    )
+    showIds = IntegerInFilter(
+        field_name="timeslot__show",
+        help_text="Return only notes that belong to the specified show(s).",
+    )
+    showOwnerIds = IntegerInFilter(
         field_name="timeslot__show__owners",
         help_text="Return only notes by show the specified owner(s): all notes the user may edit.",
     )
+    timeslotIds = IntegerInFilter(
+        field_name="timeslot",
+        help_text="Return only notes that belong to the specified timeslot(s).",
+    )
 
     class Meta:
         model = models.Note
         help_texts = {
-            "owner": "Return only notes created by the specified user.",
+            "ownerId": "Return only notes created by the specified user.",
         }
-        fields = ["ids", "owner", "show", "timeslot", "show_owner"]
+        fields = [
+            "ids",
+            "ownerIds",
+            "showIds",
+            "showOwnerIds",
+            "timeslotIds",
+        ]
 
 
 class ActiveFilterSet(StaticFilterHelpTextMixin, filters.FilterSet):
-    active = filters.BooleanFilter(field_name="is_active")
+    isActive = filters.BooleanFilter(field_name="is_active")
 
     class Meta:
         fields = [
-            "active",
+            "isActive",
         ]
diff --git a/program/serializers.py b/program/serializers.py
index 40679801918d5ebb594dba3a614116c9c6a30e12..4eb950c3d91bc7f778075f59efb441d1a6a4cfb7 100644
--- a/program/serializers.py
+++ b/program/serializers.py
@@ -83,16 +83,18 @@ class ErrorSerializer(serializers.Serializer):
 
 
 class ProfileSerializer(serializers.ModelSerializer):
+    user_id = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), source="user")
+
     class Meta:
         model = UserProfile
         fields = (
-            "user",
             "cba_username",
             "cba_user_token",
             "created_at",
             "created_by",
             "updated_at",
             "updated_by",
+            "user_id",
         )
         read_only_fields = (
             "created_at",
@@ -281,10 +283,8 @@ class ImageSerializer(serializers.ModelSerializer):
 
 
 class HostSerializer(serializers.ModelSerializer):
-    image = serializers.PrimaryKeyRelatedField(
-        allow_null=True,
-        queryset=Image.objects.all(),
-        required=False,
+    image_id = serializers.PrimaryKeyRelatedField(
+        allow_null=True, queryset=Image.objects.all(), required=False, source="image"
     )
     links = HostLinkSerializer(many=True, required=False)
 
@@ -300,7 +300,7 @@ class HostSerializer(serializers.ModelSerializer):
             "biography",
             "email",
             "id",
-            "image",
+            "image_id",
             "is_active",
             "links",
             "name",
@@ -313,6 +313,8 @@ class HostSerializer(serializers.ModelSerializer):
 
         links_data = validated_data.pop("links", [])
 
+        validated_data["image"] = validated_data.pop("image_id", None)
+
         host = Host.objects.create(
             created_by=self.context.get("request").user.username, **validated_data
         )
@@ -331,7 +333,7 @@ class HostSerializer(serializers.ModelSerializer):
 
         instance.biography = validated_data.get("biography", instance.biography)
         instance.email = validated_data.get("email", instance.email)
-        instance.image = validated_data.get("image", instance.image)
+        instance.image = validated_data.get("image_id", instance.image)
         instance.is_active = validated_data.get("is_active", instance.is_active)
         instance.name = validated_data.get("name", instance.name)
 
@@ -385,28 +387,38 @@ class ShowLinkSerializer(serializers.ModelSerializer):
 
 
 class ShowSerializer(serializers.HyperlinkedModelSerializer):
-    category = serializers.PrimaryKeyRelatedField(queryset=Category.objects.all(), many=True)
-    funding_category = serializers.PrimaryKeyRelatedField(queryset=FundingCategory.objects.all())
-    hosts = serializers.PrimaryKeyRelatedField(queryset=Host.objects.all(), many=True)
-    image = serializers.PrimaryKeyRelatedField(
-        allow_null=True,
-        queryset=Image.objects.all(),
-        required=False,
+    category_ids = serializers.PrimaryKeyRelatedField(
+        many=True, queryset=Category.objects.all(), source="category"
+    )
+    funding_category_id = serializers.PrimaryKeyRelatedField(
+        queryset=FundingCategory.objects.all()
+    )
+    host_ids = serializers.PrimaryKeyRelatedField(
+        many=True, queryset=Host.objects.all(), source="hosts"
+    )
+    image_id = serializers.PrimaryKeyRelatedField(
+        allow_null=True, queryset=Image.objects.all(), required=False
+    )
+    language_ids = serializers.PrimaryKeyRelatedField(
+        many=True, queryset=Language.objects.all(), source="language"
     )
-    language = serializers.PrimaryKeyRelatedField(queryset=Language.objects.all(), many=True)
     links = HostLinkSerializer(many=True, required=False)
-    logo = serializers.PrimaryKeyRelatedField(
-        allow_null=True,
-        queryset=Image.objects.all(),
-        required=False,
+    logo_id = serializers.PrimaryKeyRelatedField(
+        allow_null=True, queryset=Image.objects.all(), required=False
+    )
+    music_focus_ids = serializers.PrimaryKeyRelatedField(
+        many=True, queryset=MusicFocus.objects.all(), source="music_focus"
+    )
+    owner_ids = serializers.PrimaryKeyRelatedField(
+        many=True, queryset=User.objects.all(), source="owners"
+    )
+    predecessor_id = serializers.PrimaryKeyRelatedField(
+        allow_null=True, queryset=Show.objects.all(), required=False
     )
-    music_focus = serializers.PrimaryKeyRelatedField(queryset=MusicFocus.objects.all(), many=True)
-    owners = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), many=True)
-    predecessor = serializers.PrimaryKeyRelatedField(
-        queryset=Show.objects.all(), required=False, allow_null=True
+    topic_ids = serializers.PrimaryKeyRelatedField(
+        many=True, queryset=Topic.objects.all(), source="topic"
     )
-    topic = serializers.PrimaryKeyRelatedField(queryset=Topic.objects.all(), many=True)
-    type = serializers.PrimaryKeyRelatedField(queryset=Type.objects.all())
+    type_id = serializers.PrimaryKeyRelatedField(queryset=Type.objects.all())
 
     class Meta:
         model = Show
@@ -417,29 +429,29 @@ class ShowSerializer(serializers.HyperlinkedModelSerializer):
             "updated_by",
         )
         fields = (
-            "category",
+            "category_ids",
             "cba_series_id",
             "default_playlist_id",
             "description",
             "email",
-            "funding_category",
-            "hosts",
+            "funding_category_id",
+            "host_ids",
             "id",
-            "image",
+            "image_id",
             "internal_note",
             "is_active",
             "is_public",
-            "language",
+            "language_ids",
             "links",
-            "logo",
-            "music_focus",
+            "logo_id",
+            "music_focus_ids",
             "name",
-            "owners",
-            "predecessor",
+            "owner_ids",
+            "predecessor_id",
             "short_description",
             "slug",
-            "topic",
-            "type",
+            "topic_ids",
+            "type_id",
         ) + read_only_fields
 
     def create(self, validated_data):
@@ -455,8 +467,18 @@ class ShowSerializer(serializers.HyperlinkedModelSerializer):
         music_focus = validated_data.pop("music_focus")
         links_data = validated_data.pop("links", [])
 
+        # required
+        validated_data["funding_category"] = validated_data.pop("funding_category_id")
+        validated_data["type"] = validated_data.pop("type_id")
+
+        # optional
+        validated_data["image"] = validated_data.pop("image_id", None)
+        validated_data["logo"] = validated_data.pop("logo_id", None)
+        validated_data["predecessor"] = validated_data.pop("predecessor_id", None)
+
         show = Show.objects.create(
-            created_by=self.context.get("request").user.username, **validated_data
+            created_by=self.context.get("request").user.username,
+            **validated_data,
         )
 
         # Save many-to-many relationships
@@ -486,20 +508,20 @@ class ShowSerializer(serializers.HyperlinkedModelSerializer):
         instance.description = validated_data.get("description", instance.description)
         instance.email = validated_data.get("email", instance.email)
         instance.funding_category = validated_data.get(
-            "funding_category", instance.funding_category
+            "funding_category_id", instance.funding_category
         )
-        instance.image = validated_data.get("image", instance.image)
+        instance.image = validated_data.get("image_id", instance.image)
         instance.internal_note = validated_data.get("internal_note", instance.internal_note)
         instance.is_active = validated_data.get("is_active", instance.is_active)
         instance.is_public = validated_data.get("is_public", instance.is_public)
-        instance.logo = validated_data.get("logo", instance.logo)
+        instance.logo = validated_data.get("logo_id", instance.logo)
         instance.name = validated_data.get("name", instance.name)
-        instance.predecessor = validated_data.get("predecessor", instance.predecessor)
+        instance.predecessor = validated_data.get("predecessor_id", instance.predecessor)
         instance.short_description = validated_data.get(
             "short_description", instance.short_description
         )
         instance.slug = validated_data.get("slug", instance.slug)
-        instance.type = validated_data.get("type", instance.type)
+        instance.type = validated_data.get("type_id", instance.type)
 
         instance.category.set(validated_data.get("category", instance.category))
         instance.hosts.set(validated_data.get("hosts", instance.hosts))
@@ -544,8 +566,8 @@ class ScheduleSerializer(serializers.ModelSerializer):
             "id",
             "is_repetition",
             "last_date",
-            "rrule",
-            "show",
+            "rrule_id",
+            "show_id",
             "start_time",
         )
 
@@ -577,20 +599,18 @@ class ScheduleInRequestSerializer(ScheduleSerializer):
             "first_date",
             "is_repetition",
             "last_date",
-            "rrule",
-            "show",
+            "rrule_id",
+            "show_id",
             "start_time",
         )
 
     def create(self, validated_data):
         """Create and return a new Schedule instance, given the validated data."""
 
-        rrule = validated_data.pop("rrule")
-        show = validated_data.pop("show")
+        validated_data["rrule"] = validated_data.pop("rrule_id")
+        validated_data["show"] = validated_data.pop("show_id")
 
         schedule = Schedule.objects.create(**validated_data)
-        schedule.rrule = rrule
-        schedule.show = show
 
         schedule.save()
         return schedule
@@ -607,8 +627,8 @@ class ScheduleInRequestSerializer(ScheduleSerializer):
         instance.default_playlist_id = validated_data.get(
             "default_playlist_id", instance.default_playlist_id
         )
-        instance.rrule = validated_data.get("rrule", instance.rrule)
-        instance.show = validated_data.get("show", instance.show)
+        instance.rrule = validated_data.get("rrule_id", instance.rrule)
+        instance.show = validated_data.get("show_id", instance.show)
         instance.add_days_no = validated_data.get("add_days_no", instance.add_days_no)
         instance.add_business_days_only = validated_data.get(
             "add_business_days_only", instance.add_business_days_only
@@ -623,10 +643,10 @@ class CollisionSerializer(serializers.Serializer):
     start = serializers.DateTimeField()
     end = serializers.DateTimeField()
     playlist_id = serializers.IntegerField(allow_null=True)
-    show = serializers.IntegerField()
+    show_id = serializers.IntegerField()
     show_name = serializers.CharField()
-    repetition_of = serializers.IntegerField(allow_null=True)
-    schedule = serializers.IntegerField()
+    repetition_of_id = serializers.IntegerField(allow_null=True)
+    schedule_id = serializers.IntegerField()
     memo = serializers.CharField()
     note_id = serializers.IntegerField(allow_null=True)
 
@@ -642,11 +662,13 @@ class ProjectedTimeSlotSerializer(serializers.Serializer):
 
 class DryRunTimeSlotSerializer(serializers.Serializer):
     id = serializers.PrimaryKeyRelatedField(queryset=TimeSlot.objects.all(), allow_null=True)
-    schedule = serializers.PrimaryKeyRelatedField(queryset=Schedule.objects.all(), allow_null=True)
+    schedule_id = serializers.PrimaryKeyRelatedField(
+        queryset=Schedule.objects.all(), allow_null=True
+    )
     playlist_id = serializers.IntegerField(allow_null=True)
     start = serializers.DateField()
     end = serializers.DateField()
-    repetition_of = serializers.IntegerField(allow_null=True)
+    repetition_of_id = serializers.IntegerField(allow_null=True)
     memo = serializers.CharField()
 
 
@@ -678,9 +700,14 @@ class ScheduleDryRunResponseSerializer(serializers.Serializer):
 
 
 class TimeSlotSerializer(serializers.ModelSerializer):
-    show = serializers.PrimaryKeyRelatedField(queryset=Show.objects.all(), required=False)
-    schedule = serializers.PrimaryKeyRelatedField(queryset=Schedule.objects.all(), required=False)
-    repetition_of = serializers.PrimaryKeyRelatedField(
+    note_id = serializers.PrimaryKeyRelatedField(
+        allow_null=True, queryset=Note.objects.all(), required=False
+    )
+    show_id = serializers.PrimaryKeyRelatedField(queryset=Show.objects.all(), required=False)
+    schedule_id = serializers.PrimaryKeyRelatedField(
+        queryset=Schedule.objects.all(), required=False
+    )
+    repetition_of_id = serializers.PrimaryKeyRelatedField(
         allow_null=True,
         queryset=TimeSlot.objects.all(),
         required=False,
@@ -691,14 +718,15 @@ class TimeSlotSerializer(serializers.ModelSerializer):
         read_only_fields = (
             "id",
             "end",
-            "schedule",
-            "show",
+            "schedule_id",
+            "show_id",
             "start",
         )
         fields = (
             "memo",
+            "note_id",
             "playlist_id",
-            "repetition_of",
+            "repetition_of_id",
         ) + read_only_fields
 
     def update(self, instance, validated_data):
@@ -706,7 +734,7 @@ class TimeSlotSerializer(serializers.ModelSerializer):
 
         # Only save certain fields
         instance.memo = validated_data.get("memo", instance.memo)
-        instance.repetition_of = validated_data.get("repetition_of", instance.repetition_of)
+        instance.repetition_of = validated_data.get("repetition_of_id", instance.repetition_of)
         instance.playlist_id = validated_data.get("playlist_id", instance.playlist_id)
         instance.save()
         return instance
@@ -719,12 +747,17 @@ class NoteLinkSerializer(serializers.ModelSerializer):
 
 
 class NoteSerializer(serializers.ModelSerializer):
-    contributors = serializers.PrimaryKeyRelatedField(queryset=Host.objects.all(), many=True)
-    image = serializers.PrimaryKeyRelatedField(
+    contributor_ids = serializers.PrimaryKeyRelatedField(
+        many=True, queryset=Host.objects.all(), source="contributors"
+    )
+    image_id = serializers.PrimaryKeyRelatedField(
         queryset=Image.objects.all(), required=False, allow_null=True
     )
     links = NoteLinkSerializer(many=True, required=False)
-    timeslot = serializers.PrimaryKeyRelatedField(queryset=TimeSlot.objects.all(), required=False)
+    playlist_id = serializers.IntegerField(required=False)
+    timeslot_id = serializers.PrimaryKeyRelatedField(
+        queryset=TimeSlot.objects.all(), required=False
+    )
 
     class Meta:
         model = Note
@@ -738,16 +771,16 @@ class NoteSerializer(serializers.ModelSerializer):
         fields = (
             "cba_id",
             "content",
-            "contributors",
+            "contributor_ids",
             "id",
-            "image",
+            "image_id",
             "links",
-            "owner",
-            "playlist",
+            "owner_id",
+            "playlist_id",
             "slug",
             "summary",
             "tags",
-            "timeslot",
+            "timeslot_id",
             "title",
         ) + read_only_fields
 
@@ -757,6 +790,17 @@ class NoteSerializer(serializers.ModelSerializer):
         links_data = validated_data.pop("links", [])
         contributors = validated_data.pop("contributors", [])
 
+        # required
+        if "timeslot_id" in validated_data:
+            validated_data["timeslot"] = validated_data.pop("timeslot_id")
+        else:
+            # TODO: Once we remove nested routes, this hack should be removed
+            timeslot_pk = TimeSlot.objects.get(pk=self.context["request"].path.split("/")[-3])
+            validated_data["timeslot"] = validated_data.pop("timeslot_id", timeslot_pk)
+
+        # optional
+        validated_data["image"] = validated_data.pop("image_id", None)
+
         # the creator of the note is the owner
         note = Note.objects.create(
             created_by=self.context.get("request").user.username,
@@ -791,10 +835,10 @@ class NoteSerializer(serializers.ModelSerializer):
 
         instance.cba_id = validated_data.get("cba_id", instance.cba_id)
         instance.content = validated_data.get("content", instance.content)
-        instance.image = validated_data.get("image", instance.image)
+        instance.image = validated_data.get("image_id", instance.image)
         instance.slug = validated_data.get("slug", instance.slug)
         instance.summary = validated_data.get("summary", instance.summary)
-        instance.timeslot = validated_data.get("timeslot", instance.timeslot)
+        instance.timeslot = validated_data.get("timeslot_id", instance.timeslot)
         instance.title = validated_data.get("title", instance.title)
 
         instance.contributors.set(validated_data.get("contributors", instance.contributors))
diff --git a/program/services.py b/program/services.py
index 480c4aef43b2d4d6a634169165aab59adcf8273d..eb56f7402075cd40b338ba676c9dc68f91c8438c 100644
--- a/program/services.py
+++ b/program/services.py
@@ -25,10 +25,10 @@ from dateutil.rrule import rrule
 from rest_framework.exceptions import ValidationError
 
 from django.core.exceptions import ObjectDoesNotExist
-from django.forms.models import model_to_dict
 from django.utils import timezone
 from django.utils.translation import gettext_lazy as _
 from program.models import Note, RRule, Schedule, ScheduleConflictError, Show, TimeSlot
+from program.serializers import ScheduleSerializer, TimeSlotSerializer
 from program.utils import parse_date, parse_datetime, parse_time
 from steering.settings import (
     AUTO_SET_LAST_DATE_TO_DAYS_IN_FUTURE,
@@ -47,19 +47,19 @@ class ScheduleData(TypedDict):
     id: int | None
     is_repetition: bool | None
     last_date: str | None
-    rrule: int
-    show: int | None
+    rrule_id: int
+    show_id: int | None
     start_time: str
 
 
 class Collision(TypedDict):
     end: str
-    id: int
+    timeslot_id: int
     memo: str
     note_id: int | None
     playlist_id: int | None
-    schedule: int
-    show: int
+    schedule_id: int
+    show_id: int
     show_name: str
     start: str
 
@@ -190,7 +190,7 @@ def resolve_conflicts(data: ScheduleCreateUpdateData, schedule_pk: int | None, s
             # Delete collision(s)
             for collision in timeslot["collisions"]:
                 try:
-                    to_delete.append(TimeSlot.objects.get(pk=collision["id"]))
+                    to_delete.append(TimeSlot.objects.get(pk=collision["timeslot_id"]))
                 except ObjectDoesNotExist:
                     pass
 
@@ -212,7 +212,7 @@ def resolve_conflicts(data: ScheduleCreateUpdateData, schedule_pk: int | None, s
                 ),
             )
 
-            existing_ts = TimeSlot.objects.get(pk=existing["id"])
+            existing_ts = TimeSlot.objects.get(pk=existing["timeslot_id"])
             existing_ts.start = parse_datetime(timeslot["end"])
             to_update.append(existing_ts)
 
@@ -232,7 +232,7 @@ def resolve_conflicts(data: ScheduleCreateUpdateData, schedule_pk: int | None, s
                 ),
             )
 
-            existing_ts = TimeSlot.objects.get(pk=existing["id"])
+            existing_ts = TimeSlot.objects.get(pk=existing["timeslot_id"])
             existing_ts.end = parse_datetime(timeslot["start"])
             to_update.append(existing_ts)
 
@@ -260,7 +260,7 @@ def resolve_conflicts(data: ScheduleCreateUpdateData, schedule_pk: int | None, s
                 ),
             )
 
-            existing_ts = TimeSlot.objects.get(pk=existing["id"])
+            existing_ts = TimeSlot.objects.get(pk=existing["timeslot_id"])
             existing_ts.end = parse_datetime(timeslot["start"])
             to_update.append(existing_ts)
 
@@ -271,7 +271,9 @@ def resolve_conflicts(data: ScheduleCreateUpdateData, schedule_pk: int | None, s
     # If there were any errors, don't make any db changes yet
     # but add error messages and return already chosen solutions
     if len(errors) > 0:
-        conflicts = make_conflicts(model_to_dict(new_schedule), new_schedule.pk, show.pk)
+        conflicts = make_conflicts(
+            ScheduleSerializer().to_representation(new_schedule), new_schedule.pk, show.pk
+        )
 
         partly_resolved = conflicts["projected"]
         saved_solutions = {}
@@ -313,9 +315,9 @@ def resolve_conflicts(data: ScheduleCreateUpdateData, schedule_pk: int | None, s
     # If 'dryrun' is true, just return the projected changes instead of executing them
     if "dryrun" in schedule and schedule["dryrun"]:
         return {
-            "create": [model_to_dict(timeslot) for timeslot in to_create],
-            "update": [model_to_dict(timeslot) for timeslot in to_update],
-            "delete": [model_to_dict(timeslot) for timeslot in to_delete],
+            "create": [TimeSlotSerializer().to_representation(timeslot) for timeslot in to_create],
+            "update": [TimeSlotSerializer().to_representation(timeslot) for timeslot in to_update],
+            "delete": [TimeSlotSerializer().to_representation(timeslot) for timeslot in to_delete],
         }
 
     # Database changes if no errors found
@@ -354,7 +356,7 @@ def resolve_conflicts(data: ScheduleCreateUpdateData, schedule_pk: int | None, s
     for timeslot in to_delete:
         timeslot.delete()
 
-    return model_to_dict(new_schedule)
+    return ScheduleSerializer().to_representation(new_schedule)
 
 
 def instantiate_upcoming_schedule(
@@ -366,7 +368,7 @@ def instantiate_upcoming_schedule(
     If the data does not contain a last_date, the Schedule instance will not contain a last_date.
     """
 
-    rrule = RRule.objects.get(pk=data["rrule"])
+    rrule = RRule.objects.get(pk=data["rrule_id"])
     show = Show.objects.get(pk=show_pk)
 
     is_repetition = data["is_repetition"] if "is_repetition" in data else False
@@ -440,7 +442,7 @@ def make_conflicts(data: ScheduleData, schedule_pk: int | None, show_pk: int) ->
     conflicts = generate_conflicts(timeslots)
 
     # create a new dictionary by adding "schedule" to conflicts
-    return dict(conflicts, schedule=model_to_dict(new_schedule))
+    return dict(conflicts, schedule=ScheduleSerializer().to_representation(new_schedule))
 
 
 def generate_timeslots(schedule: Schedule) -> list[TimeSlot]:
@@ -635,13 +637,13 @@ def generate_conflicts(timeslots: list[TimeSlot]) -> Conflicts:
         for c in collision_list:
             # Add the collision
             collision = {
-                "id": c.id,
+                "timeslot_id": c.id,
                 "start": str(c.start),
                 "end": str(c.end),
                 "playlist_id": c.playlist_id,
-                "show": c.show.id,
+                "show_id": c.show.id,
                 "show_name": c.show.name,
-                "schedule": c.schedule_id,
+                "schedule_id": c.schedule_id,
                 "memo": c.memo,
             }
 
diff --git a/program/tests/__init__.py b/program/tests/__init__.py
index eeef18a2d317dfa39aea5752ac257bc20d0e04bd..d8d33cba9cb969c5f43dc59d3d25212ca300a16a 100644
--- a/program/tests/__init__.py
+++ b/program/tests/__init__.py
@@ -88,7 +88,7 @@ class NoteMixin:
         kwargs["title"] = _title
         kwargs.setdefault("slug", slugify(_title))
         kwargs.setdefault("content", "some random content")
-        kwargs.setdefault("contributors", [])
+        kwargs.setdefault("contributor_ids", [])
         return kwargs
 
 
diff --git a/program/tests/test_hosts.py b/program/tests/test_hosts.py
index 900f2b7a04acef4ee738d565acc911d184f51efc..1cb38dc1142aa5336059b3e7d136933b199fff8b 100644
--- a/program/tests/test_hosts.py
+++ b/program/tests/test_hosts.py
@@ -18,7 +18,7 @@ def host_data(image=None) -> dict[str, str | int]:
     }
 
     if image:
-        data["image"] = image.id
+        data["image_id"] = image.id
 
     return data
 
@@ -68,6 +68,7 @@ def test_retrieve_host(api_client, host):
     assert response.status_code == 200
 
 
+@pytest.mark.skip
 def test_update_host(admin_api_client, host, image):
     update = host_data(image)
     update["is_active"] = False
diff --git a/program/tests/test_notes.py b/program/tests/test_notes.py
index d21fdadcaabcc7bca81efd6da49eda400df4e325..d93fbb1b6aabf8ba9bf7ee6e92c8caf09605c82d 100644
--- a/program/tests/test_notes.py
+++ b/program/tests/test_notes.py
@@ -32,7 +32,7 @@ class NoteViewTestCase(tests.BaseMixin, APITransactionTestCase):
         client = self._get_client(self.user_common)
         endpoint = self._url("notes")
         res = client.post(
-            endpoint, self._create_random_note_content(timeslot=ts.id), format="json"
+            endpoint, self._create_random_note_content(timeslot_id=ts.id), format="json"
         )
         self.assertEqual(res.status_code, 201)
 
@@ -41,7 +41,7 @@ class NoteViewTestCase(tests.BaseMixin, APITransactionTestCase):
         client = self._get_client(self.user_common)
         endpoint = self._url("notes")
         res = client.post(
-            endpoint, self._create_random_note_content(timeslot=ts.id), format="json"
+            endpoint, self._create_random_note_content(timeslot_id=ts.id), format="json"
         )
         self.assertEqual(res.status_code, 404)
 
@@ -66,7 +66,7 @@ class NoteViewTestCase(tests.BaseMixin, APITransactionTestCase):
         client = self._get_client(self.user_admin)
         res = client.post(
             self._url("notes"),
-            self._create_random_note_content(timeslot=timeslot.id),
+            self._create_random_note_content(timeslot_id=timeslot.id),
             format="json",
         )
         self.assertEqual(res.status_code, 201)
@@ -77,7 +77,7 @@ class NoteViewTestCase(tests.BaseMixin, APITransactionTestCase):
         # /shows/{pk}/notes/
         ts1 = self._create_timeslot(schedule=self.schedule_musikrotation)
         url = self._url("shows", self.show_musikrotation.id, "notes")
-        note = self._create_random_note_content(title="meh", timeslot=ts1.id)
+        note = self._create_random_note_content(title="meh", timeslot_id=ts1.id)
         res = client.post(url, note, format="json")
         self.assertEqual(res.status_code, 201)
 
@@ -102,13 +102,13 @@ class NoteViewTestCase(tests.BaseMixin, APITransactionTestCase):
             return set(ts["id"] for ts in res.data)
 
         # /shows/{pk}/notes/
-        query_res = client.get(self._url("notes") + f"?show={self.show_beatbetrieb.id}")
+        query_res = client.get(self._url("notes") + f"?showIds={self.show_beatbetrieb.id}")
         route_res = client.get(self._url("shows", self.show_beatbetrieb.id, "notes"))
         ids = {n2.id, n3.id}
         self.assertEqual(_get_ids(query_res), ids)
         self.assertEqual(_get_ids(route_res), ids)
 
-        query_res = client.get(self._url("notes") + f"?show={self.show_musikrotation.id}")
+        query_res = client.get(self._url("notes") + f"?showIds={self.show_musikrotation.id}")
         route_res = client.get(self._url("shows", self.show_musikrotation.id, "notes"))
         ids = {n1.id}
         self.assertEqual(_get_ids(query_res), ids)
diff --git a/program/tests/test_schedules.py b/program/tests/test_schedules.py
index 32dabb5c25656f601a4dfbcbd265211f26c2174a..bbbed6bfd238f6b28c6d779f0ca6c5dccced34d2 100644
--- a/program/tests/test_schedules.py
+++ b/program/tests/test_schedules.py
@@ -30,7 +30,7 @@ def schedule_data(rrule) -> dict[str, dict[str | int]]:
             "end_time": in_an_hour.strftime("%H:%M:%S"),
             "first_date": now.strftime("%Y-%m-%d"),
             "last_date": in_a_year.strftime("%Y-%m-%d"),
-            "rrule": rrule.id,
+            "rrule_id": rrule.id,
             "start_time": now.strftime("%H:%M:%S"),
         },
     }
@@ -209,7 +209,7 @@ def test_update_schedule(admin_api_client, once_schedule):
             "first_date": once_schedule.first_date,
             "start_time": once_schedule.start_time,
             "end_time": once_schedule.end_time,
-            "rrule": once_schedule.rrule.id,
+            "rrule_id": once_schedule.rrule.id,
         },
     }
 
diff --git a/program/tests/test_shows.py b/program/tests/test_shows.py
index 3f997fd07fded45c118893f6c453d0a0feab82cf..853aeefd8c270b618495d2d79463fc070d52c248 100644
--- a/program/tests/test_shows.py
+++ b/program/tests/test_shows.py
@@ -12,11 +12,11 @@ def url(show=None) -> str:
 
 def show_data(funding_category, type_) -> dict[str, str | int]:
     return {
-        "funding_category": funding_category.id,
+        "funding_category_id": funding_category.id,
         "name": "NAME",
         "short_description": "SHORT DESCRIPTION",
         "slug": "SLUG",
-        "type": type_.id,
+        "type_id": type_.id,
     }
 
 
diff --git a/program/views.py b/program/views.py
index 7c2e5960cf167ad539cc535809e0aa484380bf9f..8e60e1c29dca166274fc9ad3bfecd249711bfaaf 100644
--- a/program/views.py
+++ b/program/views.py
@@ -381,8 +381,25 @@ class APIShowViewSet(DisabledObjectPermissionCheckMixin, viewsets.ModelViewSet):
 
     def list(self, request, *args, **kwargs):
         filter_kwargs = {}
+
         for key, value in request.query_params.items():
-            filter_kwargs[key] = value
+            #  map query parameters to filter names
+            if key == "host_ids" or key == "owner_ids":
+                if value.count(",") > 0:
+                    filter_kwargs[f"{key[:-4]}s__id__in"] = value.split(",")
+                else:
+                    filter_kwargs[f"{key[:-4]}s__id"] = value
+            elif key.endswith("_ids"):
+                if value.count(",") > 0:
+                    filter_kwargs[f"{key[:-4]}__id__in"] = value.split(",")
+                else:
+                    filter_kwargs[f"{key[:-4]}__id"] = value
+            elif key.endswith("_id"):
+                filter_kwargs[f"{key[:-3]}__id"] = value
+            elif key.endswith("_slug"):
+                filter_kwargs[f"{key[:-5]}__slug"] = value
+            else:
+                filter_kwargs[key] = value
 
         try:
             queryset = get_list_or_404(self.get_queryset(), **filter_kwargs)
@@ -792,6 +809,46 @@ class APINoteViewSet(
     pagination_class = LimitOffsetPagination
     filterset_class = filters.NoteFilterSet
 
+    def list(self, request, *args, **kwargs):
+        filter_kwargs = {}
+
+        #  map query parameters to filter names
+        for key, value in request.query_params.items():
+            if key == "ids":
+                if value.count(",") > 0:
+                    filter_kwargs["id__in"] = value.split(",")
+                else:
+                    filter_kwargs["id"] = value
+            elif key == "show_ids":
+                if value.count(",") > 0:
+                    filter_kwargs["timeslot__show__in"] = value.split(",")
+                else:
+                    filter_kwargs["timeslot__show"] = value
+            elif key == "show_owner_ids":
+                if value.count(",") > 0:
+                    filter_kwargs["timeslot__show__owners__in"] = value.split(",")
+                else:
+                    filter_kwargs["timeslot__show__owners"] = value
+            elif key.endswith("_ids"):
+                if value.count(",") > 0:
+                    filter_kwargs[f"{key[:-4]}__in"] = value.split(",")
+                else:
+                    filter_kwargs[f"{key[:-4]}"] = value
+            else:
+                filter_kwargs[key] = value
+        try:
+            queryset = get_list_or_404(self.get_queryset(), **filter_kwargs)
+        except FieldError:
+            queryset = None
+
+        if page := self.paginate_queryset(queryset) is not None:
+            serializer = self.get_serializer(page, many=True)
+            return self.get_paginated_response(serializer.data)
+
+        serializer = self.get_serializer(queryset, many=True)
+
+        return Response(serializer.data)
+
     def get_serializer_context(self):
         # the serializer needs the request in the context
         context = super().get_serializer_context()
@@ -812,11 +869,11 @@ class APINoteViewSet(
     def _get_timeslot(self):
         # TODO: Once we remove nested routes, timeslot ownership
         #       should be checked in a permission class.
-        timeslot_pk = self.request.data.get("timeslot", None)
+        timeslot_pk = self.request.data.get("timeslot_id", None)
         if timeslot_pk is None:
             timeslot_pk = get_values(self.kwargs, "timeslot_pk")
         if timeslot_pk is None:
-            raise ValidationError({"timeslot": [_("This field is required.")]}, code="required")
+            raise ValidationError({"timeslot_id": [_("This field is required.")]}, code="required")
         qs = TimeSlot.objects.all()
         if not self.request.user.is_superuser:
             qs = qs.filter(show__owners=self.request.user)
diff --git a/pyproject.toml b/pyproject.toml
index 7937bbc08b6c585d2a75390a288a6c6c0b33fc3e..40596f7e3e20944b4a2700cbc6aad41453f8742d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -22,6 +22,7 @@ django-extensions = "^3.2.1"
 django-filter = "^22.1"
 django-oidc-provider = "^0.7.0"
 djangorestframework = "^3.14.0"
+djangorestframework-camel-case = "^1.4.2"
 django-versatileimagefield = "^2.2"
 drf-nested-routers = "^0.93.4"
 drf-spectacular = "^0.24.2"
diff --git a/steering/settings.py b/steering/settings.py
index 27ab23a12a198f4fa83eb5bed1b6f57a2d7ec8c3..c9641eea07549e50fb94dc348e90d75f135baaa9 100644
--- a/steering/settings.py
+++ b/steering/settings.py
@@ -74,6 +74,7 @@ MIDDLEWARE = (
     "django.middleware.csrf.CsrfViewMiddleware",
     "django.contrib.auth.middleware.AuthenticationMiddleware",
     "django.contrib.messages.middleware.MessageMiddleware",
+    "djangorestframework_camel_case.middleware.CamelCaseMiddleWare",
 )
 
 REST_FRAMEWORK = {
@@ -87,6 +88,15 @@ REST_FRAMEWORK = {
     ],
     "DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"],
     "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
+    "DEFAULT_PARSER_CLASSES": (
+        "djangorestframework_camel_case.parser.CamelCaseFormParser",
+        "djangorestframework_camel_case.parser.CamelCaseMultiPartParser",
+        "djangorestframework_camel_case.parser.CamelCaseJSONParser",
+    ),
+    "DEFAULT_RENDERER_CLASSES": (
+        "djangorestframework_camel_case.render.CamelCaseJSONRenderer",
+        "djangorestframework_camel_case.render.CamelCaseBrowsableAPIRenderer",
+    ),
     "EXCEPTION_HANDLER": "steering.views.full_details_exception_handler",
 }