diff --git a/swh/web/api/views/metadata.py b/swh/web/api/views/metadata.py
--- a/swh/web/api/views/metadata.py
+++ b/swh/web/api/views/metadata.py
@@ -4,6 +4,7 @@
 # See top-level LICENSE file for more information
 
 import base64
+import re
 
 import iso8601
 
@@ -135,6 +136,7 @@
         result["metadata_url"] = reverse(
             "api-1-raw-extrinsic-metadata-get",
             url_args={"id": hashutil.hash_to_hex(metadata.id)},
+            query_params={"filename": f"{target}_metadata"},
             request=request,
         )
 
@@ -174,4 +176,16 @@
             "Metadata not found. Use /raw-extrinsic-metadata/swhid/ to access metadata."
         )
 
-    return HttpResponse(metadata[0].metadata, content_type="application/octet-stream")
+    response = HttpResponse(
+        metadata[0].metadata, content_type="application/octet-stream"
+    )
+
+    filename = request.query_params.get("filename")
+    if filename and re.match("[a-zA-Z0-9:._-]+", filename):
+        response["Content-disposition"] = f'attachment; filename="{filename}"'
+    else:
+        # It should always be not-None and match the regexp if the URL was created by
+        # /raw-extrinsic-metadata/swhid/, but we're better safe than sorry.
+        response["Content-disposition"] = "attachment"
+
+    return response
diff --git a/swh/web/tests/api/views/test_metadata.py b/swh/web/tests/api/views/test_metadata.py
--- a/swh/web/tests/api/views/test_metadata.py
+++ b/swh/web/tests/api/views/test_metadata.py
@@ -39,6 +39,10 @@
 
     rv = check_http_get_response(api_client, metadata_url, status_code=200)
     assert rv["Content-Type"] == "application/octet-stream"
+    assert (
+        rv["Content-Disposition"]
+        == f'attachment; filename="{metadata.target}_metadata"'
+    )
     assert rv.content == metadata.metadata