diff --git a/backend/authentication/serializers.py b/backend/authentication/serializers.py
new file mode 100644
index 0000000..4a62dcf
--- /dev/null
+++ b/backend/authentication/serializers.py
@@ -0,0 +1,12 @@
+from rest_framework import serializers
+
+from authentication.models import ToolshedUser
+
+
+class OwnerSerializer(serializers.ReadOnlyField):
+    class Meta:
+        model = ToolshedUser
+        fields = ['username', 'domain']
+
+    def to_representation(self, value):
+        return value.username + '@' + value.domain
diff --git a/backend/backend/urls.py b/backend/backend/urls.py
index 5e94b5e..24ee4d9 100644
--- a/backend/backend/urls.py
+++ b/backend/backend/urls.py
@@ -35,5 +35,6 @@ urlpatterns = [
     path('api/', include('toolshed.api.friend')),
     path('api/', include('toolshed.api.inventory')),
     path('api/', include('toolshed.api.info')),
+    path('api/', include('toolshed.api.files')),
     path('docs/', schema_view.with_ui('swagger', cache_timeout=0), name='api-docs'),
 ]
diff --git a/backend/files/serializers.py b/backend/files/serializers.py
new file mode 100644
index 0000000..e4bc82e
--- /dev/null
+++ b/backend/files/serializers.py
@@ -0,0 +1,20 @@
+from django.core.files.base import ContentFile
+from rest_framework import serializers
+
+from files.models import File
+
+
+class FileSerializer(serializers.Serializer):
+    data = serializers.CharField()
+    mime_type = serializers.CharField()
+    class Meta:
+        model = File
+        fields = ['data', 'mime_type']
+        read_only_fields = ['id', 'size', 'name']
+
+    def to_representation(self, instance):
+        return {'id': instance.id, 'name': instance.file.url, 'size': instance.file.size,
+                'mime_type': instance.mime_type}
+
+    def create(self, validated_data):
+        return File.objects.get_or_create(**validated_data)[0]
diff --git a/backend/toolshed/api/files.py b/backend/toolshed/api/files.py
new file mode 100644
index 0000000..b56e4dc
--- /dev/null
+++ b/backend/toolshed/api/files.py
@@ -0,0 +1,74 @@
+from django.urls import path
+from rest_framework import status
+from rest_framework.decorators import api_view, permission_classes, authentication_classes
+from rest_framework.permissions import IsAuthenticated
+from rest_framework.response import Response
+
+from authentication.signature_auth import SignatureAuthenticationLocal
+from files.models import File
+from files.serializers import FileSerializer
+from toolshed.models import InventoryItem
+
+
+@api_view(['GET'])
+@permission_classes([IsAuthenticated])
+@authentication_classes([SignatureAuthenticationLocal])
+def list_all_files(request, format=None):  # /files/
+    files = File.objects.select_related().filter(connected_items__owner=request.user).distinct()
+    return Response(FileSerializer(files, many=True).data)
+
+
+def get_item_files(request, item_id):
+    try:
+        item = InventoryItem.objects.get(id=item_id, owner=request.user)
+        files = item.files.all()
+        return Response(FileSerializer(files, many=True).data)
+    except InventoryItem.DoesNotExist:
+        return Response(status=status.HTTP_404_NOT_FOUND)
+
+
+def post_item_file(request, item_id):
+    try:
+        item = InventoryItem.objects.get(id=item_id, owner=request.user)
+        serializer = FileSerializer(data=request.data)
+        if serializer.is_valid():
+            file = serializer.save()
+            item.files.add(file)
+            return Response(serializer.data, status=status.HTTP_201_CREATED)
+        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+    except InventoryItem.DoesNotExist:
+        return Response(status=status.HTTP_404_NOT_FOUND)
+
+
+@api_view(['POST', 'GET'])
+@permission_classes([IsAuthenticated])
+@authentication_classes([SignatureAuthenticationLocal])
+def item_files(request, item_id, format=None):  # /item_files/
+    if request.method == 'GET':
+        return get_item_files(request, item_id)
+    elif request.method == 'POST':
+        return post_item_file(request, item_id)
+
+
+@api_view(['DELETE'])
+@permission_classes([IsAuthenticated])
+@authentication_classes([SignatureAuthenticationLocal])
+def delete_item_file(request, item_id, file_id, format=None):  # /item_files/
+    try:
+        item = InventoryItem.objects.get(id=item_id, owner=request.user)
+        file = item.files.get(id=file_id)
+        item.files.remove(file_id)
+        if file.connected_items.count() == 0:
+            file.delete()
+        return Response(status=status.HTTP_204_NO_CONTENT)
+    except InventoryItem.DoesNotExist:
+        return Response(status=status.HTTP_404_NOT_FOUND)
+    except File.DoesNotExist:
+        return Response(status=status.HTTP_404_NOT_FOUND)
+
+
+urlpatterns = [
+    path('files/', list_all_files),
+    path('item_files/<int:item_id>/', item_files),
+    path('item_files/<int:item_id>/<int:file_id>/', delete_item_file),
+]
diff --git a/backend/toolshed/api/inventory.py b/backend/toolshed/api/inventory.py
index 3617ad2..eb472e9 100644
--- a/backend/toolshed/api/inventory.py
+++ b/backend/toolshed/api/inventory.py
@@ -1,3 +1,4 @@
+from django.db import transaction
 from django.urls import path
 from rest_framework import routers, viewsets
 from rest_framework.decorators import authentication_classes, api_view, permission_classes
