Inventory Management Integration

This guide will explain the necessary steps to correctly implement a successful inventory management software solution into Zentail.

This guide will explain the steps to integrate your Inventory Management Software solution with Zentail. The primary tasks of the integration are to:

  • Inform Zentail about changes to a Product's quantity in one or more Warehouses.
  • Retrieve Sales Orders to reserve Inventory for Pending Sales.

This guide assumes you are familiar with registering an application and Authentication.

Get Warehouses

When a Zentail Customer installs your Application, they will also associate Warehouses in Zentail with Warehouse Unique Identifiers from your Inventory Management Software. This can be confirmed with an API call to view all of the Warehouses associated with your Integration:

curl -X GET \
    -H "Accept: application/json" \
    -H "Authorization: <bearer token>" \
    "https://api.zentail.com/v1/warehouses"

Example Response:

{
	"results": [
		{
			"warehouseId": 3,						// Zentail ID for the Warehouse
			"warehouseUniqueId": "Warehouse 0001",	// Your unique id for the warehouse
			"name": "Main Warehouse",				// Customer's name for the Warehouse in Zentail
			"canUpdateInventory": true				// deprecated
		}
	]
}

Get Inventory Levels

The current inventory levels for a given SKU can be examined:

curl -X GET \
    -H "Accept: application/json" \
    -H "Authorization: <bearer token>" \
    "https://api.zentail.com/v1/inventory/<SKU>"

Or for multiple SKUs:

curl -X GET \
    -H "Accept: application/json" \
    -H "Authorization: <bearer token>" \
    "https://api.zentail.com/v1/inventory?skus='<SKU1>,<SKU2>'"

Example Response:

{
	"results": [
		{
			"SKU": "TESTSKU1",
			"active": true,
			"standardProductId": null,
			"standardProductIdType": "UPC",
			"totalAvailableQuantity": 23,
			"warehouses": [
				{
					"warehouseName": "Warehouse One",
					"warehouseUniqueId": "Warehouse1",
					"availableQuantity": 13,
					"assembledQuantity": null,
					"inventoryLocations": [],
					"updatedTs": "2021-01-21T14:24:24-05:00",
					"inboundShipments": []
				},
				{
					"warehouseName": "Warehouse Two",
					"warehouseUniqueId": "Warehouse2",
					"availableQuantity": 10,
					"assembledQuantity": null,
					"inventoryLocations": [],
					"updatedTs": "2021-01-21T14:24:24-05:00",
					"inboundShipments": []
				}
			],
			"lastInventoryUpdateTs": "2021-01-21T14:24:24-05:00"
		},
		{
			"SKU": "TESTSKU2",
			"active": true,
			"standardProductId": null,
			"standardProductIdType": "UPC",
			"totalAvailableQuantity": 12,
			"warehouses": [
				{
					"warehouseName": "Warehouse Two",
					"warehouseUniqueId": "Warehouse2",
					"availableQuantity": 12,
					"assembledQuantity": null,
					"inventoryLocations": [],
					"updatedTs": "2021-01-21T14:30:20-05:00",
					"inboundShipments": []
				}
			],
			"lastInventoryUpdateTs": "2021-01-21T14:30:20-05:00"
		}
	],
	"pagination": {
		"nextToken": null,
		"hasNext": false
	},
	"errors": []
}

Update Inventory Levels

Make a POST call to inform Zentail about changes in inventory levels:

curl -X POST
    -H "Accept: application/json" \
    -H "Content-Type: application/json" \
    -H "Authorization: <bearer token>" \
    "https://api.zentail.com/v1/inventory"
    -d '{
      "products": [
        {
          "SKU": "TESTSKU1",
          "quantity": 10,
          "binLocation": "BIN-1",
          "warehouseUniqueId": "Warehouse1"
        },
        {
          "SKU": "TESTSKU1",
          "quantity": 4,
          "binLocation": "BIN-1",
          "warehouseUniqueId": "Warehouse2"
        },
        {
          "SKU": "TESTSKU2",
          "quantity": 6,
          "binLocation": "BIN-2",
          "warehouseUniqueId": "Warehouse2"
        }
      ]
    }'

