diff --git a/requirements-test.txt b/requirements-test.txt
--- a/requirements-test.txt
+++ b/requirements-test.txt
@@ -1,4 +1,5 @@
 pytest
+pytest-aiohttp
 pytest-postgresql
 dulwich >= 0.18.7
 swh.loader.git >= 0.0.52
diff --git a/swh/vault/api/client.py b/swh/vault/api/client.py
--- a/swh/vault/api/client.py
+++ b/swh/vault/api/client.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2018  The Software Heritage developers
+# Copyright (C) 2016-2020  The Software Heritage developers
 # See the AUTHORS file at the top-level directory of this distribution
 # License: GNU General Public License version 3, or any later version
 # See top-level LICENSE file for more information
@@ -6,10 +6,14 @@
 from swh.model import hashutil
 from swh.core.api import RPCClient
 
+from swh.vault.backend import NotFoundExc
+
 
 class RemoteVaultClient(RPCClient):
     """Client to the Software Heritage vault cache."""
 
+    reraise_exceptions = [NotFoundExc]
+
     # Web API endpoints
 
     def fetch(self, obj_type, obj_id):
diff --git a/swh/vault/api/server.py b/swh/vault/api/server.py
--- a/swh/vault/api/server.py
+++ b/swh/vault/api/server.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2019  The Software Heritage developers
+# Copyright (C) 2016-2020  The Software Heritage developers
 # See the AUTHORS file at the top-level directory of this distribution
 # License: GNU General Public License version 3, or any later version
 # See top-level LICENSE file for more information
@@ -56,7 +56,7 @@
     obj_id = request.match_info["id"]
 
     if not request.app["backend"].is_available(obj_type, obj_id):
-        raise aiohttp.web.HTTPNotFound
+        raise NotFoundExc(f"{obj_type} {obj_id} is not available.")
 
     return encode_data(request.app["backend"].fetch(obj_type, obj_id))
 
@@ -79,14 +79,11 @@
     sticky = request.query.get("sticky") in ("true", "1")
 
     if obj_type not in COOKER_TYPES:
-        raise aiohttp.web.HTTPNotFound
+        raise NotFoundExc(f"{obj_type} is an unknown type.")
 
-    try:
-        info = request.app["backend"].cook_request(
-            obj_type, obj_id, email=email, sticky=sticky
-        )
-    except NotFoundExc:
-        raise aiohttp.web.HTTPNotFound
+    info = request.app["backend"].cook_request(
+        obj_type, obj_id, email=email, sticky=sticky
+    )
 
     # TODO: return 201 status (Created) once the api supports it
     return encode_data(user_info(info))
@@ -99,7 +96,7 @@
 
     info = request.app["backend"].task_info(obj_type, obj_id)
     if not info:
-        raise aiohttp.web.HTTPNotFound
+        raise NotFoundExc(f"{obj_type} {obj_id} was not found.")
 
     return encode_data(user_info(info))
 
@@ -152,7 +149,7 @@
     batch = yield from decode_request(request)
     for obj_type, obj_id in batch:
         if obj_type not in COOKER_TYPES:
-            raise aiohttp.web.HTTPNotFound
+            raise NotFoundExc(f"{obj_type} is an unknown type.")
     batch_id = request.app["backend"].batch_cook(batch)
     return encode_data({"id": batch_id})
 
@@ -162,7 +159,7 @@
     batch_id = request.match_info["batch_id"]
     bundles = request.app["backend"].batch_info(batch_id)
     if not bundles:
-        raise aiohttp.web.HTTPNotFound
+        raise NotFoundExc(f"Batch {batch_id} does not exist.")
     bundles = [user_info(bundle) for bundle in bundles]
     counter = collections.Counter(b["status"] for b in bundles)
     res = {
@@ -180,6 +177,7 @@
 def make_app(backend, **kwargs):
     app = RPCServerApp(**kwargs)
     app.router.add_route("GET", "/", index)
+    app.client_exception_classes = (NotFoundExc,)
 
     # Endpoints used by the web API
     app.router.add_route("GET", "/fetch/{type}/{id}", vault_fetch)
diff --git a/swh/vault/tests/test_server.py b/swh/vault/tests/test_server.py
new file mode 100644
--- /dev/null
+++ b/swh/vault/tests/test_server.py
@@ -0,0 +1,56 @@
+# Copyright (C) 2020  The Software Heritage developers
+# See the AUTHORS file at the top-level directory of this distribution
+# License: GNU General Public License version 3, or any later version
+# See top-level LICENSE file for more information
+
+import pytest
+
+from swh.core.api.serializers import msgpack_loads, msgpack_dumps
+
+from swh.vault.api.server import make_app
+
+
+@pytest.fixture
+def client(swh_vault, loop, aiohttp_client):
+    app = make_app(backend=swh_vault)
+    return loop.run_until_complete(aiohttp_client(app))
+
+
+async def test_index(client):
+    resp = await client.get("/")
+    assert resp.status == 200
+
+
+async def test_cook_notfound(client):
+    resp = await client.post("/cook/directory/000000")
+    assert resp.status == 400
+    content = msgpack_loads(await resp.content.read())
+    assert content["exception"]["type"] == "NotFoundExc"
+    assert content["exception"]["args"] == ["Object 000000 was not found."]
+
+
+async def test_progress_notfound(client):
+    resp = await client.get("/progress/directory/000000")
+    assert resp.status == 400
+    content = msgpack_loads(await resp.content.read())
+    assert content["exception"]["type"] == "NotFoundExc"
+    assert content["exception"]["args"] == ["directory 000000 was not found."]
+
+
+async def test_batch_cook_invalid_type(client):
+    data = msgpack_dumps([("foobar", [])])
+    resp = await client.post(
+        "/batch_cook", data=data, headers={"Content-Type": "application/x-msgpack"}
+    )
+    assert resp.status == 400
+    content = msgpack_loads(await resp.content.read())
+    assert content["exception"]["type"] == "NotFoundExc"
+    assert content["exception"]["args"] == ["foobar is an unknown type."]
+
+
+async def test_batch_progress_notfound(client):
+    resp = await client.get("/batch_progress/1")
+    assert resp.status == 400
+    content = msgpack_loads(await resp.content.read())
+    assert content["exception"]["type"] == "NotFoundExc"
+    assert content["exception"]["args"] == ["Batch 1 does not exist."]