diff --git a/changelog.d/17374.feature b/changelog.d/17374.feature
new file mode 100644
index 0000000000000000000000000000000000000000..3321f1894726ad9713763a3d4b1aa95a8450bf75
--- /dev/null
+++ b/changelog.d/17374.feature
@@ -0,0 +1 @@
+Support [MSC4151](https://github.com/matrix-org/matrix-spec-proposals/pull/4151)'s stable report room API.
\ No newline at end of file
diff --git a/synapse/rest/client/reporting.py b/synapse/rest/client/reporting.py
index 97bd5d8c02c2e9bae3c0545673da1d89808a3e2e..949f077035182fee1c852a83932e882000d91a58 100644
--- a/synapse/rest/client/reporting.py
+++ b/synapse/rest/client/reporting.py
@@ -20,11 +20,13 @@
 #
 
 import logging
+import re
 from http import HTTPStatus
 from typing import TYPE_CHECKING, Tuple
 
 from synapse._pydantic_compat import StrictStr
 from synapse.api.errors import AuthError, Codes, NotFoundError, SynapseError
+from synapse.api.urls import CLIENT_API_PREFIX
 from synapse.http.server import HttpServer
 from synapse.http.servlet import (
     RestServlet,
@@ -105,18 +107,17 @@ class ReportEventRestServlet(RestServlet):
 class ReportRoomRestServlet(RestServlet):
     """This endpoint lets clients report a room for abuse.
 
-    Whilst MSC4151 is not yet merged, this unstable endpoint is enabled on matrix.org
-    for content moderation purposes, and therefore backwards compatibility should be
-    carefully considered when changing anything on this endpoint.
-
-    More details on the MSC: https://github.com/matrix-org/matrix-spec-proposals/pull/4151
+    Introduced by MSC4151: https://github.com/matrix-org/matrix-spec-proposals/pull/4151
     """
 
-    PATTERNS = client_patterns(
-        "/org.matrix.msc4151/rooms/(?P<room_id>[^/]*)/report$",
-        releases=[],
-        v1=False,
-        unstable=True,
+    # Cast the Iterable to a list so that we can `append` below.
+    PATTERNS = list(
+        client_patterns(
+            "/rooms/(?P<room_id>[^/]*)/report$",
+            releases=("v3",),
+            unstable=False,
+            v1=False,
+        )
     )
 
     def __init__(self, hs: "HomeServer"):
@@ -126,6 +127,16 @@ class ReportRoomRestServlet(RestServlet):
         self.clock = hs.get_clock()
         self.store = hs.get_datastores().main
 
+        # TODO: Remove the unstable variant after 2-3 releases
+        # https://github.com/element-hq/synapse/issues/17373
+        if hs.config.experimental.msc4151_enabled:
+            self.PATTERNS.append(
+                re.compile(
+                    f"^{CLIENT_API_PREFIX}/unstable/org.matrix.msc4151"
+                    "/rooms/(?P<room_id>[^/]*)/report$"
+                )
+            )
+
     class PostBody(RequestBodyModel):
         reason: StrictStr
 
@@ -153,6 +164,4 @@ class ReportRoomRestServlet(RestServlet):
 
 def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
     ReportEventRestServlet(hs).register(http_server)
-
-    if hs.config.experimental.msc4151_enabled:
-        ReportRoomRestServlet(hs).register(http_server)
+    ReportRoomRestServlet(hs).register(http_server)
diff --git a/tests/rest/client/test_reporting.py b/tests/rest/client/test_reporting.py
index 009deb9cb056c40a2ffbfa5b74b3df6f79ac4990..723553979f7d900b536645c8bc905e798baca625 100644
--- a/tests/rest/client/test_reporting.py
+++ b/tests/rest/client/test_reporting.py
@@ -156,58 +156,31 @@ class ReportRoomTestCase(unittest.HomeserverTestCase):
         self.room_id = self.helper.create_room_as(
             self.other_user, tok=self.other_user_tok, is_public=True
         )
-        self.report_path = (
-            f"/_matrix/client/unstable/org.matrix.msc4151/rooms/{self.room_id}/report"
-        )
+        self.report_path = f"/_matrix/client/v3/rooms/{self.room_id}/report"
 
-    @unittest.override_config(
-        {
-            "experimental_features": {"msc4151_enabled": True},
-        }
-    )
     def test_reason_str(self) -> None:
         data = {"reason": "this makes me sad"}
         self._assert_status(200, data)
 
-    @unittest.override_config(
-        {
-            "experimental_features": {"msc4151_enabled": True},
-        }
-    )
     def test_no_reason(self) -> None:
         data = {"not_reason": "for typechecking"}
         self._assert_status(400, data)
 
-    @unittest.override_config(
-        {
-            "experimental_features": {"msc4151_enabled": True},
-        }
-    )
     def test_reason_nonstring(self) -> None:
         data = {"reason": 42}
         self._assert_status(400, data)
 
-    @unittest.override_config(
-        {
-            "experimental_features": {"msc4151_enabled": True},
-        }
-    )
     def test_reason_null(self) -> None:
         data = {"reason": None}
         self._assert_status(400, data)
 
-    @unittest.override_config(
-        {
-            "experimental_features": {"msc4151_enabled": True},
-        }
-    )
     def test_cannot_report_nonexistent_room(self) -> None:
         """
         Tests that we don't accept event reports for rooms which do not exist.
         """
         channel = self.make_request(
             "POST",
-            "/_matrix/client/unstable/org.matrix.msc4151/rooms/!bloop:example.org/report",
+            "/_matrix/client/v3/rooms/!bloop:example.org/report",
             {"reason": "i am very sad"},
             access_token=self.other_user_tok,
             shorthand=False,