@@ -35,11 +36,13 @@ class InventoryItemViewSet(viewsets.ModelViewSet):
         return InventoryItem.objects.filter(owner=self.request.user.user.get())
 
     def perform_create(self, serializer):
-        serializer.save(owner=self.request.user.user.get())
+        with transaction.atomic():
+            serializer.save(owner=self.request.user.user.get()).clean()
 
     def perform_update(self, serializer):
-        if serializer.instance.owner == self.request.user.user.get():
-            serializer.save()
+        with transaction.atomic():
+            if serializer.instance.owner == self.request.user.user.get():
+                serializer.save().clean()
 
     def perform_destroy(self, instance):
         if instance.owner == self.request.user.user.get():
diff --git a/backend/toolshed/migrations/0003_inventoryitem_files_and_more.py b/backend/toolshed/migrations/0003_inventoryitem_files_and_more.py
new file mode 100644
index 0000000..b558420
--- /dev/null
+++ b/backend/toolshed/migrations/0003_inventoryitem_files_and_more.py
@@ -0,0 +1,34 @@
+# Generated by Django 4.2.2 on 2023-07-07 17:30
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('files', '0001_initial'),
+        ('toolshed', '0002_inventoryitem_itemtag_itemproperty_and_more'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='inventoryitem',
+            name='files',
+            field=models.ManyToManyField(related_name='connected_items', to='files.file'),
+        ),
+        migrations.AlterField(
+            model_name='inventoryitem',
+            name='availability_policy',
+            field=models.CharField(default='private', max_length=255),
+        ),
+        migrations.AlterField(
+            model_name='inventoryitem',
+            name='description',
+            field=models.TextField(blank=True, null=True),
+        ),
+        migrations.AlterField(
+            model_name='inventoryitem',
+            name='name',
+            field=models.CharField(blank=True, max_length=255, null=True),
+        ),
+    ]
diff --git a/backend/toolshed/models.py b/backend/toolshed/models.py
index d3a4968..f1448a9 100644
--- a/backend/toolshed/models.py
+++ b/backend/toolshed/models.py
@@ -1,8 +1,10 @@
 from django.db import models
 from django.core.validators import MinValueValidator
 from django_softdelete.models import SoftDeleteModel
+from rest_framework.exceptions import ValidationError
 
 from authentication.models import ToolshedUser, KnownIdentity
+from files.models import File
 
 
 class Category(SoftDeleteModel):
@@ -28,7 +30,7 @@ class Property(models.Model):
     unit_name_plural = models.CharField(max_length=255, null=True, blank=True)
     base2_prefix = models.BooleanField(default=False)
     dimensions = models.IntegerField(null=False, blank=False, default=1, validators=[MinValueValidator(1)])
-    origin = models.CharField(max_length=255)
+    origin = models.CharField(max_length=255, null=False, blank=False)
 
     class Meta:
         verbose_name_plural = 'properties'
@@ -41,7 +43,7 @@ class Tag(models.Model):
     name = models.CharField(max_length=255)
     description = models.TextField(null=True, blank=True)
     category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True, blank=True, related_name='tags')
