diff --git a/arkindex/documents/api/elements.py b/arkindex/documents/api/elements.py
index 83cb626b504ee295e31ce650ec5588540345d1ca..1cbb52e4c37ca532ae87b49f1e813c44bc9df197 100644
--- a/arkindex/documents/api/elements.py
+++ b/arkindex/documents/api/elements.py
@@ -181,6 +181,22 @@ class ElementsListAutoSchema(AutoSchema):
                 type=UUID,
                 required=False,
             ),
+            OpenApiParameter(
+                'rotation_angle',
+                description='Restrict to elements with the given rotation angle.',
+                type={
+                    'type': 'integer',
+                    'minimum': 0,
+                    'maximum': 359,
+                },
+                required=False,
+            ),
+            OpenApiParameter(
+                'mirrored',
+                description='Restrict to or exclude mirrored elements.',
+                type=bool,
+                required=False,
+            )
         ]
 
         # Add method-specific parameters
@@ -450,9 +466,21 @@ class ElementsListBase(CorpusACLMixin, DestroyModelMixin, ListAPIView):
         }
         errors = {}
 
-        if 'name' in self.request.query_params:
+        if 'name' in self.clean_params:
             filters['name__icontains'] = self.clean_params['name']
 
+        if 'rotation_angle' in self.clean_params:
+            try:
+                rotation_angle = int(self.clean_params['rotation_angle'])
+                assert 0 <= rotation_angle <= 359, 'A rotation angle must be between 0 and 359 degrees'
+            except (AssertionError, TypeError, ValueError) as e:
+                errors['rotation_angle'] = [str(e)]
+            else:
+                filters['rotation_angle'] = rotation_angle
+
+        if 'mirrored' in self.clean_params:
+            filters['mirrored'] = self.clean_params['mirrored'].lower() not in ('false', '0')
+
         if self.type_filter:
             filters['type'] = self.type_filter
         elif self.folder_filter is not None:
diff --git a/arkindex/documents/tests/test_children_elements.py b/arkindex/documents/tests/test_children_elements.py
index 3730d00c960bd22677fa882f6b50749b6e08a00d..ffdba1daf3efc63a27025a98b47c7debcd8a85a2 100644
--- a/arkindex/documents/tests/test_children_elements.py
+++ b/arkindex/documents/tests/test_children_elements.py
@@ -498,6 +498,86 @@ class TestChildrenElements(FixtureAPITestCase):
             ]
         )
 
+    def test_children_filter_rotation_angle(self):
+        element_type = self.corpus.types.first()
+        not_rotated = self.corpus.elements.create(type=element_type, name='not rotated')
+        rotated = self.corpus.elements.create(type=element_type, name='rotated', rotation_angle=180)
+        not_rotated.add_parent(self.vol)
+        rotated.add_parent(self.vol)
+
+        cases = [
+            (0, ['not rotated']),
+            (1, []),
+            (180, ['rotated']),
+            (181, [])
+        ]
+        for rotation_angle, expected_elements in cases:
+            with self.subTest(rotation_angle=rotation_angle):
+                # Requests that return no elements only make 3 SQL queries
+                num_queries = 5 if len(expected_elements) else 3
+                with self.assertNumQueries(num_queries):
+                    response = self.client.get(
+                        reverse('api:elements-children', kwargs={'pk': str(self.vol.id)}),
+                        data={
+                            'name': 'rotated',
+                            'rotation_angle': rotation_angle,
+                        }
+                    )
+                    self.assertEqual(response.status_code, status.HTTP_200_OK)
+                self.assertListEqual(
+                    [element['name'] for element in response.json()['results']],
+                    expected_elements
+                )
+
+    def test_children_filter_rotation_angle_invalid(self):
+        cases = [
+            ('to the left', "invalid literal for int() with base 10: 'to the left'"),
+            (-1, 'A rotation angle must be between 0 and 359 degrees'),
+            (360, 'A rotation angle must be between 0 and 359 degrees'),
+            (54.2, "invalid literal for int() with base 10: '54.2'"),
+            (4.14j, "invalid literal for int() with base 10: '4.14j'"),
+        ]
+        for rotation_angle, expected_error in cases:
+            with self.subTest(rotation_angle=rotation_angle):
+                with self.assertNumQueries(1):
+                    response = self.client.get(
+                        reverse('api:elements-children', kwargs={'pk': str(self.vol.id)}),
+                        data={
+                            'rotation_angle': rotation_angle,
+                        }
+                    )
+                    self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+                self.assertEqual(response.json(), {
+                    'rotation_angle': [expected_error]
+                })
+
+    def test_children_filter_mirrored(self):
+        element_type = self.corpus.types.first()
+        mirrored = self.corpus.elements.create(type=element_type, name='mirrored', mirrored=True)
+        not_mirrored = self.corpus.elements.create(type=element_type, name='not mirrored', mirrored=False)
+        mirrored.add_parent(self.vol)
+        not_mirrored.add_parent(self.vol)
+
+        cases = [
+            (False, ['not mirrored']),
+            (True, ['mirrored']),
+        ]
+        for mirrored, expected_elements in cases:
+            with self.subTest(mirrored=mirrored):
+                with self.assertNumQueries(5):
+                    response = self.client.get(
+                        reverse('api:elements-children', kwargs={'pk': str(self.vol.id)}),
+                        data={
+                            'name': 'mirrored',
+                            'mirrored': mirrored,
+                        }
+                    )
+                    self.assertEqual(response.status_code, status.HTTP_200_OK)
+                self.assertListEqual(
+                    [element['name'] for element in response.json()['results']],
+                    expected_elements
+                )
+
     def test_children_invalid_sort(self):
         cases = [
             ({'order': 'blah', 'order_direction': 'asc'}, {'order': ['Unknown sorting field']}),
diff --git a/arkindex/documents/tests/test_corpus_elements.py b/arkindex/documents/tests/test_corpus_elements.py
index 2f364b979ea6ff88f9dac55dfb2ffab900ec1fdd..db9c72337141420a8f255565b3bd72d575d083e0 100644
--- a/arkindex/documents/tests/test_corpus_elements.py
+++ b/arkindex/documents/tests/test_corpus_elements.py
@@ -536,6 +536,82 @@ class TestListElements(FixtureAPITestCase):
             ['Volume 1, page 1v', 'Volume 2, page 1v']
         )
 