Example Response:

[
	{
		"SKU": "TESTSKU1",
		"standardProductId": null,
		"standardProductIdType": "UPC",
		"totalAvailableQuantity": 14,
		"lastInventoryUpdateTs": "2021-01-21T14:24:24-05:00",
		"errorMessage": ""
	},
	{
		"SKU": "TESTSKU2",
		"standardProductId": null,
		"standardProductIdType": "UPC",
		"totalAvailableQuantity": 6,
		"lastInventoryUpdateTs": "2021-01-21T14:30:20-05:00",
		"errorMessage": ""
	}
]

Reserve Inventory

When a customer has pending Sales Orders, it is important to mark Inventory as reserved. This allocates the inventory towards fulfilling the order. Once the inventory is reserved, it should be communicated back to Zentail, by decreasing the quantity for the SKUs with an update inventory call. This tells Zentail that the quantity was reserved, and allows Zentail to update any Sales Channel, to prevent overselling. If a Sale Order is canceled, the reserved inventory should be reclaimed. This is done by polling the salesOrder endpoint:

curl -X GET
    -H "Accept: application/json" \
    -H "Authorization: <bearer token>" \
    "https://api.zentail.com/v1/salesOrder?status=PENDING_PAYMENT,PENDING,CANCELLED&lastUpdatedTs=2021-01-22T16:15:46.740Z"

Example Response:

{
	"results": [
		{
			"orderNumber": "1000004",
			"status": "PENDING_PAYMENT",
			"channel": "CustomOrderChannel",
			"channelLabel": "Custom 51",
			"channelOrderId": "0000012",
			"channelOrderReferenceNumber": null,
			"customer_notes": null,
			"marketplaceId": null,
			"orderTs": "2021-01-22T09:01:30-05:00",
			"lastUpdatedTs": "2021-01-22T09:01:48-05:00",
			"requestedServiceLevel": "Standard",
			"standardServiceLevel": "Standard",
			"shipBy": null,
			"accounting": {
				"payment": 31.42,
				"channelTax": 0,
				"resellerCommission": 0,
				"shippingCost": 0,
				"itemCost": 0,
				"totalCost": 0,
				"revenue": 31.42,
				"profit": 31.42,
				"margin": 1,
				"itemPrice": 20.6,
				"shippingPrice": 0,
				"refund": 0
			},
			"packages": [],
			"products": [
				{
					"lineItemId": "1",
					"status": "PENDING_PAYMENT",
					"requestedTitle": null,
					"requestedSku": "TESTSKU1",
					"SKU": "TESTSKU1",
					"mpn": null,
					"standard_product_id": null,
					"title": "TEST SKU 1",
					"quantity": 2,
					"routing_info": [
						{
							"warehouseId": 3,
							"quantity": 2,
							"assembledQuantity": null,
							"kitComponents": null,
							"warehouseUniqueId": "Warehouse1"
						}
					],
					"cancelQuantity": null,
					"shippedQuantity": 0,
					"unitPrice": 4.12,
					"cost": null,
					"totalWeight": 1572,
					"refund": null,
					"refundReason": null,
					"giftWrapMessage": null,
					"giftWrapLevel": null,
					"earliestShipBy": null,
					"latestShipBy": null,
					"earliestDeliverBy": null,
					"latestDeliverBy": null,
					"requestedServiceLevel": "Standard"
				}
			],
			"returnOrders": [],
			"fba": null,
			"prime": false,
			"businessOrder": false
		}
	],
	"pagination": {
		"nextToken": null,
		"hasNext": false
	}
}

Order Data

At the root of the order, there is some important information:

FieldRecommended Use
orderNumberThis is the Zentail order number, use this as the unique identifier for this order for this particular account
statusAn indicator of the overall status of the order, PENDING_PAYMENT and PENDING orders should trigger the inventory to be reserved. For a full list of statuses see the docs

Line Items

If you now examine the products field of the order response, there is more important information:

