diff --git a/app/api/main.py b/app/api/main.py
index 316a011dbc41c0aa086531b7ad67f465d17201aa..e2c3f310b8795babd591656a60c58c8c5f55db75 100644
--- a/app/api/main.py
+++ b/app/api/main.py
@@ -89,7 +89,8 @@ def login():
 @jwt_required
 def get_items():
     # TODO: add pagination
-    items = Item.query.order_by(Item._created)
+    query = utils.get_query(Item.query, request.args)
+    items = query.order_by(Item._created)
     data = [item.to_dict() for item in items]
     return jsonify(data)
 
diff --git a/app/utils.py b/app/utils.py
index 8365bcf318415c4949ba9ee03b1cafd3e3868f2e..a369fbf48f03f73371f8992e875f3b5bddbe4c4b 100644
--- a/app/utils.py
+++ b/app/utils.py
@@ -12,6 +12,7 @@ This module implements utility functions.
 import base64
 import datetime
 import io
+import sqlalchemy as sa
 
 
 class InventoryError(Exception):
@@ -103,3 +104,19 @@ def get_choices(iterable, allow_blank=False, allow_null=False):
         choices.append(('null', 'not set'))
     choices.extend([(val, val) for val in iterable])
     return choices
+
+
+def get_query(query, args):
+    """Retrieve the query from the arguments
+
+    :param query: sqlalchemy base query
+    :param MultiDict args: args from a request
+    :returns: query filtered by the arguments
+    """
+    if args:
+        kwargs = args.to_dict()
+        try:
+            query = query.filter_by(**kwargs)
+        except (sa.exc.InvalidRequestError, AttributeError) as e:
+            raise InventoryError('Invalid query arguments', status_code=422)
+    return query
diff --git a/tests/functional/test_api.py b/tests/functional/test_api.py
index 088eece33ebb0de8d3a5c337471d02a32f92df80..396dc55cfcf9541b2b660d57b22ac20c7b252b16 100644
--- a/tests/functional/test_api.py
+++ b/tests/functional/test_api.py
@@ -357,3 +357,40 @@ def test_patch_item_parent(client, session, user_token):
     assert response.json['manufacturer'] is None
     response = get(client, f'/api/items/{item3.ics_id}', token=user_token)
     assert response.json['manufacturer'] == 'Dell'
+
+
+def test_get_items(client, session, readonly_token):
+    # Create some items
+    session.add(models.Manufacturer(name='Dell'))
+    session.add(models.Status(name='Stock'))
+    session.add(models.Status(name='In service'))
+    session.add(models.Location(name='ESS'))
+    session.add(models.Location(name='ICS lab'))
+    item1 = models.Item(serial_number='123456', ics_id='AAA001', location='ESS', status='In service',
+                        manufacturer='Dell')
+    item2 = models.Item(serial_number='234567', ics_id='AAA002', status='Stock')
+    item3 = models.Item(serial_number='345678', ics_id='AAA003', status='Stock', manufacturer='Dell')
+    for item in (item1, item2, item3):
+        session.add(item)
+    session.commit()
+
+    response = get(client, '/api/items', token=readonly_token)
+    assert response.status_code == 200
+    assert len(response.json) == 3
+    check_items(response, (item1.to_dict(), item2.to_dict(), item3.to_dict()))
+
+    # test filtering
+    response = get(client, '/api/items?serial_number=234567', token=readonly_token)
+    assert response.status_code == 200
+    assert len(response.json) == 1
+    check_items(response, (item2.to_dict(),))
+    # filtering on location_id works but not location (might want to change that)
+    response = get(client, f'/api/items?location_id={item1.location_id}', token=readonly_token)
+    assert response.status_code == 200
+    assert len(response.json) == 1
+    check_items(response, (item1.to_dict(),))
+    response = get(client, '/api/items?location=ESS', token=readonly_token)
+    check_response_message(response, 'Invalid query arguments', 422)
+    # using an unknown key raises a 422
+    response = get(client, '/api/items?foo=bar', token=readonly_token)
+    check_response_message(response, 'Invalid query arguments', 422)