diff --git a/nomad/api.py b/nomad/api.py
index 5893de3006f39866d86803fcd89f2ec558cc2f16..6eedda86d749f00767e77dc2e46ede3e1b33c43f 100644
--- a/nomad/api.py
+++ b/nomad/api.py
@@ -69,8 +69,25 @@ class Upload(Resource):
         return Uploads._render(upload), 200
 
 
+class Calc(Resource):
+    def get(self, upload_hash, calc_hash):
+        archive_id = '%s/%s' % (upload_hash, calc_hash)
+        logger = get_logger(__name__, archive_id=archive_id)
+        try:
+            file = files.open_archive_json(archive_id)
+            return file, 200
+        except KeyError:
+            abort(404, message='Archive %s does not exist.' % archive_id)
+        except Exception as e:
+            logger.error('Exception on reading archive', exc_info=e)
+            abort(500, message='Could not read the archive.')
+        finally:
+            file.close()
+
+
 api.add_resource(Uploads, '/uploads')
 api.add_resource(Upload, '/uploads/<string:upload_id>')
+api.add_resource(Calc, '/archive/<string:upload_hash>/<string:calc_hash>')
 
 
 def start_upload_handler(quit=False):
diff --git a/nomad/files.py b/nomad/files.py
index d5d3fe59cb99fb163671af82e35c0779dd12c5a0..0b76806e6432233aef90e40548d32058ea4a2e5d 100644
--- a/nomad/files.py
+++ b/nomad/files.py
@@ -41,7 +41,7 @@ Uploads
 from typing import Callable, List, Any, Generator, IO, TextIO, cast
 import sys
 import os
-from os.path import join
+import os.path
 from zipfile import ZipFile, BadZipFile
 import shutil
 from minio import Minio
@@ -176,8 +176,8 @@ class Upload():
     """
     def __init__(self, upload_id: str) -> None:
         self.upload_id = upload_id
-        self.upload_file: str = '%s/uploads/%s.zip' % (config.fs.tmp, upload_id)
-        self.upload_extract_dir: str = '%s/uploads_extracted/%s' % (config.fs.tmp, upload_id)
+        self.upload_file: str = os.path.join(config.fs.tmp, 'uploads', upload_id)
+        self.upload_extract_dir: str = os.path.join(config.fs.tmp, 'uploads_extracted', upload_id)
         self.filelist: List[str] = None
 
         try:
@@ -218,6 +218,9 @@ class Upload():
             UploadError: If some IO went wrong.
             KeyError: If the upload does not exist.
         """
+        os.makedirs(os.path.join(config.fs.tmp, 'uploads'), exist_ok=True)
+        os.makedirs(os.path.join(config.fs.tmp, 'uploads_extracted'), exist_ok=True)
+
         try:
             _client.fget_object(config.files.uploads_bucket, self.upload_id, self.upload_file)
         except minio.error.NoSuchKey:
@@ -263,7 +266,7 @@ class Upload():
 
     def get_path(self, filename: str) -> str:
         """ Returns the tmp directory relative version of a filename. """
-        return join(self.upload_extract_dir, filename)
+        return os.path.join(self.upload_extract_dir, filename)
 
 
 @contextmanager
diff --git a/tests/test_api.py b/tests/test_api.py
index 543f5fa98faa3df89a6120667152bcc0a807a0a9..01ceed9e5312f42e66ff04bfe69479f64247ee2b 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -6,11 +6,13 @@ import time
 import json
 from mongoengine import connect
 from mongoengine.connection import disconnect
-from minio.error import ResponseError
 
 from nomad import config, api, files
 
 from tests.test_processing import example_files
+from tests.test_files import assert_exists
+# import fixtures
+from tests.test_files import clear_files  # pylint: disable=unused-import
 
 
 @pytest.fixture
@@ -101,10 +103,7 @@ def test_upload_to_upload(client, file):
 
     handle_uploads_thread.join()
 
-    try:
-        files._client.remove_object(config.files.uploads_bucket, upload['id'])
-    except ResponseError:
-        assert False
+    assert_exists(config.files.uploads_bucket, upload['id'])
 
 
 @pytest.mark.parametrize("file", example_files)
@@ -136,8 +135,4 @@ def test_processing(client, file):
             break
 
     assert upload['processing']['status'] == 'SUCCESS'
-
-    try:
-        files._client.remove_object(config.files.uploads_bucket, upload['id'])
-    except ResponseError:
-        assert False
+    assert_exists(config.files.uploads_bucket, upload['id'])
diff --git a/tests/test_files.py b/tests/test_files.py
index 627a2db3a4bbf660d253d086c7d35a90fc3b94a6..06baf3dd9740b6fb59e28bd9c344a2097774ee88 100644
--- a/tests/test_files.py
+++ b/tests/test_files.py
@@ -13,13 +13,16 @@
 # limitations under the License.
 
 import pytest