FieldRecommended Use
lineItemIdAn identifier unique to this order which represents this specific line item on the order.
statusSimilar to status above, this is a more granular indication of the status of this individual line item.
requestedSkuThis is the SKU requested by the sales channel, this may be a SKU that doesn't exist in Zentail. It also may not match the SKU that Zentail has chosen to fulfill this order (see the SKU field for this).
SKUThis is the SKU Zentail chose to fulfill this line item. It is possible that this field is NULL if the line item does not correspond to a known product in Zentail. In that case, requestedSku should be used as a fallback.
quantityThis is the quantity that was requested to fulfill this line item. If a warehouse ID filter is provided, this filed will only contain the quantity routed to that warehouse.
cancelQuantityThis is the quantity that has been cancelled, this should be deducted from the quantity field above when determining how many items still need to be shipped.
shippedQuantityThis is the quantity that has already been shipped.

Kits / Multi-packs

Multi-packs or Kits may require additional consideration to account for inventory changes from sales. A customer may have a "Kit SKU" that is not present in the Warehouse, but derives its inventory from one or more SKUs that are in the Warehouse.

As an example, Warehouse1 stocks SKU TESTSKU1. The customer sells this SKU directly, as well as a multi-pack version with sku TESTSKU1-2PK. An order is placed like the following (note some fields are omitted for brevity):

{
    "orderNumber": "1000005",
    "status": "PENDING_PAYMENT",
    "orderTs": "2021-01-22T09:39:57-05:00",
    "lastUpdatedTs": "2021-01-22T09:39:57-05:00",
    "products": [
        {
            "lineItemId": "0",
            "status": "PENDING_PAYMENT",
            "requestedSku": "TESTSKU1-2PK",
            "SKU": "TESTSKU1-2PK",
            "quantity": 2,
            "routing_info": [
                {
                    "warehouseId": 3,
                    "quantity": 2,
                    "assembledQuantity": 0,
                    "kitComponents": [
                        {
                            "componentQuantity": 2,
                            "SKU": "TESTSKU1"
                        }
                    ],
                    "warehouseUniqueId": "Warehouse1"
                }
            ]
        }
    ]
}

To determine the correct quantity to reserve, routing_info fields must be utilized. In this example, an order was placed for 2 of TESTSKU1-2PK as indicated by the quantity. The routing_info["quantity"] indicates that both of the requested orders for TESTSKU1-2PK were routed to the warehouse Warehouse1. The routing_info["kitComponents"][0]["componentQuantity"] indicates that each TESTSKU1-2PK is composed of 2 of TESTSKU1. Thus the total amount of inventory to reserve is 4 of TESTSKU1, or routing_info["quantity"] * routing_info["kitComponets"][0]["componentQuantity"].

Also make note of the routing_info["assembledQuantity"] field. If your Warehouse tracks Kits, and provides Zentail with the assembled quantity via the update inventory requests, this field will include the amount of already assembled units that should be reserved. As an example, consider the following (note, again some fields are omitted for brevity):

{
    "orderNumber": "1000005",
    "status": "PENDING_PAYMENT",
    "orderTs": "2021-01-22T09:39:57-05:00",
    "lastUpdatedTs": "2021-01-22T09:39:57-05:00",
    "products": [
        {
            "lineItemId": "0",
            "status": "PENDING_PAYMENT",
            "requestedSku": "TESTSKU1-2PK",
            "SKU": "TESTSKU1-2PK",
            "quantity": 2,
            "routing_info": [
                {
                    "warehouseId": 3,
                    "quantity": 2,
                    "assembledQuantity": 1,
                    "kitComponents": [
                        {
                            "componentQuantity": 2,
                            "SKU": "TESTSKU1"
                        }
                    ],
                    "warehouseUniqueId": "Warehouse1"
                }
            ]
        }
    ]
}

In this case there is already an assembled TESTSKU1-2PK. So one unit of that assembled SKU should be reserved, and the remaining quantity should be reserved as 2 units of the component SKU, TESTSKU1

Was this section helpful? Yes No