diff --git a/api/jobs/handlers.py b/api/jobs/handlers.py
index ebef965d67a68db8b6c75aa4436d7330d1f14b90..3c058c97083e2a4b7f34827b55f787856fcd9d0a 100644
--- a/api/jobs/handlers.py
+++ b/api/jobs/handlers.py
@@ -444,8 +444,9 @@ class JobHandler(base.RequestHandler):
     def prepare_complete(self, _id):
         payload = self.request.json
         success = payload['success']
+        elapsed = payload['elapsed']
 
-        ticket = JobTicket.create(_id, success)
+        ticket = JobTicket.create(_id, success, elapsed)
         return { 'ticket': ticket }
 
     @require_login
diff --git a/api/jobs/jobs.py b/api/jobs/jobs.py
index 35be594fd77952d7aab6fced4cbf568e4752ba20..7c408d0c411c602c84276bb90f6d12741fd7fa9a 100644
--- a/api/jobs/jobs.py
+++ b/api/jobs/jobs.py
@@ -21,7 +21,7 @@ class Job(object):
                  modified=None, state='pending', request=None,
                  id_=None, config_=None, now=False, origin=None,
                  saved_files=None, produced_metadata=None, batch=None,
-                 failed_output_accepted=False):
+                 failed_output_accepted=False, profile=None):
         """
         Creates a job.
 
@@ -71,6 +71,8 @@ class Job(object):
             created = time_now
         if modified is None:
             modified = time_now
+        if profile is None:
+            profile = {}
 
         if destination is None and inputs is not None:
             # Grab an arbitrary input's container
@@ -106,6 +108,7 @@ class Job(object):
         self.produced_metadata  = produced_metadata
         self.batch              = batch
         self.failed_output_accepted = failed_output_accepted
+        self.profile = profile
 
 
     def intention_equals(self, other_job):
@@ -165,7 +168,9 @@ class Job(object):
             saved_files=d.get('saved_files'),
             produced_metadata=d.get('produced_metadata'),
             batch=d.get('batch'),
-            failed_output_accepted=d.get('failed_output_accepted', False))
+            failed_output_accepted=d.get('failed_output_accepted', False),
+            profile=d.get('profile', {})
+        )
 
     @classmethod
     def get(cls, _id):
@@ -380,12 +385,13 @@ class JobTicket(object):
         return config.db.job_tickets.find_one({'_id': bson.ObjectId(_id)})
 
     @staticmethod
-    def create(job_id, success):
+    def create(job_id, success, elapsed):
         j = Job.get(job_id)
 
         result = config.db.job_tickets.insert_one({
             'job': j.id_,
             'success': success,
+            'elapsed': elapsed,
         })
 
         return result.inserted_id
diff --git a/api/placer.py b/api/placer.py
index 2bba7ca5596ae425aca6f4b4202b868b3748b195..6f3339383cef1caa98c628d6fac27d3c0da8007a 100644
--- a/api/placer.py
+++ b/api/placer.py
@@ -251,6 +251,13 @@ class EnginePlacer(Placer):
 
     def check(self):
         self.requireTarget()
+
+        # Check that required state exists
+        if self.context.get('job_id'):
+            Job.get(self.context.get('job_id'))
+        if self.context.get('job_ticket_id'):
+            JobTicket.get(self.context.get('job_ticket_id'))
+
         if self.metadata is not None:
             validators.validate_data(self.metadata, 'enginemetadata.json', 'input', 'POST', optional=True)
 
@@ -325,9 +332,19 @@ class EnginePlacer(Placer):
 
         if job_ticket is not None:
             if success:
-                Queue.mutate(job, {'state': 'complete'})
+                Queue.mutate(job, {
+                    'state': 'complete',
+                    'profile': {
+                        'elapsed': job_ticket['elapsed']
+                    }
+                })
             else:
-                Queue.mutate(job, {'state': 'failed'})
+                Queue.mutate(job, {
+                    'state': 'failed',
+                    'profile': {
+                        'elapsed': job_ticket['elapsed']
+                    }
+                })
 
         if self.context.get('job_id'):
             job = Job.get(self.context.get('job_id'))
@@ -697,6 +714,12 @@ class AnalysisJobPlacer(Placer):
         if self.id_ is None:
             raise Exception('Must specify a target analysis')
 
+        # Check that required state exists
+        if self.context.get('job_id'):
+            Job.get(self.context.get('job_id'))
+        if self.context.get('job_ticket_id'):
+            JobTicket.get(self.context.get('job_ticket_id'))
+
     def process_file_field(self, field, file_attrs):
         if self.metadata is not None:
             file_mds = self.metadata.get('acquisition', {}).get('files', [])
diff --git a/tests/integration_tests/python/test_jobs.py b/tests/integration_tests/python/test_jobs.py
index 72d76622fdaae5c31305e1ffc3f1904d85e73b3e..220b21140da218aaa69345ae6c9e8d257a1805fc 100644
--- a/tests/integration_tests/python/test_jobs.py
+++ b/tests/integration_tests/python/test_jobs.py
@@ -387,8 +387,10 @@ def test_failed_job_output(data_builder, default_payload, as_user, as_admin, as_
     assert r.ok
     job = r.json()['_id']
 
+    api_db.jobs.update_one({'_id': bson.ObjectId(job)}, {'$set':{'state': 'running'}})
+
     # prepare completion (send success status before engine upload)
-    r = as_drone.post('/jobs/' + job + '/prepare-complete', json={'success': False})
+    r = as_drone.post('/jobs/' + job + '/prepare-complete', json={'success': False, 'elapsed': -1})
     assert r.ok
 
     # verify that job ticket has been created