diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 464ce2402377ddacbf94a9147aa958ebbd9bdf3d..bf5ccd3615ec12af29cbb647ea867e696296128e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -622,8 +622,8 @@ sanity:
     - cd ${CI_PROJECT_DIR}/Python  # change into the Python directory
     - pip install --user -e '.[test]'  # install the package + test deps
     - make all 2&>1 | tee python-test.log  # this runs all of the Python tests
-    - echo "finished" >> python-test.log # create even an empty file...
-    - cd ${CI_PROJECT_DIR}  # reset the directory    
+    - cd ${CI_PROJECT_DIR}  # reset the directory
+  coverage: '/^TOTAL\s*\d+\s*\d+\s*(.*\%)/'
   artifacts:
     when: always
     expire_in: 1 year
diff --git a/Python/Makefile b/Python/Makefile
index 59a1613fdf9cd1605eb64b675e2e2a82ba85e034..ccc13257fe572b456857657929c41d8199ad6529 100644
--- a/Python/Makefile
+++ b/Python/Makefile
@@ -16,7 +16,7 @@ tests:
 	${PYTHON} -m pytest --cov=corsika tests
 
 flake:
-	${PYTHON} -m flake8 corsika
+	${PYTHON} -m flake8 corsika tests
 
 black:
 	${PYTHON} -m black -t py37 corsika tests
diff --git a/Python/corsika/__init__.py b/Python/corsika/__init__.py
index cf4f1519eae59106d31ae10721fa393f8fb51c8e..f5331efad507aac2016ffe21fd4e35b027dad9b0 100644
--- a/Python/corsika/__init__.py
+++ b/Python/corsika/__init__.py
@@ -1,4 +1,6 @@
 """
+ A Python interface to CORSIKA 8.
+
  (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
 
  This software is distributed under the terms of the GNU General Public
@@ -6,4 +8,9 @@
  the license.
 """
 
+from . import io
+
+# all imported objects
+__all__ = ["io"]
+
 __version__: str = "8.0.0-alpha"
diff --git a/Python/corsika/io/__init__.py b/Python/corsika/io/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..bd8a0d8bc7aedbd071fe40f23925a463104ec434
--- /dev/null
+++ b/Python/corsika/io/__init__.py
@@ -0,0 +1,14 @@
+"""
+ The 'io' module provides for reading CORSIKA8 output files.
+
+ (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
+
+ This software is distributed under the terms of the GNU General Public
+ Licence version 3 (GPL Version 3). See file LICENSE for a full version of
+ the license.
+"""
+
+from .hist import read_hist
+
+# all exported objects
+__all__ = ["read_hist"]
diff --git a/Python/corsika/io/hist.py b/Python/corsika/io/hist.py
new file mode 100644
index 0000000000000000000000000000000000000000..e12dcfd1bc828b1ea759f6051cb45694e1956e79
--- /dev/null
+++ b/Python/corsika/io/hist.py
@@ -0,0 +1,70 @@
+"""
+ This file supports reading boost_histograms from CORSIKA8.
+
+ (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
+
+ This software is distributed under the terms of the GNU General Public
+ Licence version 3 (GPL Version 3). See file LICENSE for a full version of
+ the license.
+"""
+
+import boost_histogram as bh
+import numpy as np
+
+
+def read_hist(filename: str) -> bh.Histogram:
+    """
+    Read a histogram produced with CORSIKA8's `save_hist()` function.
+
+    Parameters
+    ----------
+    filename: str
+        The filename of the .npy file containing the histogram.
+
+    Returns
+    -------
+    hist: bh.Histogram
+        An initialized bh.Histogram instance.
+
+    Throws
+    ------
+    ValueError:
+        If the histogram type is not supported.
+    """
+
+    # load the filenames
+    d = np.load(filename)
+
+    # extract the axis and overflows information
+    axistypes = d["axistypes"].view("c")
+    overflow = d["overflow"]
+    underflow = d["underflow"]
+
+    # this is where we store the axes that we extract from the file.
+    axes = []
+
+    # we now loop over the axes
+    for i, (at, has_overflow, has_underflow) in enumerate(
+        zip(axistypes, overflow, underflow)
+    ):
+
+        # continuous histogram
+        if at == b"c":
+            axes.append(
+                bh.axis.Variable(
+                    d[f"binedges_{i}"], overflow=has_overflow, underflow=has_underflow
+                )
+            )
+        # discrete histogram
+        elif at == b"d":
+            axes.append(bh.axis.IntCategory(d[f"bins_{i}"], growth=(not has_overflow)))
+
+        # and unknown histogram type
+        else:
+            raise ValueError(f"'{at}' is not a valid C8 histogram axistype.")
+
+    # create the histogram and fill it in
+    h = bh.Histogram(*axes)
+    h.view(flow=True)[:] = d["data"]
+
+    return h
diff --git a/Python/setup.cfg b/Python/setup.cfg
index d318235ee166e27b1e147bae61daf5ef95e91f81..2544189cacdaef9ab0c8e4e6dcf730f5bff4b344 100644
--- a/Python/setup.cfg
+++ b/Python/setup.cfg
@@ -59,3 +59,7 @@ ignore_missing_imports = True
 # ignore missing types for matplotlib
 [mypy-matplotlib.*]
 ignore_missing_imports = True
+
+# ignore missing types for boost_histogram
+[mypy-boost_histogram.*]
+ignore_missing_imports = True
diff --git a/Python/setup.py b/Python/setup.py
index 36360358f181b35f8c36c548c3778b92436c0c7e..350fbe9fac598a11fc7a7acc33a53ee6c4be89d3 100644
--- a/Python/setup.py
+++ b/Python/setup.py
@@ -32,7 +32,7 @@ setup(
     keywords=["cosmic ray", "physics", "astronomy", "simulation"],
     packages=["corsika"],
     python_requires=">=3.6*, <4",
-    install_requires=["numpy", "pyyaml",],
+    install_requires=["numpy", "pyyaml", "boost_histogram"],
     extras_require={
         "test": [
             "pytest",
diff --git a/Python/tests/test_hist.py b/Python/tests/test_hist.py
new file mode 100644
index 0000000000000000000000000000000000000000..0ad70b734c0af38baf180987ed97e01de02b0740
--- /dev/null
+++ b/Python/tests/test_hist.py
@@ -0,0 +1,36 @@
+"""
+ Tests for `corsika.io.hist`
+
+ (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
+
+ This software is distributed under the terms of the GNU General Public
+ Licence version 3 (GPL Version 3). See file LICENSE for a full version of
+ the license.
+"""
+import pytest
+
+import corsika
+
+
+def test_corsika_io() -> None:
+    """
+    Test I can corsika.io without a further import.
+    """
+    corsika.io.read_hist
+
+
+def test_corsika_read_hist() -> None:
+    """
+    Check that I can read in the test histograms with `read_hist`.
+    """
+
+    # try and read in a continuous histogram
+
+    # try and read in a discrete histogram
+
+
+def test_corsika_read_hist_fail() -> None:
+    """
+    Check that an exception is thrown when reading
+    an incorrectly formatted histogram.
+    """