diff --git a/pount/apps/api/signals.py b/pount/apps/api/signals.py
index 686dbad5d96e5186848bfdf87212cc6012e6cead..3b1d094848095c84dfa495c0741408934628ec41 100644
--- a/pount/apps/api/signals.py
+++ b/pount/apps/api/signals.py
@@ -5,7 +5,6 @@ from django.dispatch import receiver
 from pount.apps.api import models
 from pount.apps.api.libs.referential import clear_cache_for_referential, delete_referential_in_mongodb
 from pount.apps.api.models.template import DELETED_SUFFIX
-from pount.apps.api.tasks import clean_iiif_media, ensure_media_has_iiif
 
 pre_soft_delete = django.dispatch.Signal()
 
@@ -52,15 +51,3 @@ def _create_project_group_and_permission(project, group_name, role_name):
         content_object=project,
         role=models.permission.Role.objects.get(name=role_name),
     )
-
-
-@receiver(post_save, sender=models.item.MediaFile)
-def create_iiif_task(sender, instance: models.item.MediaFile, **kwargs):
-    if instance.component == "Image":
-        ensure_media_has_iiif.delay_on_commit(instance.id)
-
-
-@receiver(post_delete, sender=models.item.MediaFile)
-def remove_iiif_file(sender, instance: models.item.MediaFile, **kwargs):
-    if instance.component == "Image":
-        clean_iiif_media.delay_on_commit(str(instance.id))
diff --git a/pount/apps/api/tasks.py b/pount/apps/api/tasks.py
index 4dedc1cabc870168d550e3790ff401d45444fef4..8359b82fee816142a9ef6243f9af03939cbc9285 100644
--- a/pount/apps/api/tasks.py
+++ b/pount/apps/api/tasks.py
@@ -1,5 +1,4 @@
 import logging
-from typing import Union
 from uuid import UUID
 
 from celery import shared_task
@@ -32,76 +31,3 @@ def handle_update_item_metadata_on_template_save(template_id: UUID) -> None:
     from pount.apps.api.libs.template import update_items_using_template
 
     update_items_using_template(template=template_id)
-
-
-@shared_task
-def clean_iiif_media(media_id: Union[str, UUID]) -> None:
-    from pount.apps.api.storage_backends import IIIFFileStorage
-
-    iiif_storage = IIIFFileStorage()
-    tiled_tif = f"{media_id}.tiled.tif"
-    iiif_storage.delete(tiled_tif)
-    logger.debug(f"deleted {tiled_tif}")
-
-
-@shared_task
-def ensure_media_has_iiif(media_id: Union[str, UUID]) -> None:
-    import os
-    import subprocess
-    import tempfile
-    from pathlib import Path
-    from typing import Union
-
-    import requests
-
-    from pount.apps.api.libs.file import item_mediafile_fullname
-    from pount.apps.api.models.item import MediaFile
-    from pount.apps.api.storage_backends import IIIFFileStorage, ItemMediaFileStorage
-
-    def pic_to_tiled_tiff(source: Union[str, Path], destination: Union[str, Path], dpi: int = 300):
-        tif_source = f"{source}.delme.tif"
-        _, extension = os.path.splitext(source)
-        if extension.lower() in [".pdf", ".ai"]:
-            pdftoppm_dest = f"{source}.delme"  # pdftoppm adds .tif extension already
-            subprocess.run(["pdftoppm", "-singlefile", "-tiff", source, "-r", str(dpi), pdftoppm_dest])
-        else:
-            subprocess.run(["convert", "-quiet", "-auto-orient", source, tif_source])
-        subprocess.run(
-            [
-                "vips",
-                "im_vips2tiff",
-                tif_source,
-                f"{destination}:deflate,tile:256x256,pyramid",
-            ]
-        )
-        os.remove(tif_source)
-
-    media_file: MediaFile = MediaFile.objects.filter(id=media_id).first()
-
-    if not media_file:
-        logger.debug(f"no such media file: {media_id}")
-        return
-
-    item_media_storage = ItemMediaFileStorage()
-    full_name = item_mediafile_fullname(media_file.filename, media_file.item)
-
-    if not item_media_storage.exists(full_name):
-        logger.debug(f"media file not on storage: {media_id} / {full_name}")
-        return
-
-    with tempfile.TemporaryDirectory() as tmp_dirname:
-        logger.debug(f"Start tiling in {tmp_dirname}")
-        response = requests.get(media_file.url, stream=True)
-        if response.ok:
-            tmp_file_name = Path(tmp_dirname, media_file.filename)
-            basename, _ = os.path.splitext(tmp_file_name)
-            with open(tmp_file_name, "wb") as f:
-                for chunk in response.iter_content(chunk_size=1024):
-                    if chunk:
-                        f.write(chunk)
-            tiled_tif = f"{basename}.tiled.tif"
-            pic_to_tiled_tiff(tmp_file_name, tiled_tif)
-            iiif_storage = IIIFFileStorage()
-            with open(tiled_tif, "rb") as f:
-                iiif_storage.save(f"{media_file.id}.tiled.tif", f)
-                logger.debug(f"saved {tiled_tif}")
diff --git a/pount/apps/iiif/apps.py b/pount/apps/iiif/apps.py
index 08c5b2d62d20ff0e24139619bbb08e0a4fbf1607..cf8b217ec3a560b10611479999733d1a22e5d02f 100644
--- a/pount/apps/iiif/apps.py
+++ b/pount/apps/iiif/apps.py
@@ -3,3 +3,6 @@ from django.apps import AppConfig
 
 class IiifAppConfig(AppConfig):
     name = "pount.apps.iiif"
