diff --git a/swh/deposit/api/private/deposit_list.py b/swh/deposit/api/private/deposit_list.py
index 4bb40e31..e8c07e31 100644
--- a/swh/deposit/api/private/deposit_list.py
+++ b/swh/deposit/api/private/deposit_list.py
@@ -3,10 +3,20 @@
 # License: GNU General Public License version 3, or any later version
 # See top-level LICENSE file for more information
 
-
+from django.core.paginator import Paginator
+from django.db.models import CharField, Q, TextField
+from django.http import JsonResponse
+from rest_framework.decorators import (
+    api_view,
+    authentication_classes,
+    permission_classes,
+)
 from rest_framework.generics import ListAPIView
+from rest_framework.permissions import AllowAny
+import sentry_sdk
 
 from swh.deposit.api.utils import DefaultPagination, DepositSerializer
+from swh.deposit.utils import parse_swh_deposit_origin, parse_swh_metadata_provenance
 
 from . import APIPrivateView
 from ...models import Deposit
@@ -66,3 +76,96 @@ class APIList(ListAPIView, APIPrivateView):
             deposits_qs = deposits_qs.exclude(external_id__startswith=exclude_like)
 
         return deposits_qs.order_by("id")
+
+
+def _deposit_search_query(search_value: str) -> Q:
+    fields = [f for f in Deposit._meta.fields if isinstance(f, (CharField, TextField))]
+    queries = [Q(**{f.name + "__icontains": search_value}) for f in fields]
+    search_query = Q()
+    for query in queries:
+        search_query = search_query | query
+    return search_query
+
+
+@api_view()
+@authentication_classes([])
+@permission_classes([AllowAny])
+def deposit_list_datatables(request):
+    table_data = {}
+    table_data["draw"] = int(request.GET["draw"])
+    try:
+        deposits = Deposit.objects.all()
+        deposits_count = deposits.count()
+        search_value = request.GET["search[value]"]
+        if search_value:
+            deposits = deposits.filter(_deposit_search_query(search_value))
+
+        exclude_pattern = request.GET.get("excludePattern")
+        if exclude_pattern:
+            deposits = deposits.exclude(_deposit_search_query(exclude_pattern))
+
+        column_order = request.GET["order[0][column]"]
+        field_order = request.GET["columns[%s][name]" % column_order]
+        order_dir = request.GET["order[0][dir]"]
+
+        if order_dir == "desc":
+            field_order = "-" + field_order
+
+        deposits = deposits.order_by(field_order)
+
+        length = int(request.GET["length"])
+        page = int(request.GET["start"]) / length + 1
+        paginator = Paginator(deposits, length)
+
+        data = [DepositSerializer(d).data for d in paginator.page(page).object_list]
+
+        table_data["recordsTotal"] = deposits_count
+        table_data["recordsFiltered"] = deposits.count()
+        data_list = []
+        for d in data:
+            data_dict = {
+                "id": d["id"],
+                "type": d["type"],
+                "external_id": d["external_id"],
+                "reception_date": d["reception_date"],
+                "status": d["status"],
+                "status_detail": d["status_detail"],
+                "swhid": d["swhid"],
+                "swhid_context": d["swhid_context"],
+            }
+            provenance = None
+            raw_metadata = d["raw_metadata"]
+            # Try to determine provenance out of the raw metadata
+            if raw_metadata and d["type"] == "meta":  # metadata provenance
+                provenance = parse_swh_metadata_provenance(d["raw_metadata"])
+            elif raw_metadata and d["type"] == "code":
+                provenance = parse_swh_deposit_origin(raw_metadata)
+
+            if not provenance and d["origin_url"]:
+                provenance = d["origin_url"]
+
+            # Finally, if still not found, we determine uri using the swhid
+            if not provenance and d["swhid_context"]:
+                # Trying to compute the origin as we did before in the js
+                from swh.model.swhids import QualifiedSWHID
+
+                swhid = QualifiedSWHID.from_string(d["swhid_context"])
+                provenance = swhid.origin
+
+            data_dict["uri"] = provenance  # could be None
+
+            # This could be large. As this is not displayed yet, drop it to avoid
+            # cluttering the data dict
+            data_dict.pop("raw_metadata", None)
+
+            data_list.append(data_dict)
+
+        table_data["data"] = data_list
+
+    except Exception as exc:
+        sentry_sdk.capture_exception(exc)
+        table_data[
+            "error"
+        ] = "An error occurred while retrieving the list of deposits !"
+
+    return JsonResponse(table_data)
diff --git a/swh/deposit/api/private/urls.py b/swh/deposit/api/private/urls.py
index e48290d6..2243aca1 100644
--- a/swh/deposit/api/private/urls.py
+++ b/swh/deposit/api/private/urls.py
@@ -10,10 +10,11 @@ from ...config import (
     PRIVATE_GET_DEPOSIT_METADATA,
     PRIVATE_GET_RAW_CONTENT,
     PRIVATE_LIST_DEPOSITS,
+    PRIVATE_LIST_DEPOSITS_DATATABLES,
     PRIVATE_PUT_DEPOSIT,
 )
 from .deposit_check import APIChecks
-from .deposit_list import APIList
+from .deposit_list import APIList, deposit_list_datatables
 from .deposit_read import APIReadArchives, APIReadMetadata
 from .deposit_update_status import APIUpdateStatus
 
@@ -75,4 +76,9 @@ urlpatterns = [
         name=PRIVATE_CHECK_DEPOSIT + "-nc",
     ),
     url(r"^deposits/$", APIList.as_view(), name=PRIVATE_LIST_DEPOSITS),
+    url(
+        r"^deposits/datatables/$",
+        deposit_list_datatables,
+        name=PRIVATE_LIST_DEPOSITS_DATATABLES,
+    ),
 ]
diff --git a/swh/deposit/config.py b/swh/deposit/config.py
index 758ecf09..e91a781b 100644
--- a/swh/deposit/config.py
+++ b/swh/deposit/config.py
@@ -27,6 +27,7 @@ PRIVATE_CHECK_DEPOSIT = "check-deposit"
 PRIVATE_PUT_DEPOSIT = "private-update"
 PRIVATE_GET_DEPOSIT_METADATA = "private-read"
 PRIVATE_LIST_DEPOSITS = "private-deposit-list"
+PRIVATE_LIST_DEPOSITS_DATATABLES = "private-deposit-list-datatables"
 
 ARCHIVE_KEY = "archive"
 RAW_METADATA_KEY = "raw-metadata"