diff --git a/swh/core/api/__init__.py b/swh/core/api/__init__.py
--- a/swh/core/api/__init__.py
+++ b/swh/core/api/__init__.py
@@ -436,6 +436,11 @@
         A function with no argument that returns an instance of
         `backend_class`. If unset, defaults to calling `backend_class`
         constructor directly.
+
+    For each method 'do_x()' of the ``backend_factory``, subclasses may implement
+    two methods: ``pre_do_x(self, kw)`` and ``post_do_x(self, ret, kw)`` that will
+    be called respectively before and after ``do_x(**kw)``. ``kw`` is the dict
+    of request parameters, and ``ret`` is the return value of ``do_x(**kw)``.
     """
 
     request_class = BytesRequest
@@ -447,6 +452,9 @@
     """Value of `extra_decoders` passed to `json_loads` or `msgpack_loads`
     to be able to deserialize more object types."""
 
+    method_decorators: List[Callable[[Callable], Callable]] = []
+    """List of decorators to all methods generated from the ``backend_class``."""
+
     def __init__(self, *args, backend_class=None, backend_factory=None, **kwargs):
         super().__init__(*args, **kwargs)
         self.add_backend_class(backend_class, backend_factory)
@@ -466,12 +474,27 @@
     def __add_endpoint(self, meth_name, meth, backend_factory):
         from flask import request
 
-        @self.route("/" + meth._endpoint_path, methods=["POST"])
         @negotiate(MsgpackFormatter, extra_encoders=self.extra_type_encoders)
         @negotiate(JSONFormatter, extra_encoders=self.extra_type_encoders)
         @functools.wraps(meth)  # Copy signature and doc
-        def _f():
+        def f():
             # Call the actual code
+            pre_hook = getattr(self, f"pre_{meth_name}", None)
+            post_hook = getattr(self, f"post_{meth_name}", None)
             obj_meth = getattr(backend_factory(), meth_name)
             kw = decode_request(request, extra_decoders=self.extra_type_decoders)
-            return obj_meth(**kw)
+
+            if pre_hook is not None:
+                pre_hook(kw)
+
+            ret = obj_meth(**kw)
+
+            if post_hook is not None:
+                post_hook(ret, kw)
+
+            return ret
+
+        for decorator in self.method_decorators:
+            f = decorator(f)
+
+        self.route("/" + meth._endpoint_path, methods=["POST"])(f)