-    origin = models.CharField(max_length=255)
+    origin = models.CharField(max_length=255, null=False, blank=False)
 
     def __str__(self):
         return self.name
@@ -49,16 +51,21 @@ class Tag(models.Model):
 
 class InventoryItem(SoftDeleteModel):
     published = models.BooleanField(default=False)
-    name = models.CharField(max_length=255)
-    description = models.TextField()
+    name = models.CharField(max_length=255, null=True, blank=True)
+    description = models.TextField(null=True, blank=True)
     category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True, blank=True,
                                  related_name='inventory_items')
-    availability_policy = models.CharField(max_length=255)
+    availability_policy = models.CharField(max_length=255, default="private")
     owned_quantity = models.IntegerField(default=1, validators=[MinValueValidator(0)])
     owner = models.ForeignKey(ToolshedUser, on_delete=models.CASCADE, related_name='inventory_items')
     created_at = models.DateTimeField(auto_now_add=True)
-    tags = models.ManyToManyField('Tag', through='ItemTag', related_name='inventory_items')
-    properties = models.ManyToManyField('Property', through='ItemProperty')
+    tags = models.ManyToManyField(Tag, through='ItemTag', related_name='inventory_items')
+    properties = models.ManyToManyField(Property, through='ItemProperty')
+    files = models.ManyToManyField(File, related_name='connected_items')
+
+    def clean(self):
+        if (self.name is None or self.name == "") and self.files.count() == 0:
+            raise ValidationError("Name or at least one file must be set")
 
 
 class ItemProperty(models.Model):
diff --git a/backend/toolshed/serializers.py b/backend/toolshed/serializers.py
index 41bd40a..cccebd4 100644
--- a/backend/toolshed/serializers.py
+++ b/backend/toolshed/serializers.py
@@ -1,5 +1,9 @@
 from rest_framework import serializers
+
 from authentication.models import KnownIdentity, ToolshedUser
+from authentication.serializers import OwnerSerializer
+from files.models import File
+from files.serializers import FileSerializer
 from toolshed.models import Category, Property, ItemProperty, InventoryItem, Tag
 
 
@@ -34,15 +38,6 @@ class CategorySerializer(serializers.ModelSerializer):
         return Category.objects.get(name=data.split("/")[-1])
 
 
-class InventoryItemOwnerSerializer(serializers.ReadOnlyField):
-    class Meta:
-        model = ToolshedUser
-        fields = '__all__'
-
-    def to_representation(self, value):
-        return value.username + '@' + value.domain
-
-
 class ItemPropertySerializer(serializers.ModelSerializer):
     property = PropertySerializer(read_only=True)
 
@@ -60,7 +55,7 @@ class ItemPropertySerializer(serializers.ModelSerializer):
 
 
 class InventoryItemSerializer(serializers.ModelSerializer):
-    owner = InventoryItemOwnerSerializer(read_only=True)
+    owner = OwnerSerializer(read_only=True)
     tags = serializers.SlugRelatedField(many=True, required=False, queryset=Tag.objects.all(), slug_field='name')
     properties = ItemPropertySerializer(many=True, required=False, source='itemproperty_set')
     category = CategorySerializer(required=False, allow_null=True)
@@ -70,14 +65,34 @@ class InventoryItemSerializer(serializers.ModelSerializer):
         fields = ['id', 'name', 'description', 'owner', 'category', 'availability_policy', 'owned_quantity', 'owner',
                   'tags', 'properties']
 
+    def to_internal_value(self, data):
+        files = data.pop('files', [])
+        ret = super().to_internal_value(data)
+        ret['files'] = files
+        return ret
+
     def create(self, validated_data):
         tags = validated_data.pop('tags', [])
         props = validated_data.pop('itemproperty_set', [])
+        files = validated_data.pop('files', [])
         item = InventoryItem.objects.create(**validated_data)
         for tag in tags:
             item.tags.add(tag, through_defaults={})
         for prop in props:
             ItemProperty.objects.create(inventory_item=item, property=prop['property'], value=prop['value'])
+        for file in files:
+            if type(file) == dict:
+                file_serializer = FileSerializer(data=file)
+                if file_serializer.is_valid():
+                    file_serializer.save()
+                    item.files.add(file_serializer.instance)
+                else:
+                    raise serializers.ValidationError(file_serializer.errors)
+            elif type(file) == int:
+                if File.objects.filter(id=file).exists():
+                    item.files.add(File.objects.get(id=file))
+                else:
+                    raise serializers.ValidationError("File with id {} does not exist".format(file))
         item.save()
         return item
 