-from minio import ResponseError
 from threading import Thread
 import subprocess
 import shlex
 import time
 from typing import Generator
 import json
+import shutil
+import os.path
+from minio import ResponseError
+from minio.error import NoSuchBucket
 
 import nomad.files as files
 import nomad.config as config
@@ -28,36 +31,54 @@ example_file = 'data/examples_vasp.zip'
 empty_file = 'data/empty.zip'
 
 
-@pytest.fixture
-def uploaded_id() -> Generator[str, None, None]:
+def assert_exists(bucket_name, object_name):
+    try:
+        stats = files._client.stat_object(bucket_name, object_name)
+        assert stats is not None
+    except NoSuchBucket:
+        assert False
+    except ResponseError:
+        assert False
+
+
+@pytest.fixture(scope='function')
+def clear_files():
+    """ Utility fixture that removes all files from files and tmp after test. """
+    try:
+        yield
+    finally:
+        try:
+            for bucket in [config.files.uploads_bucket, config.files.archive_bucket]:
+                to_remove = [obj.object_name for obj in files._client.list_objects(bucket)]
+                for _ in files._client.remove_objects(bucket, to_remove):
+                    pass
+        except NoSuchBucket:
+            pass
+        except ResponseError:
+            pass
+
+        shutil.rmtree(os.path.join(config.fs.tmp, 'uploads'), ignore_errors=True)
+        shutil.rmtree(os.path.join(config.fs.tmp, 'uploads_extracted'), ignore_errors=True)
+
+
+@pytest.fixture(scope='function')
+def uploaded_id(clear_files) -> Generator[str, None, None]:
     example_upload_id = '__test_upload_id'
 
     files._client.fput_object(config.files.uploads_bucket, example_upload_id, example_file)
     yield example_upload_id
-    try:
-        files._client.remove_object(config.files.uploads_bucket, example_upload_id)
-    except ResponseError:
-        pass
 
 
-@pytest.fixture
-def upload_id() -> Generator[str, None, None]:
+@pytest.fixture(scope='function')
+def upload_id(clear_files) -> Generator[str, None, None]:
     example_upload_id = '__test_upload_id'
     yield example_upload_id
-    try:
-        files._client.remove_object(config.files.uploads_bucket, example_upload_id)
-    except ResponseError:
-        pass
 
 
-@pytest.fixture
-def archive_id() -> Generator[str, None, None]:
+@pytest.fixture(scope='function')
+def archive_id(clear_files) -> Generator[str, None, None]:
     example_archive_id = '__test_archive_id'
     yield example_archive_id
-    try:
-        files._client.remove_object(config.files.archive_bucket, example_archive_id)
-    except ResponseError:
-        pass
 
 
 def test_presigned_url(upload_id):
diff --git a/tests/test_processing.py b/tests/test_processing.py
index 3404e2b002f9fcce23c07d91c728e4e0cf6693d7..edf586e35d07dc546ee169cf6d2dc554faf769c1 100644
--- a/tests/test_processing.py
+++ b/tests/test_processing.py
@@ -20,16 +20,15 @@ reading from the redis result backend, even though all task apperently ended suc
 
 from typing import Generator
 import pytest
-from minio import ResponseError
-from minio.error import NoSuchBucket
 
 import nomad.config as config
 import nomad.files as files
 from nomad.processing import UploadProcessing
 
-# delete search index after each test via imported fixture
-from tests.test_search import index  # pylint: disable=unused-import
 from tests.test_files import example_file, empty_file
+# import fixtures
+from tests.test_search import index  # pylint: disable=unused-import
+from tests.test_files import clear_files  # pylint: disable=unused-import
 
 example_files = [empty_file, example_file]
 
@@ -58,28 +57,13 @@ def celery_config():
 
 
 @pytest.fixture(scope='function', params=example_files)
-def uploaded_id(request) -> Generator[str, None, None]:
+def uploaded_id(request, clear_files) -> Generator[str, None, None]:
     example_file = request.param
     example_upload_id = example_file.replace('.zip', '')
     files._client.fput_object(config.files.uploads_bucket, example_upload_id, example_file)
 
     yield example_upload_id
 
-    try:
-        # remove the created uploads
-        files._client.remove_object(config.files.uploads_bucket, example_upload_id)
-
-        # remove all the created archive files
-        archive_objs = files._client.list_objects(config.files.archive_bucket, recursive=True)
-        errors = files._client.remove_objects(config.files.archive_bucket, [obj.object_name for obj in archive_objs])
-        # you have to walk the iterator for minio to work (?!)
-        for _ in errors:
-            pass
-    except ResponseError:
-        pass
-    except NoSuchBucket:
-        pass
-
 
 def test_processing(uploaded_id, celery_session_worker):
     run = UploadProcessing(uploaded_id)