diff --git a/.gitignore b/.gitignore
index fc3393b..454f9d9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,18 +1,19 @@
 .eggs/
 /sgloader/__pycache__/
 /dataset/
 *.pyc
 /.coverage
 /.coverage.*
 /scratch/swhgitloader.cProfile
 /scratch/swhgitloader.profile
 /scratch/save.p
 *.egg-info
 version.txt
 /resources/repo-linux-to-load.ini
 /resources/repo-to-load.ini
 build/
 dist/
 .hypothesis
 .pytest_cache
 .tox/
+.mypy_cache/
diff --git a/mypy.ini b/mypy.ini
new file mode 100644
index 0000000..9c0878e
--- /dev/null
+++ b/mypy.ini
@@ -0,0 +1,18 @@
+[mypy]
+namespace_packages = True
+warn_unused_ignores = True
+
+
+# 3rd party libraries without stubs (yet)
+
+[mypy-celery.*]
+ignore_missing_imports = True
+
+[mypy-dulwich.*]
+ignore_missing_imports = True
+
+[mypy-pkg_resources.*]
+ignore_missing_imports = True
+
+[mypy-pytest.*]
+ignore_missing_imports = True
diff --git a/swh/__init__.py b/swh/__init__.py
index 69e3be5..f14e196 100644
--- a/swh/__init__.py
+++ b/swh/__init__.py
@@ -1 +1,4 @@
-__path__ = __import__('pkgutil').extend_path(__path__, __name__)
+from pkgutil import extend_path
+from typing import Iterable
+
+__path__ = extend_path(__path__, __name__)  # type: Iterable[str]
diff --git a/swh/loader/__init__.py b/swh/loader/__init__.py
index 69e3be5..f14e196 100644
--- a/swh/loader/__init__.py
+++ b/swh/loader/__init__.py
@@ -1 +1,4 @@
-__path__ = __import__('pkgutil').extend_path(__path__, __name__)
+from pkgutil import extend_path
+from typing import Iterable
+
+__path__ = extend_path(__path__, __name__)  # type: Iterable[str]
diff --git a/swh/loader/git/py.typed b/swh/loader/git/py.typed
new file mode 100644
index 0000000..1242d43
--- /dev/null
+++ b/swh/loader/git/py.typed
@@ -0,0 +1 @@
+# Marker file for PEP 561.
diff --git a/swh/loader/git/tests/conftest.py b/swh/loader/git/tests/conftest.py
index 55e1b3e..90b7ea7 100644
--- a/swh/loader/git/tests/conftest.py
+++ b/swh/loader/git/tests/conftest.py
@@ -1,10 +1,10 @@
 import pytest
 
 from swh.scheduler.tests.conftest import *  # noqa
 
 
-@pytest.fixture(scope='session')
+@pytest.fixture(scope='session')  # type: ignore  # expected redefinition
 def celery_includes():
     return [
         'swh.loader.git.tasks',
     ]
diff --git a/tox.ini b/tox.ini
index 6b8f975..bb4c7d0 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,20 +1,28 @@
 [tox]
-envlist=flake8,py3
+envlist=flake8,mypy,py3
 
 [testenv:py3]
 deps =
   # the dependency below is needed for now as a workaround for
   #   https://github.com/pypa/pip/issues/6239
   # TODO: remove when this issue is fixed
   swh.core[http] >= 0.0.61
   .[testing]
   pytest-cov
 commands =
   pytest --cov=swh --cov-branch {posargs}
 
 [testenv:flake8]
 skip_install = true
 deps =
   flake8
 commands =
   {envpython} -m flake8
+
+[testenv:mypy]
+skip_install = true
+deps =
+  .[testing]
+  mypy
+commands =
+  mypy swh