diff --git a/backend/toolshed/tests/fixtures.py b/backend/toolshed/tests/fixtures.py
index 443bf6d..6755591 100644
--- a/backend/toolshed/tests/fixtures.py
+++ b/backend/toolshed/tests/fixtures.py
@@ -3,28 +3,28 @@ from toolshed.models import Category, Tag, Property, InventoryItem, ItemProperty
 
 class CategoryTestMixin:
     def prepare_categories(self):
-        self.f['cat1'] = Category.objects.create(name='cat1')
-        self.f['cat2'] = Category.objects.create(name='cat2')
-        self.f['cat3'] = Category.objects.create(name='cat3')
-        self.f['subcat1'] = Category.objects.create(name='subcat1', parent=self.f['cat1'])
-        self.f['subcat2'] = Category.objects.create(name='subcat2', parent=self.f['cat1'])
-        self.f['subcat3'] = Category.objects.create(name='subcat3', parent=self.f['subcat1'])
+        self.f['cat1'] = Category.objects.create(name='cat1', origin='test')
+        self.f['cat2'] = Category.objects.create(name='cat2', origin='test')
+        self.f['cat3'] = Category.objects.create(name='cat3', origin='test')
+        self.f['subcat1'] = Category.objects.create(name='subcat1', parent=self.f['cat1'], origin='test')
+        self.f['subcat2'] = Category.objects.create(name='subcat2', parent=self.f['cat1'], origin='test')
+        self.f['subcat3'] = Category.objects.create(name='subcat3', parent=self.f['subcat1'], origin='test')
 
 
 class TagTestMixin:
     def prepare_tags(self):
-        self.f['tag1'] = Tag.objects.create(name='tag1', description='tag1 description', category=self.f['cat1'])
-        self.f['tag2'] = Tag.objects.create(name='tag2', description='tag2 description', category=self.f['cat1'])
-        self.f['tag3'] = Tag.objects.create(name='tag3')
+        self.f['tag1'] = Tag.objects.create(name='tag1', description='tag1 description', category=self.f['cat1'], origin='test')
+        self.f['tag2'] = Tag.objects.create(name='tag2', description='tag2 description', category=self.f['cat1'], origin='test')
+        self.f['tag3'] = Tag.objects.create(name='tag3', origin='test')
 
 
 class PropertyTestMixin:
     def prepare_properties(self):
-        self.f['prop1'] = Property.objects.create(name='prop1')
+        self.f['prop1'] = Property.objects.create(name='prop1', origin='test')
         self.f['prop2'] = Property.objects.create(
-            name='prop2', description='prop2 description', category=self.f['cat1'])
+            name='prop2', description='prop2 description', category=self.f['cat1'], origin='test')
         self.f['prop3'] = Property.objects.create(
-            name='prop3', description='prop3 description', category=self.f['cat1'])
+            name='prop3', description='prop3 description', category=self.f['cat1'], origin='test')
 
 
 class InventoryTestMixin(CategoryTestMixin, TagTestMixin, PropertyTestMixin):
