diff --git a/core/utils.py b/core/utils.py
index 2f56cebf..d9e8f180 100644
--- a/core/utils.py
+++ b/core/utils.py
@@ -17,6 +17,7 @@ from datetime import date, timedelta
 
 # Image utils
 from io import BytesIO
+from typing import Final
 
 import PIL
 from django.conf import settings
@@ -26,6 +27,19 @@ from django.utils.timezone import localdate
 from PIL import ExifTags
 from PIL.Image import Image, Resampling
 
+RED_PIXEL_PNG: Final[bytes] = (
+    b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52"
+    b"\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90\x77\x53"
+    b"\xde\x00\x00\x00\x0c\x49\x44\x41\x54\x08\xd7\x63\xf8\xcf\xc0\x00"
+    b"\x00\x03\x01\x01\x00\x18\xdd\x8d\xb0\x00\x00\x00\x00\x49\x45\x4e"
+    b"\x44\xae\x42\x60\x82"
+)
+"""A single red pixel, in PNG format.
+
+Can be used in tests and in dev, when there is a need
+to generate a dummy image that is considered valid nonetheless
+"""
+
 
 def get_start_of_semester(today: date | None = None) -> date:
     """Return the date of the start of the semester of the given date.
diff --git a/galaxy/management/commands/generate_galaxy_test_data.py b/galaxy/management/commands/generate_galaxy_test_data.py
index d0dea4a5..563b35e6 100644
--- a/galaxy/management/commands/generate_galaxy_test_data.py
+++ b/galaxy/management/commands/generate_galaxy_test_data.py
@@ -32,17 +32,10 @@ from django.utils import timezone
 
 from club.models import Club, Membership
 from core.models import Group, Page, SithFile, User
+from core.utils import RED_PIXEL_PNG
 from sas.models import Album, PeoplePictureRelation, Picture
 from subscription.models import Subscription
 
-RED_PIXEL_PNG: Final[bytes] = (
-    b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52"
-    b"\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90\x77\x53"
-    b"\xde\x00\x00\x00\x0c\x49\x44\x41\x54\x08\xd7\x63\xf8\xcf\xc0\x00"
-    b"\x00\x03\x01\x01\x00\x18\xdd\x8d\xb0\x00\x00\x00\x00\x49\x45\x4e"
-    b"\x44\xae\x42\x60\x82"
-)
-
 USER_PACK_SIZE: Final[int] = 1000
 
 
diff --git a/sas/api.py b/sas/api.py
index d9e2ad2e..2d6d64ef 100644
--- a/sas/api.py
+++ b/sas/api.py
@@ -1,7 +1,9 @@
 from django.conf import settings
+from django.core.exceptions import ValidationError
 from django.db.models import F
 from django.urls import reverse
-from ninja import Query
+from ninja import Body, Query, UploadedFile
+from ninja.errors import HttpError
 from ninja_extra import ControllerBase, api_controller, paginate, route
 from ninja_extra.exceptions import NotFound, PermissionDenied
 from ninja_extra.pagination import PageNumberPaginationExtra
@@ -9,7 +11,13 @@ from ninja_extra.permissions import IsAuthenticated
 from ninja_extra.schemas import PaginatedResponseSchema
 from pydantic import NonNegativeInt
 
-from core.auth.api_permissions import CanAccessLookup, CanView, IsInGroup, IsRoot
+from core.auth.api_permissions import (
+    CanAccessLookup,
+    CanEdit,
+    CanView,
+    IsInGroup,
+    IsRoot,
+)
 from core.models import Notification, User
 from sas.models import Album, PeoplePictureRelation, Picture
 from sas.schemas import (
@@ -92,6 +100,34 @@ class PicturesController(ControllerBase):
             .annotate(album=F("parent__name"))
         )
 
+    @route.post(
+        "",
+        permissions=[CanEdit],
+        response={200: None, 409: dict[str, list[str]]},
+        url_name="upload_picture",
+    )
+    def upload_picture(self, album_id: Body[int], picture: UploadedFile):
+        album = self.get_object_or_exception(Album, pk=album_id)
+        user = self.context.request.user
+        self_moderate = user.has_perm("sas.moderate_sasfile")
+        new = Picture(
+            parent=album,
+            name=picture.name,
+            file=picture,
+            owner=user,
+            is_moderated=self_moderate,
+            is_folder=False,
+            mime_type=picture.content_type,
+        )
+        if self_moderate:
+            new.moderator = user
+        try:
+            new.generate_thumbnails()
+            new.full_clean()
+            new.save()
+        except ValidationError as e:
+            raise HttpError(status_code=409, message=str(e)) from e
+
     @route.get(
         "/{picture_id}/identified",
         permissions=[IsAuthenticated, CanView],
diff --git a/sas/tests/test_api.py b/sas/tests/test_api.py
index 25014e86..2570a3c6 100644
--- a/sas/tests/test_api.py
+++ b/sas/tests/test_api.py
@@ -1,13 +1,16 @@
+import pytest
 from django.conf import settings
 from django.core.cache import cache
+from django.core.files.uploadedfile import SimpleUploadedFile
 from django.db import transaction
-from django.test import TestCase
+from django.test import Client, TestCase
 from django.urls import reverse
 from model_bakery import baker
 from model_bakery.recipe import Recipe
 
 from core.baker_recipes import old_subscriber_user, subscriber_user
 from core.models import Group, SithFile, User
+from core.utils import RED_PIXEL_PNG
 from sas.baker_recipes import picture_recipe
 from sas.models import Album, PeoplePictureRelation, Picture, PictureModerationRequest
 
@@ -241,3 +244,25 @@ class TestAlbumSearch(TestSas):
             # - 1 for pagination
             # - 1 for the actual results
             self.client.get(reverse("api:search-album"))
+
+
+@pytest.mark.django_db
+def test_upload_picture(client: Client):
+    sas = SithFile.objects.get(pk=settings.SITH_SAS_ROOT_DIR_ID)
+    album = baker.make(Album, is_in_sas=True, parent=sas, name="test album")
+    user = baker.make(User, is_superuser=True)
+    client.force_login(user)
+    img = SimpleUploadedFile(
+        name="img.png", content=RED_PIXEL_PNG, content_type="image/png"
+    )
+    res = client.post(
+        reverse("api:upload_picture"), {"album_id": album.id, "picture": img}
+    )
+    assert res.status_code == 200
+    picture = Picture.objects.filter(parent_id=album.id).first()
+    assert picture is not None
+    assert picture.name == "img.png"
+    assert picture.owner == user
+    assert picture.file.name == "SAS/test album/img.png"
+    assert picture.compressed.name == ".compressed/SAS/test album/img.webp"
+    assert picture.thumbnail.name == ".thumbnails/SAS/test album/img.webp"