+    def test_list_elements_filter_rotation_angle(self):
+        element_type = self.corpus.types.first()
+        self.corpus.elements.create(type=element_type, name='not rotated')
+        self.corpus.elements.create(type=element_type, name='rotated', rotation_angle=180)
+
+        cases = [
+            (0, ['not rotated']),
+            (1, []),
+            (180, ['rotated']),
+            (181, [])
+        ]
+        for rotation_angle, expected_elements in cases:
+            with self.subTest(rotation_angle=rotation_angle):
+                # Requests that return no elements only make 2 SQL queries
+                num_queries = 4 if len(expected_elements) else 2
+                with self.assertNumQueries(num_queries):
+                    response = self.client.get(
+                        reverse('api:corpus-elements', kwargs={'corpus': self.corpus.id}),
+                        data={
+                            'name': 'rotated',
+                            'rotation_angle': rotation_angle,
+                        }
+                    )
+                    self.assertEqual(response.status_code, status.HTTP_200_OK)
+                self.assertListEqual(
+                    [element['name'] for element in response.json()['results']],
+                    expected_elements
+                )
+
+    def test_list_elements_filter_rotation_angle_invalid(self):
+        cases = [
+            ('to the left', "invalid literal for int() with base 10: 'to the left'"),
+            (-1, 'A rotation angle must be between 0 and 359 degrees'),
+            (360, 'A rotation angle must be between 0 and 359 degrees'),
+            (54.2, "invalid literal for int() with base 10: '54.2'"),
+            (4.14j, "invalid literal for int() with base 10: '4.14j'"),
+        ]
+        for rotation_angle, expected_error in cases:
+            with self.subTest(rotation_angle=rotation_angle):
+                with self.assertNumQueries(1):
+                    response = self.client.get(
+                        reverse('api:corpus-elements', kwargs={'corpus': self.corpus.id}),
+                        data={
+                            'rotation_angle': rotation_angle,
+                        }
+                    )
+                    self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+                self.assertEqual(response.json(), {
+                    'rotation_angle': [expected_error]
+                })
+
+    def test_list_elements_filter_mirrored(self):
+        element_type = self.corpus.types.first()
+        self.corpus.elements.create(type=element_type, name='mirrored', mirrored=True)
+        self.corpus.elements.create(type=element_type, name='not mirrored', mirrored=False)
+
+        cases = [
+            (False, ['not mirrored']),
+            (True, ['mirrored']),
+        ]
+        for mirrored, expected_elements in cases:
+            with self.subTest(mirrored=mirrored):
+                with self.assertNumQueries(4):
+                    response = self.client.get(
+                        reverse('api:corpus-elements', kwargs={'corpus': self.corpus.id}),
+                        data={
+                            'name': 'mirrored',
+                            'mirrored': mirrored,
+                        }
+                    )
+                    self.assertEqual(response.status_code, status.HTTP_200_OK)
+                self.assertListEqual(
+                    [element['name'] for element in response.json()['results']],
+                    expected_elements
+                )
+
     def test_list_elements_with_corpus_false(self):
         with self.assertNumQueries(5):
             response = self.client.get(
diff --git a/arkindex/documents/tests/test_parents_elements.py b/arkindex/documents/tests/test_parents_elements.py
index 6a6d8dc5a32e37f2cbf79dff1580cf6957eb176c..e4e16e672a00cedd0b1ee409a7b8a8929f643e3f 100644
--- a/arkindex/documents/tests/test_parents_elements.py
+++ b/arkindex/documents/tests/test_parents_elements.py
@@ -317,6 +317,86 @@ class TestParentsElements(FixtureAPITestCase):
             ['Element with metadata']
         )
 