diff --git a/backend/toolshed/tests/test_files.py b/backend/toolshed/tests/test_files.py
new file mode 100644
index 0000000..1925a02
--- /dev/null
+++ b/backend/toolshed/tests/test_files.py
@@ -0,0 +1,140 @@
+from django.test import Client
+from authentication.tests import SignatureAuthClient, UserTestMixin, ToolshedTestCase
+from files.tests import FilesTestMixin
+from toolshed.models import File
+
+from toolshed.tests import InventoryTestMixin
+
+anonymous_client = Client()
+client = SignatureAuthClient()
+
+
+class FileApiTestCase(UserTestMixin, FilesTestMixin, InventoryTestMixin, ToolshedTestCase):
+
+    def setUp(self):
+        super().setUp()
+        self.prepare_users()
+        self.prepare_files()
+        self.prepare_categories()
+        self.prepare_tags()
+        self.prepare_properties()
+        self.prepare_inventory()
+        self.f['item1'].files.add(self.f['test_file1'])
+        self.f['item1'].files.add(self.f['test_file2'])
+        self.f['item2'].files.add(self.f['test_file1'])
+
+    def test_files_anonymous(self):
+        response = anonymous_client.get(f"/api/item_files/{self.f['item1'].id}/")
+        self.assertEqual(response.status_code, 403)
+
+    def test_list_all_files(self):
+        response = client.get(f"/api/files/", self.f['local_user1'])
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(len(response.json()), 2)
+        self.assertEqual(response.json()[0]['mime_type'], 'text/plain')
+        self.assertEqual(response.json()[0]['name'],
+                         f"/media/{self.f['hash1'][:2]}/{self.f['hash1'][2:4]}/{self.f['hash1'][4:6]}/{self.f['hash1'][6:]}")
+        self.assertEqual(response.json()[1]['mime_type'], 'text/plain')
+        self.assertEqual(response.json()[1]['name'],
+                         f"/media/{self.f['hash2'][:2]}/{self.f['hash2'][2:4]}/{self.f['hash2'][4:6]}/{self.f['hash2'][6:]}")
+
+    def test_files(self):
+        response = client.get(f"/api/item_files/{self.f['item1'].id}/", self.f['local_user1'])
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(len(response.json()), 2)
+        self.assertEqual(response.json()[0]['mime_type'], 'text/plain')
+        self.assertEqual(response.json()[0]['name'],
+                         f"/media/{self.f['hash1'][:2]}/{self.f['hash1'][2:4]}/{self.f['hash1'][4:6]}/{self.f['hash1'][6:]}")
+        self.assertEqual(response.json()[1]['mime_type'], 'text/plain')
+        self.assertEqual(response.json()[1]['name'],
+                         f"/media/{self.f['hash2'][:2]}/{self.f['hash2'][2:4]}/{self.f['hash2'][4:6]}/{self.f['hash2'][6:]}")
+
+    def test_files_not_found(self):
+        response = client.get(f"/api/item_files/99999/", self.f['local_user1'])
+        self.assertEqual(response.status_code, 404)
+
+    def test_post_file(self):
+        response = client.post(f"/api/item_files/{self.f['item1'].id}/", self.f['local_user1'],
+                               {'data': self.f['encoded_content4'], 'mime_type': 'text/plain'})
+        self.assertEqual(response.status_code, 201)
+        self.assertEqual(File.objects.count(), 4)
+        self.assertEqual(File.objects.last().mime_type, 'text/plain')
+        self.assertEqual(File.objects.last().file.read(), self.f['test_content4'])
+        self.assertEqual(File.objects.last().file.size, len(self.f['test_content4']))
+        self.assertEqual(File.objects.last().file.name,
+                         f"{self.f['hash4'][:2]}/{self.f['hash4'][2:4]}/{self.f['hash4'][4:6]}/{self.f['hash4'][6:]}")
+
+    def test_post_file_duplicate(self):
+        self.assertEqual(File.objects.count(), 3)
+        self.assertEqual(self.f['item1'].files.count(), 2)
+        response = client.post(f"/api/item_files/{self.f['item1'].id}/", self.f['local_user1'],
+                               {'data': self.f['encoded_content3'], 'mime_type': 'text/plain'})
+        self.assertEqual(response.status_code, 201)
+        self.assertEqual(File.objects.count(), 3)
+        self.assertEqual(self.f['item1'].files.count(), 3)
+        self.assertEqual(self.f['item1'].files.last().file.read(), self.f['test_content3'])
+        self.assertEqual(self.f['item1'].files.last().file.size, len(self.f['test_content3']))
+        self.assertEqual(self.f['item1'].files.last().file.name,
+                         f"{self.f['hash3'][:2]}/{self.f['hash3'][2:4]}/{self.f['hash3'][4:6]}/{self.f['hash3'][6:]}")
+
+    def test_post_file_invalid(self):
+        response = client.post(f"/api/item_files/{self.f['item1'].id}/", self.f['local_user1'],
+                               {'data': self.f['encoded_content4']})
+        self.assertEqual(response.status_code, 400)
+
+    def test_post_file_not_found_item(self):
+        response = client.post(f"/api/item_files/99999/", self.f['local_user1'],
+                               {'data': self.f['encoded_content3'], 'mime_type': 'text/plain'})
+        self.assertEqual(response.status_code, 404)
+        self.assertEqual(File.objects.count(), 3)
+
+    def test_post_file_not_authenticated(self):
+        response = anonymous_client.post(f"/api/item_files/{self.f['item1'].id}/",
+                                         {'data': self.f['encoded_content3'], 'mime_type': 'text/plain'})
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(File.objects.count(), 3)
+
+    def test_post_file_not_authorized(self):
+        response = client.post(f"/api/item_files/{self.f['item1'].id}/", self.f['local_user2'],
+                               {'data': self.f['encoded_content3'], 'mime_type': 'text/plain'})
+        self.assertEqual(response.status_code, 404)
+        self.assertEqual(File.objects.count(), 3)
+
+    def test_delete_file(self):
+        response = client.delete(f"/api/item_files/{self.f['item1'].id}/{self.f['test_file1'].id}/",
+                                 self.f['local_user1'])
+        self.assertEqual(response.status_code, 204)
+        self.assertEqual(File.objects.count(), 3)
+        self.assertEqual(self.f['item1'].files.count(), 1)
+
+    def test_delete_file_last_use(self):
+        response = client.delete(f"/api/item_files/{self.f['item1'].id}/{self.f['test_file2'].id}/",
+                                 self.f['local_user1'])
+        self.assertEqual(response.status_code, 204)
+        self.assertEqual(File.objects.count(), 2)
+        self.assertEqual(self.f['item1'].files.count(), 1)
+
+    def test_delete_file_not_found(self):
+        response = client.delete(f"/api/item_files/{self.f['item1'].id}/99999/", self.f['local_user1'])
+        self.assertEqual(response.status_code, 404)
+        self.assertEqual(File.objects.count(), 3)
+        self.assertEqual(self.f['item1'].files.count(), 2)
+
+    def test_delete_file_not_found_item(self):
+        response = client.delete(f"/api/item_files/99999/{self.f['test_file1'].id}/", self.f['local_user1'])
+        self.assertEqual(response.status_code, 404)
+        self.assertEqual(File.objects.count(), 3)
+        self.assertEqual(self.f['item1'].files.count(), 2)
+
+    def test_delete_file_not_owner(self):
+        response = client.delete(f"/api/item_files/{self.f['item1'].id}/{self.f['test_file1'].id}/",
+                                 self.f['local_user2'])
+        self.assertEqual(response.status_code, 404)
+        self.assertEqual(File.objects.count(), 3)
+        self.assertEqual(self.f['item1'].files.count(), 2)
+
+    def test_delete_file_anonymous(self):
+        response = anonymous_client.delete(f"/api/item_files/{self.f['item1'].id}/{self.f['test_file1'].id}/")
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(File.objects.count(), 3)
+        self.assertEqual(self.f['item1'].files.count(), 2)
diff --git a/backend/toolshed/tests/test_inventory.py b/backend/toolshed/tests/test_inventory.py
index 5cf8014..d71e6a2 100644
--- a/backend/toolshed/tests/test_inventory.py
+++ b/backend/toolshed/tests/test_inventory.py
@@ -1,11 +1,12 @@
 from authentication.tests import SignatureAuthClient, UserTestMixin, ToolshedTestCase
