diff --git a/zesje/api.py b/zesje/api.py
index 54496c500b4dbf6e0418bf83c19e746e56ff607e..61594fff4f0895f4d33d1ebcbb264195da70d34b 100644
--- a/zesje/api.py
+++ b/zesje/api.py
@@ -39,6 +39,8 @@ api.add_resource(Feedback, '/feedback/<int:problem_id>')
 # Other resources that don't return JSON
 # It is possible to get flask_restful to work with these, but not
 # very idiomatic.
+
+# Images
 api_bp.add_url_rule(
     '/images/signature/<int:exam_id>/<int:submission_id>',
     'signature',
@@ -55,8 +57,14 @@ api_bp.add_url_rule(
     summary_plot.get,
 )
 
+# Exports
 api_bp.add_url_rule(
     '/export/full',
     'full_export',
     export.full,
 )
+api_bp.add_url_rule(
+    '/export/dataframe/<int:exam_id>',
+    'dataframe_export',
+    export.dataframe,
+)
diff --git a/zesje/helpers/db_helper.py b/zesje/helpers/db_helper.py
index d22e59506ff684df7668be1c5c75e39bd8e6bd3f..66d085a4904ecc6fd73f4fc86cc6b82aaf9a6377 100644
--- a/zesje/helpers/db_helper.py
+++ b/zesje/helpers/db_helper.py
@@ -68,7 +68,10 @@ def solution_data(exam_id, student_id):
 def full_exam_data(exam_id):
     """Compute all grades of an exam as a pandas DataFrame."""
     with orm.db_session:
-        students = sorted(Exam[exam_id].submissions.student.id)
+        exam = Exam[exam_id]
+        if exam is None:
+            raise KeyError("No such exam.")
+        students = sorted(exam.submissions.student.id)
 
         data = [solution_data(exam_id, student_id)
                 for student_id in students]
diff --git a/zesje/resources/export.py b/zesje/resources/export.py
index 2cbf09761829421f724ec7c5103fdf088058eef9..6b2f01052e9e90801a276a9e8e98ea447f68f20f 100644
--- a/zesje/resources/export.py
+++ b/zesje/resources/export.py
@@ -21,3 +21,26 @@ def full():
     resp.headers.set('Content-Disposition', 'attachment',
                      filename='course.sqlite')
     return resp
+
+
+def dataframe(exam_id):
+    """Export exam data as a pandas dataframe
+
+    Parameters
+    ----------
+    exam_id : int
+
+    Returns
+    -------
+    exam.pd : pickled pandas dataframe
+    """
+    try:
+        data = db_helper.full_exam_data(exam_id)
+    except KeyError:
+        abort(404)
+    serialized = BytesIO()
+    data.to_pickle(serialized)
+    resp = Response(serialized.getvalue(), 200)
+    resp.headers.set('Content-Disposition', 'attachment',
+                     filename='exam.pd')
+    return resp