+    def test_parents_filter_rotation_angle(self):
+        element_type = self.corpus.types.first()
+        not_rotated = self.corpus.elements.create(type=element_type, name='not rotated')
+        rotated = self.corpus.elements.create(type=element_type, name='rotated', rotation_angle=180)
+        self.page.add_parent(not_rotated)
+        self.page.add_parent(rotated)
+
+        cases = [
+            (0, ['not rotated']),
+            (1, []),
+            (180, ['rotated']),
+            (181, [])
+        ]
+        for rotation_angle, expected_elements in cases:
+            with self.subTest(rotation_angle=rotation_angle):
+                # Requests that return no elements only make 2 SQL queries
+                num_queries = 4 if len(expected_elements) else 2
+                with self.assertNumQueries(num_queries):
+                    response = self.client.get(
+                        reverse('api:elements-parents', kwargs={'pk': str(self.page.id)}),
+                        data={
+                            'name': 'rotated',
+                            'rotation_angle': rotation_angle,
+                        }
+                    )
+                    self.assertEqual(response.status_code, status.HTTP_200_OK)
+                self.assertListEqual(
+                    [element['name'] for element in response.json()['results']],
+                    expected_elements
+                )
+
+    def test_parents_filter_rotation_angle_invalid(self):
+        cases = [
+            ('to the left', "invalid literal for int() with base 10: 'to the left'"),
+            (-1, 'A rotation angle must be between 0 and 359 degrees'),
+            (360, 'A rotation angle must be between 0 and 359 degrees'),
+            (54.2, "invalid literal for int() with base 10: '54.2'"),
+            (4.14j, "invalid literal for int() with base 10: '4.14j'"),
+        ]
+        for rotation_angle, expected_error in cases:
+            with self.subTest(rotation_angle=rotation_angle):
+                with self.assertNumQueries(1):
+                    response = self.client.get(
+                        reverse('api:elements-parents', kwargs={'pk': str(self.page.id)}),
+                        data={
+                            'rotation_angle': rotation_angle,
+                        }
+                    )
+                    self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+                self.assertEqual(response.json(), {
+                    'rotation_angle': [expected_error]
+                })
+
+    def test_parents_filter_mirrored(self):
+        element_type = self.corpus.types.first()
+        mirrored = self.corpus.elements.create(type=element_type, name='mirrored', mirrored=True)
+        not_mirrored = self.corpus.elements.create(type=element_type, name='not mirrored', mirrored=False)
+        self.page.add_parent(mirrored)
+        self.page.add_parent(not_mirrored)
+
+        cases = [
+            (False, ['not mirrored']),
+            (True, ['mirrored']),
+        ]
+        for mirrored, expected_elements in cases:
+            with self.subTest(mirrored=mirrored):
+                with self.assertNumQueries(4):
+                    response = self.client.get(
+                        reverse('api:elements-parents', kwargs={'pk': str(self.page.id)}),
+                        data={
+                            'name': 'mirrored',
+                            'mirrored': mirrored,
+                        }
+                    )
+                    self.assertEqual(response.status_code, status.HTTP_200_OK)
+                self.assertListEqual(
+                    [element['name'] for element in response.json()['results']],
+                    expected_elements
+                )
+
     def test_parents_invalid_sort(self):
         cases = [
             ({'order': 'blah', 'order_direction': 'asc'}, {'order': ['Unknown sorting field']}),