+from files.tests import FilesTestMixin
 from toolshed.models import InventoryItem, Category
 from toolshed.tests import InventoryTestMixin, CategoryTestMixin, TagTestMixin, PropertyTestMixin
 
 client = SignatureAuthClient()
 
 
-class InventoryTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase):
+class InventoryApiTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase):
 
     def setUp(self):
         super().setUp()
@@ -77,6 +78,15 @@ class InventoryTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase):
         self.assertEqual([t for t in item.tags.all()], [])
         self.assertEqual([p for p in item.properties.all()], [])
 
+    def test_post_new_item_empty(self):
+        reply = client.post('/api/inventory_items/', self.f['local_user1'], {
+            'availability_policy': 'friends',
+            'owned_quantity': 1,
+            'image': '',
+        })
+        self.assertEqual(reply.status_code, 400)
+        self.assertEqual(InventoryItem.objects.count(), 2)
+
     def test_post_new_item3(self):
         reply = client.post('/api/inventory_items/', self.f['local_user1'], {
             'availability_policy': 'friends',
@@ -202,3 +212,84 @@ class InventoryTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase):
         reply = client.get('/api/search/?query=test', self.f['ext_user1'])
         self.assertEqual(reply.status_code, 200)
         self.assertEqual(len(reply.json()), 0)
+
+
+class TestInventoryItemWithFileApiTestCase(UserTestMixin, FilesTestMixin, InventoryTestMixin, ToolshedTestCase):
+    def setUp(self):
+        super().setUp()
+        self.prepare_users()
+        self.prepare_categories()
+        self.prepare_tags()
+        self.prepare_properties()
+        self.prepare_files()
+        self.prepare_inventory()
+
+    def test_post_item_with_file_id(self):
+        reply = client.post('/api/inventory_items/', self.f['local_user1'], {
+            'name': 'test4',
+            'description': 'test',
+            'category': 'cat1',
+            'owned_quantity': 1,
+            'tags': ['tag1', 'tag2'],
+            'properties': [{'name': 'prop1', 'value': 'value1'}, {'name': 'prop2', 'value': 'value2'}],
+            'files': [self.f['test_file1'].id]
+        })
+        self.assertEqual(reply.status_code, 201)
+        self.assertEqual(InventoryItem.objects.count(), 3)
+        item = InventoryItem.objects.get(id=3)
+        self.assertEqual(item.availability_policy, 'private')
+        self.assertEqual(item.category, Category.objects.get(name='cat1'))
+        self.assertEqual(item.name, 'test4')
+        self.assertEqual(item.description, 'test')
+        self.assertEqual(item.owned_quantity, 1)
+        self.assertEqual([t for t in item.tags.all()], [self.f['tag1'], self.f['tag2']])
+        self.assertEqual([p for p in item.properties.all()], [self.f['prop1'], self.f['prop2']])
+        self.assertEqual([p.value for p in item.itemproperty_set.all()], ['value1', 'value2'])
+        self.assertEqual([f for f in item.files.all()], [self.f['test_file1']])
+
+    def test_post_item_with_encoded_file(self):
+        reply = client.post('/api/inventory_items/', self.f['local_user1'], {
+            'name': 'test4',
+            'description': 'test',
+            'category': 'cat1',
+            'owned_quantity': 1,
+            'tags': ['tag1', 'tag2'],
+            'properties': [{'name': 'prop1', 'value': 'value1'}, {'name': 'prop2', 'value': 'value2'}],
+            'files': [{'data': self.f['encoded_content3'], 'mime_type': 'text/plain'}]
+        })
+        self.assertEqual(reply.status_code, 201)
+        self.assertEqual(InventoryItem.objects.count(), 3)
+        item = InventoryItem.objects.get(id=3)
+        self.assertEqual(item.availability_policy, 'private')
+        self.assertEqual(item.category, Category.objects.get(name='cat1'))
+        self.assertEqual(item.name, 'test4')
+        self.assertEqual(item.description, 'test')
+        self.assertEqual(item.owned_quantity, 1)
+        self.assertEqual([t for t in item.tags.all()], [self.f['tag1'], self.f['tag2']])
+        self.assertEqual([p for p in item.properties.all()], [self.f['prop1'], self.f['prop2']])
+        self.assertEqual([p.value for p in item.itemproperty_set.all()], ['value1', 'value2'])
+        self.assertEqual([f for f in item.files.all()], [self.f['test_file3']])
+
+    def test_post_item_with_file_id_fail(self):
+        reply = client.post('/api/inventory_items/', self.f['local_user1'], {
+            'name': 'test4',
+            'description': 'test',
+            'category': 'cat1',
+            'owned_quantity': 1,
+            'tags': ['tag1', 'tag2'],
+            'properties': [{'name': 'prop1', 'value': 'value1'}, {'name': 'prop2', 'value': 'value2'}],
+            'files': [99999]
+        })
+        self.assertEqual(reply.status_code, 400)
+
+    def test_post_item_with_encoded_file_fail(self):
+        reply = client.post('/api/inventory_items/', self.f['local_user1'], {
+            'name': 'test4',
+            'description': 'test',
+            'category': 'cat1',
+            'owned_quantity': 1,
+            'tags': ['tag1', 'tag2'],
+            'properties': [{'name': 'prop1', 'value': 'value1'}, {'name': 'prop2', 'value': 'value2'}],
+            'files': [{'data': self.f['encoded_content3']}]
+        })
+        self.assertEqual(reply.status_code, 400)
\ No newline at end of file