diff --git a/arkindex/documents/serializers/elements.py b/arkindex/documents/serializers/elements.py
index 4222394c9750c005f17a9f75c60100721d46556f..91bbbdc8220676750893b04256766e9c64e35b88 100644
--- a/arkindex/documents/serializers/elements.py
+++ b/arkindex/documents/serializers/elements.py
@@ -895,12 +895,14 @@ class ElementBulkSerializer(serializers.Serializer):
     Serialize child elements on a common parent
     """
     worker_version = ForbiddenField()
-    worker_run_id = serializers.PrimaryKeyRelatedField(
-        queryset=WorkerRun.objects.all(),
-        help_text='A WorkerRun ID that the new elements will refer to',
-        source='worker_run',
-        # Avoids thousands of SQL queries when opening the endpoint in a browser
-        style={'base_template': 'input.html'},
+    worker_run_id = WorkerRunIDField(
+        help_text=dedent("""
+            A WorkerRun ID that the new elements will refer to.
+
+            Regular users may only use the WorkerRuns of their own `Local` process.
+
+            Tasks authenticated via the Ponos task authentication may only use the WorkerRuns of their process.
+        """).strip(),
     )
 
     elements = ElementBulkItemSerializer(
diff --git a/arkindex/documents/tests/test_bulk_elements.py b/arkindex/documents/tests/test_bulk_elements.py
index f849b7658455f696cb6b88a47ebd29b927b664d9..0e280a0c7e2ec5358fa33b347d7643b04759f17f 100644
--- a/arkindex/documents/tests/test_bulk_elements.py
+++ b/arkindex/documents/tests/test_bulk_elements.py
@@ -18,8 +18,9 @@ class TestBulkElements(FixtureAPITestCase):
 
         cls.worker_version = WorkerVersion.objects.get(worker__slug='reco')
         cls.worker_run = cls.worker_version.worker_runs.filter(process__mode=ProcessMode.Workers).get()
+        cls.local_worker_run = cls.worker_version.worker_runs.filter(process__mode=ProcessMode.Local).get()
         cls.payload = {
-            'worker_run_id': str(cls.worker_run.id),
+            'worker_run_id': str(cls.local_worker_run.id),
             'elements': [
                 {
                     'name': 'A',
@@ -111,12 +112,12 @@ class TestBulkElements(FixtureAPITestCase):
         })
 
     def test_bulk_create_requires_elements(self):
-        self.client.force_login(self.user)
-        with self.assertNumQueries(6):
+        self.client.force_login(self.superuser)
+        with self.assertNumQueries(4):
             response = self.client.post(
                 reverse('api:elements-bulk-create', kwargs={'pk': str(self.element.id)}),
                 data={
-                    'worker_run_id': str(self.worker_run.id),
+                    'worker_run_id': str(self.local_worker_run.id),
                     'elements': [],
                 },
                 format='json',
@@ -125,12 +126,12 @@ class TestBulkElements(FixtureAPITestCase):
         self.assertDictEqual(response.json(), {'elements': {'non_field_errors': ['This list may not be empty.']}})
 
     def test_bulk_create_missing_types(self):
-        self.client.force_login(self.user)
-        with self.assertNumQueries(7):
+        self.client.force_login(self.superuser)
+        with self.assertNumQueries(5):
             response = self.client.post(
                 reverse('api:elements-bulk-create', kwargs={'pk': str(self.element.id)}),
                 data={
-                    'worker_run_id': str(self.worker_run.id),
+                    'worker_run_id': str(self.local_worker_run.id),
                     'elements': [
                         {
                             'name': 'nope',
@@ -155,8 +156,8 @@ class TestBulkElements(FixtureAPITestCase):
         self.element.add_parent(parent)
         element_path1, element_path2 = self.element.paths.order_by('path__0')
 
-        self.client.force_login(self.user)
-        with self.assertNumQueries(13):
+        self.client.force_login(self.superuser)
+        with self.assertNumQueries(11):
             response = self.client.post(
                 reverse('api:elements-bulk-create', kwargs={'pk': str(self.element.id)}),
                 data=self.payload,
@@ -200,12 +201,12 @@ class TestBulkElements(FixtureAPITestCase):
         self.assertFalse(element.paths.exists())
 
         # Try to create a sub element on that zone
-        self.client.force_login(self.user)
-        with self.assertNumQueries(13):
+        self.client.force_login(self.superuser)
+        with self.assertNumQueries(11):
             response = self.client.post(
                 reverse('api:elements-bulk-create', kwargs={'pk': str(element.id)}),
                 data={
-                    'worker_run_id': str(self.worker_run.id),
+                    'worker_run_id': str(self.local_worker_run.id),
                     'elements': [
                         {
                             'name': f'surface {i}',
@@ -231,12 +232,12 @@ class TestBulkElements(FixtureAPITestCase):
         self.assertEqual(paths.count(), 4)
 
     def test_bulk_create_negative_polygon(self):
-        self.client.force_login(self.user)
-        with self.assertNumQueries(6):
+        self.client.force_login(self.superuser)
+        with self.assertNumQueries(4):
             response = self.client.post(
                 reverse('api:elements-bulk-create', kwargs={'pk': str(self.element.id)}),
                 {
-                    'worker_run_id': str(self.worker_run.id),
+                    'worker_run_id': str(self.local_worker_run.id),
                     'elements': [
                         {
                             'name': 'Blah',
@@ -264,7 +265,7 @@ class TestBulkElements(FixtureAPITestCase):
         """
         Created children elements inherit their parent's rotation angle
         """
-        self.client.force_login(self.user)
+        self.client.force_login(self.superuser)
         response = self.client.post(
             reverse('api:elements-bulk-create', kwargs={'pk': str(self.rotated_page.id)}),
             data=self.payload,
@@ -279,7 +280,7 @@ class TestBulkElements(FixtureAPITestCase):
         """
         Created children elements inherit their parent's mirroring
         """
-        self.client.force_login(self.user)
+        self.client.force_login(self.superuser)
         response = self.client.post(
             reverse('api:elements-bulk-create', kwargs={'pk': str(self.mirrored_page.id)}),
             data=self.payload,
@@ -294,9 +295,9 @@ class TestBulkElements(FixtureAPITestCase):
         """
         Cannot create elements outside their image
         """
-        self.client.force_login(self.user)
+        self.client.force_login(self.superuser)
         payload = {
-            'worker_run_id': str(self.worker_run.id),
+            'worker_run_id': str(self.local_worker_run.id),
             'elements': [
                 {
                     'name': 'A',
@@ -335,7 +336,7 @@ class TestBulkElements(FixtureAPITestCase):
                 }
             ]
         }
-        with self.assertNumQueries(7):
+        with self.assertNumQueries(5):
             response = self.client.post(
                 reverse('api:elements-bulk-create', kwargs={'pk': str(self.element.id)}),
                 data=payload,
@@ -372,15 +373,17 @@ class TestBulkElements(FixtureAPITestCase):
             'worker_run_id': [f'Invalid pk "{random_uuid}" - object does not exist.']
         })
 
-    def test_bulk_create_worker_run_or_version(self):
-        """Either a worker run or a worker version is required"""
-
+    def test_bulk_create_worker_run_required(self):
+        """
+        Worker run attribute is required, worker version attribute is forbidden
+        """
         self.client.force_login(self.user)
         with self.assertNumQueries(5):
             response = self.client.post(
                 reverse('api:elements-bulk-create', kwargs={'pk': str(self.element.id)}),
                 data={
-                    'elements': [
+                    "worker_version": str(self.worker_version.id),
+                    "elements": [
                         {
                             'name': 'Blah',
                             'type': 'surface',
@@ -393,59 +396,75 @@ class TestBulkElements(FixtureAPITestCase):
             self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
         self.assertDictEqual(response.json(), {
             'worker_run_id': ['This field is required.'],
+            'worker_version': ['This field is forbidden.'],
         })
 
-    def test_bulk_create_worker_run_and_version(self):
-        """Worker run and worker version cannot be set at the same time"""
+    def test_worker_run_non_local(self):
+        """
+        A regular user cannot create elements with a WorkerRun of a non-local process
+        """
+        self.client.force_login(self.superuser)
+        payload = {**self.payload, 'worker_run_id': str(self.worker_run.id)}
+        with self.assertNumQueries(4):
+            response = self.client.post(
+                reverse('api:elements-bulk-create', kwargs={'pk': str(self.element.id)}),
+                data=payload,
+                format='json',
+            )
+            self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        self.assertEqual(response.json(), {'worker_run_id': [
+            "Ponos task authentication is required to use a WorkerRun of a process other than the user's local process."
+        ]})
 
-        self.client.force_login(self.user)
-        with self.assertNumQueries(6):
+    def test_worker_run_other_user(self):
+        """
+        A regular user cannot create elements with a WorkerRun of someone else's local process
+        """
+        worker_run = self.user.processes.get(mode=ProcessMode.Local).worker_runs.first()
+        self.client.force_login(self.superuser)
+        payload = {**self.payload, 'worker_run_id': str(worker_run.id)}
+        with self.assertNumQueries(4):
             response = self.client.post(
                 reverse('api:elements-bulk-create', kwargs={'pk': str(self.element.id)}),
-                {
-                    'worker_version': str(self.worker_version.id),
-                    'worker_run_id': str(self.worker_run.id),
-                    'elements': [
-                        {
-                            'name': 'Blah',
-                            'type': 'surface',
-                            'polygon': [[0, 10], [10, 20], [30, 40], [50, 60], [0, 10]]
-                        }
-                    ]
-                },
+                data=payload,
                 format='json',
             )
             self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
-        self.assertDictEqual(response.json(), {
-            'worker_version': ['This field is forbidden.'],
-        })
+        self.assertEqual(response.json(), {'worker_run_id': [
+            "Ponos task authentication is required to use a WorkerRun of a process other than the user's local process."
+        ]})
+
+    def test_worker_run_other_process(self):
+        """
+        A Ponos task cannot create elements with a WorkerRun of another process
+        """
+        process2 = self.worker_run.process.creator.processes.create(
+            mode=ProcessMode.Workers,
+            corpus=self.corpus,
+        )
+        other_worker_run = process2.worker_runs.create(version=self.worker_run.version, parents=[])
+        self.worker_run.process.start()
+        task = self.worker_run.process.workflow.tasks.first()
+        payload = {**self.payload, 'worker_run_id': str(other_worker_run.id)}
 
-    def test_bulk_create_with_worker_version(self):
-        self.client.force_login(self.user)
         with self.assertNumQueries(5):
             response = self.client.post(
                 reverse('api:elements-bulk-create', kwargs={'pk': str(self.element.id)}),
-                data={
-                    "worker_version": str(self.worker_version.id),
-                    "elements": [
-                        {
-                            'name': 'Blah',
-                            'type': 'surface',
-                            'polygon': [[0, 10], [10, 20], [30, 40], [50, 60], [0, 10]]
-                        }
-                    ]
-                },
+                data=payload,
                 format='json',
+                HTTP_AUTHORIZATION=f'Ponos {task.token}',
             )
             self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
-        self.assertDictEqual(response.json(), {
-            'worker_run_id': ['This field is required.'],
-            'worker_version': ['This field is forbidden.'],
-        })
+        self.assertEqual(response.json(), {'worker_run_id': [
+            "Only the WorkerRuns of the authenticated task's process may be used."
+        ]})
 
-    def test_bulk_create_with_worker_run(self):
-        self.client.force_login(self.user)
-        with self.assertNumQueries(13):
+    def test_bulk_create_local_worker_run(self):
+        """
+        A regular user can create elements with a WorkerRun of their own local process
+        """
+        self.client.force_login(self.superuser)
+        with self.assertNumQueries(11):
             response = self.client.post(
                 reverse('api:elements-bulk-create', kwargs={'pk': str(self.element.id)}),
                 data=self.payload,
@@ -473,7 +492,7 @@ class TestBulkElements(FixtureAPITestCase):
                 'mirrored',
                 'paths__path',
             )),
-            3 * [(self.worker_version.id, self.worker_run.id, self.element.image_id, 0, False, [*parent_path, self.element.id])]
+            3 * [(self.worker_version.id, self.local_worker_run.id, self.element.image_id, 0, False, [*parent_path, self.element.id])]
         )
         # Test specific attributes
         self.assertListEqual(
@@ -484,3 +503,99 @@ class TestBulkElements(FixtureAPITestCase):
                 ('C', 'surface', 1, LineString(self.payload['elements'][2]['polygon'])),
             ],
         )
+
+    def test_bulk_create_task_auth(self):
+        """
+        Elements can be created with a WorkerRun of a non-local process
+        when authenticated as a Ponos task of this process
+        """
+        self.worker_run.process.start()
+        task = self.worker_run.process.workflow.tasks.first()
+        payload = {**self.payload, 'worker_run_id': str(self.worker_run.id)}
+
+        with self.assertNumQueries(12):
+            response = self.client.post(
+                reverse('api:elements-bulk-create', kwargs={'pk': str(self.element.id)}),
+                data=payload,
+                format='json',
+                HTTP_AUTHORIZATION=f'Ponos {task.token}',
+            )
+            self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+        elements = (
+            Element.objects
+            .get_descending(self.element.id)
+            .filter(type__slug__in=('act', 'surface'))
+            .order_by('name')
+        )
+        element_ids = list(elements.values_list("id", flat=True))
+        # Ensure elements have been created with valid attributes
+        self.assertListEqual(response.json(), [{'id': str(elt_id)} for elt_id in element_ids])
+        parent_path = self.element.paths.get().path
+        self.assertListEqual(
+            list(elements.values_list(
+                'worker_version_id',
+                'worker_run_id',
+                'image_id',
+                'rotation_angle',
+                'mirrored',
+                'paths__path',
+            )),
+            3 * [(
+                self.worker_version.id,
+                self.worker_run.id,
+                self.element.image_id,
+                0,
+                False,
+                [*parent_path, self.element.id]
+            )]
+        )
+
+    def test_worker_run_local_task_auth(self):
+        """
+        Elements can be created with a WorkerRun of a Local process
+        even when authenticated as a Ponos task from a different process
+        """
+        local_process = self.user.processes.get(mode=ProcessMode.Local)
+        local_worker_run = local_process.worker_runs.get()
+
+        self.worker_run.process.start()
+        task = self.worker_run.process.workflow.tasks.first()
+
+        payload = {**self.payload, 'worker_run_id': str(local_worker_run.id)}
+
+        with self.assertNumQueries(12):
+            response = self.client.post(
+                reverse('api:elements-bulk-create', kwargs={'pk': str(self.element.id)}),
+                data=payload,
+                format='json',
+                HTTP_AUTHORIZATION=f'Ponos {task.token}',
+            )
+            self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+        elements = (
+            Element.objects
+            .get_descending(self.element.id)
+            .filter(type__slug__in=('act', 'surface'))
+            .order_by('name')
+        )
+        element_ids = list(elements.values_list("id", flat=True))
+        # Ensure elements have been created with valid attributes
+        self.assertListEqual(response.json(), [{'id': str(elt_id)} for elt_id in element_ids])
+        parent_path = self.element.paths.get().path
+        self.assertListEqual(
+            list(elements.values_list(
+                'worker_version_id',
+                'worker_run_id',
+                'image_id',
+                'rotation_angle',
+                'mirrored',
+                'paths__path',
+            )),
+            3 * [(
+                local_worker_run.version_id,
+                local_worker_run.id,
+                self.element.image_id,
+                0,
+                False,
+                [*parent_path, self.element.id]
+            )]
+        )