diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py
index a834989fd9..40819a3bff 100644
--- a/InvenTree/order/api.py
+++ b/InvenTree/order/api.py
@@ -11,6 +11,9 @@ from django_filters.rest_framework import DjangoFilterBackend
 from rest_framework import generics
 from rest_framework import filters, status
 from rest_framework.response import Response
+from rest_framework.serializers import ValidationError
+
+from django.utils.translation import ugettext_lazy as _
 
 from InvenTree.helpers import str2bool
 from InvenTree.api import AttachmentMixin
@@ -27,6 +30,7 @@ from .models import SalesOrder, SalesOrderLineItem, SalesOrderAllocation
 from .models import SalesOrderAttachment
 from .serializers import SalesOrderSerializer, SOLineItemSerializer, SOAttachmentSerializer
 from .serializers import SalesOrderAllocationSerializer
+from .serializers import POReceiveSerializer, POLineItemReceiveSerializer
 
 
 class POList(generics.ListCreateAPIView):
@@ -204,6 +208,41 @@ class PODetail(generics.RetrieveUpdateDestroyAPIView):
         return queryset
 
 
+class POReceive(generics.CreateAPIView):
+    """
+    API endpoint to receive stock items against a purchase order.
+
+    - The purchase order is specified in the URL.
+    - Items to receive are specified as a list called "items" with the following options:
+        - supplier_part: pk value of the supplier part
+        - quantity: quantity to receive
+        - status: stock item status
+        - location: destination for stock item (optional)
+    - A global location can also be specified
+    """
+
+    queryset = PurchaseOrderLineItem.objects.none()
+
+    serializer_class = POReceiveSerializer
+
+    def get_order(self):
+        """
+        Returns the PurchaseOrder associated with this API endpoint
+        """
+
+        order = PurchaseOrder.objects.get(pk=self.kwargs['pk'])
+
+        return order
+
+    def create(self, request, *args, **kwargs):
+        # Validate the serialized data
+        serializer = self.get_serializer(data=request.data)
+        serializer.is_valid(raise_exception=True)
+
+        headers = self.get_success_headers(serializer.data)
+        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
+
+
 class POLineItemList(generics.ListCreateAPIView):
     """ API endpoint for accessing a list of POLineItem objects
 
@@ -616,13 +655,25 @@ class POAttachmentDetail(generics.RetrieveUpdateDestroyAPIView, AttachmentMixin)
 
 
 order_api_urls = [
+
     # API endpoints for purchase orders
-    url(r'po/attachment/', include([
-        url(r'^(?P<pk>\d+)/$', POAttachmentDetail.as_view(), name='api-po-attachment-detail'),
-        url(r'^.*$', POAttachmentList.as_view(), name='api-po-attachment-list'),
+    url(r'^po/', include([
+
+        # Purchase order attachments
+        url(r'attachment/', include([
+            url(r'^(?P<pk>\d+)/$', POAttachmentDetail.as_view(), name='api-po-attachment-detail'),
+            url(r'^.*$', POAttachmentList.as_view(), name='api-po-attachment-list'),
+        ])),
+
+        # Individual purchase order detail URLs
+        url(r'^(?P<pk>\d+)/', include([
+            url(r'^receive/', POReceive.as_view(), name='api-po-receive'),
+            url(r'.*$', PODetail.as_view(), name='api-po-detail'),
+        ])),
+
+        # Purchase order list
+        url(r'^.*$', POList.as_view(), name='api-po-list'),
     ])),
-    url(r'^po/(?P<pk>\d+)/$', PODetail.as_view(), name='api-po-detail'),
-    url(r'^po/.*$', POList.as_view(), name='api-po-list'),
 
     # API endpoints for purchase order line items
     url(r'^po-line/(?P<pk>\d+)/$', POLineItemDetail.as_view(), name='api-po-line-detail'),
diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py
index dd35eb3cb8..f090faf214 100644
--- a/InvenTree/order/serializers.py
+++ b/InvenTree/order/serializers.py
@@ -186,6 +186,13 @@ class POLineItemReceiveSerializer(serializers.Serializer):
         help_text=_('Select destination location for received items'),
     )
 
+    quantity = serializers.DecimalField(
+        max_digits=15,
+        decimal_places=5,
+        min_value=0,
+        required=True,
+    )
+
     class Meta:
         fields = [
             'supplier_part',
@@ -198,8 +205,8 @@ class POReceiveSerializer(serializers.Serializer):
     Serializer for receiving items against a purchase order
     """
 
-    items = serializers.StringRelatedField(
-        many=True
+    items = POLineItemReceiveSerializer(
+        many=True,
     )
 
     location = serializers.PrimaryKeyRelatedField(
@@ -220,9 +227,10 @@ class POReceiveSerializer(serializers.Serializer):
         items = data.get('items', [])
 
         if len(items) == 0:
-            raise ValidationError({
-                'items': _('Line items must be provided'),
-            })
+            self._errors['items'] = _('Line items must be provided')
+
+        if self._errors and raise_exception:
+            raise ValidationError(self.errors)
 
         return not bool(self._errors)
 
diff --git a/InvenTree/order/test_api.py b/InvenTree/order/test_api.py
index 24ca8581d9..5ab5230d8e 100644
--- a/InvenTree/order/test_api.py
+++ b/InvenTree/order/test_api.py
@@ -201,6 +201,73 @@ class PurchaseOrderTest(OrderTest):
         response = self.get(url, expected_code=404)
 
 
+class PurchaseOrderReceiveTest(OrderTest):
+    """
+    Unit tests for receiving items against a PurchaseOrder
+    """
+
+    def setUp(self):
+        super().setUp()
+        
+        self.assignRole('purchase_order.add')
+
+        self.url = reverse('api-po-receive', kwargs={'pk': 1})
+
+    def test_empty(self):
+        """
+        Test without any POST data
+        """
+
+        data = self.post(self.url, {}, expected_code=400).data
+
+        self.assertIn('This field is required', str(data['items']))
+        self.assertIn('This field is required', str(data['location']))
+
+    def test_no_items(self):
+        """
+        Test with an empty list of items
+        """
+
+        data = self.post(
+            self.url,
+            {
+                "items": [],
+                "location": None,
+            },
+            expected_code=400
+        ).data
+
+        self.assertIn('Line items must be provided', str(data['items']))
+
+    def test_invalid_items(self):
+        """
+        Test than errors are returned as expected for invalid data 
+        """
+
+        data = self.post(
+            self.url,
+            {
+                "items": [
+                    {
+                        "supplier_part": 12345,
+                        "location": 12345
+                    }
+                ]
+            },
+            expected_code=400
+        ).data
+
+        items = data['items']
+
+        self.assertIn('Invalid pk "12345"', str(items['supplier_part']))
+        self.assertIn("object does not exist", str(items['location']))
+
+    def test_mismatched_items(self):
+        """
+        Test for supplier parts which *do* exist but do not match the order supplier
+        """
+
+
 class SalesOrderTest(OrderTest):
     """
     Tests for the SalesOrder API