+
+    def ready(self):
+        import pount.apps.iiif.signals  # noqa:F401
diff --git a/pount/apps/iiif/signals.py b/pount/apps/iiif/signals.py
new file mode 100644
index 0000000000000000000000000000000000000000..efeed65192ae2e446ffc1142aa51cf40c37bfad5
--- /dev/null
+++ b/pount/apps/iiif/signals.py
@@ -0,0 +1,17 @@
+from django.db.models.signals import post_delete, post_save
+from django.dispatch import receiver
+
+from pount.apps.api import models
+from pount.apps.iiif.tasks import clean_iiif_media, ensure_media_has_iiif
+
+
+@receiver(post_save, sender=models.item.MediaFile)
+def create_iiif_task(sender, instance: models.item.MediaFile, **kwargs):
+    if instance.component == "Image":
+        ensure_media_has_iiif.delay_on_commit(instance.id)
+
+
+@receiver(post_delete, sender=models.item.MediaFile)
+def remove_iiif_file(sender, instance: models.item.MediaFile, **kwargs):
+    if instance.component == "Image":
+        clean_iiif_media.delay_on_commit(str(instance.id))
diff --git a/pount/apps/iiif/tasks.py b/pount/apps/iiif/tasks.py
new file mode 100644
index 0000000000000000000000000000000000000000..eddc119719217cf3991bcce7fa6e84775adf548d
--- /dev/null
+++ b/pount/apps/iiif/tasks.py
@@ -0,0 +1,80 @@
+import logging
+from typing import Union
+from uuid import UUID
+
+from celery import shared_task
+
+logger = logging.getLogger(__name__)
+
+
+@shared_task
+def clean_iiif_media(media_id: Union[str, UUID]) -> None:
+    from pount.apps.api.storage_backends import IIIFFileStorage
+
+    iiif_storage = IIIFFileStorage()
+    tiled_tif = f"{media_id}.tiled.tif"
+    iiif_storage.delete(tiled_tif)
+    logger.debug(f"deleted {tiled_tif}")
+
+
+@shared_task
+def ensure_media_has_iiif(media_id: Union[str, UUID]) -> None:
+    import os
+    import subprocess
+    import tempfile
+    from pathlib import Path
+    from typing import Union
+
+    import requests
+
+    from pount.apps.api.libs.file import item_mediafile_fullname
+    from pount.apps.api.models.item import MediaFile
+    from pount.apps.api.storage_backends import IIIFFileStorage, ItemMediaFileStorage
+
+    def pic_to_tiled_tiff(source: Union[str, Path], destination: Union[str, Path], dpi: int = 300):
+        tif_source = f"{source}.delme.tif"
+        _, extension = os.path.splitext(source)
+        if extension.lower() in [".pdf", ".ai"]:
+            pdftoppm_dest = f"{source}.delme"  # pdftoppm adds .tif extension already
+            subprocess.run(["pdftoppm", "-singlefile", "-tiff", source, "-r", str(dpi), pdftoppm_dest])
+        else:
+            subprocess.run(["convert", "-quiet", "-auto-orient", source, tif_source])
+        subprocess.run(
+            [
+                "vips",
+                "im_vips2tiff",
+                tif_source,
+                f"{destination}:deflate,tile:256x256,pyramid",
+            ]
+        )
+        os.remove(tif_source)
+
+    media_file: MediaFile = MediaFile.objects.filter(id=media_id).first()
+
+    if not media_file:
+        logger.debug(f"no such media file: {media_id}")
+        return
+
+    item_media_storage = ItemMediaFileStorage()
+    full_name = item_mediafile_fullname(media_file.filename, media_file.item)
+
+    if not item_media_storage.exists(full_name):
+        logger.debug(f"media file not on storage: {media_id} / {full_name}")
+        return
+
+    with tempfile.TemporaryDirectory() as tmp_dirname:
+        logger.debug(f"Start tiling in {tmp_dirname}")
+        response = requests.get(media_file.url, stream=True)
+        if response.ok:
+            tmp_file_name = Path(tmp_dirname, media_file.filename)
+            basename, _ = os.path.splitext(tmp_file_name)
+            with open(tmp_file_name, "wb") as f:
+                for chunk in response.iter_content(chunk_size=1024):
+                    if chunk:
+                        f.write(chunk)
+            tiled_tif = f"{basename}.tiled.tif"
+            pic_to_tiled_tiff(tmp_file_name, tiled_tif)
+            iiif_storage = IIIFFileStorage()
+            with open(tiled_tif, "rb") as f:
+                iiif_storage.save(f"{media_file.id}.tiled.tif", f)
+                logger.debug(f"saved {tiled_tif}")
diff --git a/pount/settings/common.py b/pount/settings/common.py
index dc19a24404a38e33314ce5db566fb283affd3111..b707df131e8156f2d609bbc989f00feba9333565 100644
--- a/pount/settings/common.py
+++ b/pount/settings/common.py
@@ -336,6 +336,7 @@ LOCAL_APPS = [
     "pount.apps.api",
     "pount.apps.public",
     "pount.apps.search",
+    "pount.apps.iiif",
 ]
 
 INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS