Inventory API Overview

There are several important concepts to understand before interfacing with the Inventory APIs - best practices for handling large amounts of data, how inventory levels are calculated, how inventory jobs are defined, and how segmentation tags are configured via API.

The Inventory API specs can be accessed here. Note that if you are using both the user interface and the API (such as for refresh or adjust requests), the "Product Code" field in the UI maps to the upc parameter in the API data.

Inventory API Best Practices

Due to the large amounts of data associated with inventory and the potential errors or slow performance when processing that data, there are some best practices that Kibo recommends for managing requests and responses.

Limiting Inquiry Responses

An inquiry request without a restriction on location may return a large number of results. The API can return up to n*100 results by default (where n = number of products in the request) but this may not be evenly split by 100 results per product. For instance, if there are two products then the response can include a maximum of 200 results; 50 results may be for one product while the other product has 150. Increase the value of the limit parameter in your inquiry request to change this setting if needed.

Sizing Adjust and Refresh Requests

Inventory Adjust and Refresh requests are placed into a queue of jobs that OMS processes one at a time. Due to this, the best practice is to submit fewer requests with more items per call, rather than numerous requests with a small number of items each. The Adjust API can accept up to a maximum of 1,000 items per call. The Refresh API can accept up to a maximum of 12,000 items per call. If these limits are reached, a Bad Request error will be returned.

Even though the Refresh API can support up to 12,000 items per call, Kibo recommends that you send requests in batches of 3,000 items for optimal performance.

Inventory Export Settings

You can use the Inventory API to configure your settings for the inventory export process to either S3 or FTP endpoints. There are three /create endpoints used to achieve this: a general endpoint, an S3-specific endpoint, and an FTP-specific endpoint.  You can also configure fetch files with the Save Fetch Config API.

You may use any of these, but the general endpoint allows you to fine-tune your settings by specifying specific location groups and sites that those configurations should apply to when inventory is being exported from those locations or sites. These specifications cannot be defined with the S3 and FTP specific endpoints.

Configuring these settings will determine how these files are delivered and received, but that will not activate any exports. To activate, Kibo Engineering must configure processes on Kibo's side as well. If you need to enable inventory imports and exports, contact Kibo Support.

Deallocating Items

Sometimes, you may be stuck with items in "Allocated" status. These are not automatically cleared by a Refresh Inventory call, so you must make a Deallocate Inventory request. This is a two-step process because deallocation requires you to specify which shipments contain those line items.

First, make a GET Shipments query for shipments that contain the allocated item. In this example, the item is a product variation with the code 1234.

GET /api/commerce/shipments/?filter=(items.variationProductCode=eq=1234) and shipmentStatus=ne=FULFILLED&fields=shipmentNumber,orderNumber,items.lineId,items.variationProductCode,items.quantity

Then you can use the shipment and order item IDs from the query response in the Deallocate call:

[
  {
    "orderID": 15,
    "orderItemID": 1,
    "shipmentID": 25,
    "locationCode": "examplelocation",
    "upc": "1234",
    "quantity": 1
 }
]

Inventory Quantity Types

It is also important to understand how inventory quantities are calculated while you are refreshing and adjusting inventory levels. The inclusion or exclusion of safety stock affects the total on hand and available inventory calculations. 

If safety stock is not included, then those values are calculated as follows:

  • On Hand = Allocated + Available + Pending
  • Available = On Hand – Allocated – Pending

If safety stock is included, then the calculations are:

  • On Hand = (Allocated + Available + Pending) – Safety Stock
  • Available = (On Hand – Allocated – Pending) – Safety Stock

For more details about inventory quantities, see the Inventory Quantity Types guide.

Understanding Inventory Jobs

A GET call to the Inventory Jobs API will retrieve the current status of a job, 

Once an inventory file has been uploaded for to process, call the API to find the correct job ID as well as the status of the job. Once the job has been found, continue calling the API to monitor the status of the inventory upload and see when the job has been successfully uploaded, as well as the number of items uploaded.

https://t{tenantId}.{host}/api/commerce/inventory/v1/queue/[Job ID]

However, there are also a number of fields that this particular API request can be filtered by to use the call as a query in which a “/?” is added to the call followed by parameters. Multiple search parameters are joined by the “&” symbol. This will return a list of jobs as the search results.

These filters and the values they support are:

FilterData TypeDescription
locationCodestringThe identifier of the location that the inventory job is applying to.
limitintegerThe maximum amount of search results that may be returned.
typesstring(s)The type(s) of job being performed, such as an inventory refresh or adjust, as either a single value or a list. See below for the full list of supported types.
originalFilenamestringThe original file name of the uploaded inventory data for the job.

Types can be a single string or a list of strings separated by commas. Both of the below examples are valid queries:

  • https://t{tenantId}.{host}/api/commerce/inventory/v1/queue/?types=REFRESH
  • https://t{tenantId}.{host}/api/commerce/inventory/v1/queue/?types=REFRESH, ADJUST

This is the full list of supported job types. The query is case-sensitive, so the type must always be fully capitalized.

  • REFRESH
  • ADJUST
  • DELETE
  • ALLOCATE
  • DEALLOCATE
  • FULFILL
  • RELEASE_SHIPMENTS
  • CREATE_PICK_WAVE
  • CLOSE_PICK_WAVE
  • PUT_AWAY_FILE
  • SHORT_PICK_ERROR
  • BIN_AUDIT_START
  • BIN_AUDIT_COMPLETION
  • CREATE_BIN_AUDIT
  • RELEASE_PENDING_ITEMS

Job Query Example

This example queries for all inventory refresh jobs at an “example” location.

https://t{tenantId}.{host}/api/commerce/inventory/v1/queue/?types=REFRESH&locationCode=example

After sending the request, the API responds with a collection of job objects as the search results.

This response may return a “locationID” – this is an internal field that may be removed in the future. Do not reference this parameter; you should only query by locationCode.

[
    {
        "jobID": 00000,
        "tenantID": 11111,
        "locationID": 01010,
        "type": "REFRESH",
        "added": "2020-03-13T21:01:48+0000",
        "started": "2020-03-13T21:01:48+0000",
        "finished": "2020-03-13T21:01:48+0000",
        "hasData": true,
        "itemCount": "1",
        "status": "SUCCESS",
        "success": true
    },
    {
        "jobID": 11111,
        "tenantID": 11111,
        "locationID": 01010,
        "type": "REFRESH",
        "added": "2020-03-13T19:59:58+0000",
        "started": "2020-03-13T19:59:58+0000",
        "finished": "2020-03-13T19:59:58+0000",
        "hasData": true,
        "itemCount": "1",
        "status": "SUCCESS",
        "success": true
    }
]

Segmentation in the API

Segmentation brings more flexibility to inventory management, allowing each inventory record to be separated into different categories to indicate that portions of its total quantity are intended for different channels, customer groups, fulfillment methods, or other needs. This allows for:

  • Setting a percentage of the quantity that would be available for each category. 
  • Setting discrete units at the location level as available for each category. 

Segmentation is determined with tags, which identify the groups that inventory must be split into. For example, tags could define how much of each inventory record is set aside for a certain sales channel: the Kibo storefront, Walmart, or Amazon. The percentages of the inventory allotted for each channel would add up to 100% – the Kibo storefront could have 80% of the inventory, Amazon 10%, and Walmart 10%.  

For more information about segmentation and how these configurations are reflected in the user interface, see the general Inventory Management feature guide.

Tag Categories

You should first create a category for your tags. For example, the below would create a category with three tags. Any number of additional tags could be added to the new category, and the percent values (the percentage of the total quantity that should be allotted to this tag value) could be excluded if they do not need to be enforced.

{
    "name": "Channel",
    "tenantID": 1,
    "tags": [
        {
            "tagValue": "Kibo",
            "isDefault": true,
            "percent": 50
        },
        {
            "tagValue": "Amazon",
            "isDefault": false,
            "percent": 25
        },
        {
            "tagValue": "Walmart",
            "isDefault": false,
            "percent": 25
        }
}

You can then retrieve, edit, and delete the category as necessary with additional POST (with the same request format), GET, and DELETE calls.

Tag Values

Once a tag category exists, tags can also be managed at the individual level rather than updating the entire category every time. However, this is still a call made to the Tag Category API – the name of the category is just appended, followed by the tag resource. For example, the below URL would add a tag to the Channel category that was just created:

../v1/tagCategory/Channel/tag

The tag would be defined in the request body (as with the categories, the percent is optional):

{
    "tagValue": "Ebay",
    "isDefault": false,
    "percent": 10,
}

You can then retrieve, edit, and delete the values as necessary with additional POST (with the same request format), GET, and DELETE calls.

Tags in the Inquiry Response

If tagged inventory is being utilized for inventory segmentation, then the future inventory data will be placed within the taggedInventory object of an inquiry. For example, this would be a full request with tagged and future inventory:

[
    {
        "locationName": "Example2",
        "locationCode": "00002",
        "tenantID": 11111,
        "onHand": 0,
        "available": 0,
        "allocated": 0,
        "pending": 0,
        "upc": "InventoryTagTest",
        "blockAssignment": false,
        "ltd": 0,
        "floor": 0,
        "safetyStock": 0,
        "distance": 0,
        "directShip": true,
        "transferEnabled": false,
        "pickup": true,
        "countryCode": "US",
        "currencyID": 1,
        "retailPrice": 1.99,
        "taggedInventory": [
            {
                "onHand": 0,
                "available": 0,
                "allocated": 0,
                "pending": 0,
                "tags": {
                    "HolidayItems": "HolidayTest1"
                },
                "futureInventory": [
                    {
                        "futureInventoryID": 3,
                        "quantity": 150,
                        "type": "Refresh",
                        "deliveryDate": "2021-03-20T17:00:00+0000",
                        "createDate": "2021-03-08T11:52:32+0000"
                    }
                ]
            }
        ]
    }
]

Tags in the Create Order Request

Multiple tag names and values can be passed in the Create Order call as part of order item data. This will allow the inventory, order routing, and fulfillment services to allocate inventory appropriately. The values included in the inventoryTags object will be validated against existing tags in the system, and if no tags are passed then the order item will be set to the default tag.

{
 "isTaxExempt": false,
 "email": "example@kibo.com",
 "ipAddress": "172.16.254.167",
 "type": "Offline",
 "externalId": "tags",
 "isEligibleForReturns": false,
 "totalCollected": 0,
 "attributes": [],
 "shippingDiscounts": [],
 "handlingDiscounts": [],
 "handlingTotal": 0,
 "notes": [],
 "items": [
  {
   "id": "eb75a8fa9a354e66ae32ada300c9c5a1",
   "fulfillmentLocationCode": "Loc1",
   "fulfillmentMethod": "Ship",
   "lineId": 1,
   "product": {
    "fulfillmentTypesSupported": [
     "DirectShip",
     "InStorePickup"
    ],
    "options": [],
    "properties": [],
    "categories": [],
    "price": {
     "price": 10
    },
    "discountsRestricted": false,
    "isTaxable": true,
    "productType": "AllUsage",
    "productUsage": "Standard",
    "bundledProducts": [],
    "productCode": "sp_01",
    "name": "sp_01",
    "goodsType": "Physical",
    "isPackagedStandAlone": false,
    "stock": {
     "manageStock": true,
     "isOnBackOrder": false,
     "stockAvailable": 100,
     "aggregateInventory": 0
    },
    "measurements": {
     "height": {
      "unit": "in",
      "value": 1
     },
     "width": {
      "unit": "in",
      "value": 1
     },
     "length": {
      "unit": "in",
      "value": 1
     },
     "weight": {
      "unit": "lbs",
      "value": 1
     }
    },
    "fulfillmentStatus": "PendingFulfillment"
   },
   "quantity": 1,
   "subtotal": 10,
   "extendedTotal": 10,
   "taxableTotal": 10,
   "discountTotal": 0,
   "discountedTotal": 10,
   "itemTaxTotal": 0,
   "shippingTaxTotal": 0,
   "shippingTotal": 0,
   "feeTotal": 0,
   "total": 10,
   "unitPrice": {
    "extendedAmount": 10,
    "listAmount": 10
   },
   "productDiscounts": [],
   "shippingDiscounts": [],
     "shippingAmountBeforeDiscountsAndAdjustments": 0,
   "weightedOrderAdjustment": 0,
   "weightedOrderDiscount": 0,
   "adjustedLineItemSubtotal": 10,
   "totalWithoutWeightedShippingAndHandling": 10,
   "weightedOrderTax": 0,
   "weightedOrderShipping": 15,
   "weightedOrderShippingDiscount": 0,
   "weightedOrderShippingManualAdjustment": 0,
   "weightedOrderShippingTax": 0,
   "weightedOrderHandlingFee": 0,
   "weightedOrderHandlingFeeTax": 0,
   "weightedOrderHandlingFeeDiscount": 0,
   "weightedOrderDuty": 0,
   "totalWithWeightedShippingAndHandling": 10,
   "weightedOrderHandlingAdjustment": 0,
   "isAssemblyRequired": false,
   "inventoryTags": [{
				"name": "Online",
				"value": "Flipkart"
			},
			{
				"name": "Offline",
				"value": "OnlinePurchase"
			}
		]
  }
 ],
 "validationResults": [],
 "billingInfo": {
  "billingContact": {
   "id": 1000,
   "email": "example@kibo.com",
   "firstName": "anagha",
   "middleNameOrInitial": "",
   "lastNameOrSurname": "deshmukh",
   "companyOrOrganization": "Test",
   "phoneNumbers": {
    "home": "5129991111",
    "mobile": "",
    "work": ""
   },
   "address": {
    "address1": "717 N. Harwood St.",
    "address2": "",
    "address3": "",
    "address4": "",
    "cityOrTown": "Dallas",
    "stateOrProvince": "TX",
    "postalOrZipCode": "75201",
    "countryCode": "US",
    "addressType": "Residential",
    "isValidated": false
   }
  },
  "isSameBillingShippingAddress": false

 },
 "payments": [
  {
         "paymentType": "CreditCard",
         "paymentWorkflow": "Mozu",
         "billingInfo": {
            "paymentType": "CreditCard",
            "billingContact": {
               "email": "sam.billing@email.com",
               "firstName": "Sam",
               "middleNameOrInitial": "",
               "lastNameOrSurname": "Billing",
               "phoneNumbers": {
                  "home": "1234567895",
                  "mobile": "1234567895",
                  "work": ""
               },
               "address": {
                  "address1": "1845 Kramer Ln",
                  "address2": "",
                  "address3": "",
                  "address4": "",
                  "cityOrTown": "Austin",
                  "stateOrProvince": "TX",
                  "postalOrZipCode": "78758",
                  "countryCode": "US",
                  "addressType": "Residential",
                  "isValidated": false
               }
            },
            "isSameBillingShippingAddress": false,
            "card": {
               "isUsedRecurring": false,
               "nameOnCard": "Sam Billing",
               "isCardInfoSaved": false,
               "paymentOrCardType": "VISA",
               "cardNumberPartOrMask": "4111111111111111",
               "isTokenized": true,
               "expireMonth": 1,
               "expireYear": 2023
            },
            "auditInfo": {
               "updateDate": "2018-09-17T17:54:21.027Z",
               "createDate": "2018-09-17T17:53:24.598Z",
               "updateBy": "355060a60a5e48eeb7f2fb8d92af2ba5",
               "createBy": "355060a60a5e48eeb7f2fb8d92af2ba5"
            }
         },
         "status": "Authorized",
         "subPayments": [],
         "interactions": [
            {
               "currencyCode": "USD",
               "interactionType": "Authorization",
               "status": "Authorized",
               "paymentEntryStatus": "New",
               "isRecurring": false,
               "isManual": false,
               "gatewayTransactionId": "40018563192",
               "gatewayAuthCode": "ASQLVP",
               "gatewayAVSCodes": "Y",
               "gatewayCVV2Codes": "P",
               "gatewayResponseCode": "1",
               "gatewayResponseText": "This transaction has been approved.",
               "gatewayResponseData": [
                  {
                     "key": "AuthorizationRequestId",
                     "value": "5642607567736184003013"
                  },
                  {
                     "key": "AuthorizationRequestToken",
                     "value": "Ahj/7wSTMeLpFwo2JFnENyzVs0ZNmDdq2bt2bZi4aMGDNgxZKNP3ock7AVGn70OSd6QNnBx/EMmkmW6QHez7QJyZjxdIuFGxIs4gLxmU"
                  },
                  {
                     "key": "currencyCode",
                     "value": "USD"
                  }
               ],
               "amount": 65.1,
               "interactionDate": "2018-09-17T17:54:21.098Z"
            }
         ],
         "isRecurring": false,
         "amountCollected": 0,
         "amountCredited": 0,
         "amountRequested": 65.1
      }
 ],
 "refunds": [],
 "credits": [],
 "packages": [],
 "pickups": [],
 "digitalPackages": [],
 "isDraft": false,
 "hasDraft": false,
 "isImport": true,
 "isHistoricalImport": false,
 "isUnified": true,
 "couponCodes": [],
 "invalidCoupons": [],
 "amountAvailableForRefund": 0,
 "amountRemainingForPayment": 0,
 "amountRefunded": 0,
 "readyToCapture": false,
 "isOptInForSms": false,
 "userId": "82bb750a90d34ed1b1ddc2c534293773",
 "id": "1240f01063e4ce00014e0aa7000047f0",
 "tenantId": 18416,
 "siteId": 23245,
 "channelCode": "Online",
 "currencyCode": "USD",
 "customerInteractionType": "Unknown",
 "fulfillmentInfo": {
  "fulfillmentContact": {
   "id": 1000,
   "email": "example@kibo.com",
   "firstName": "anagha",
   "middleNameOrInitial": "",
   "lastNameOrSurname": "deshmukh",
   "companyOrOrganization": "Test",
   "phoneNumbers": {
    "home": "5129991111",
    "mobile": "",
    "work": ""
   },
   "address": {
    "address1": "717 N. Harwood St.",
    "address2": "",
    "address3": "",
    "address4": "",
    "cityOrTown": "Dallas",
    "stateOrProvince": "TX",
    "postalOrZipCode": "75201",
    "countryCode": "US",
    "addressType": "Residential",
    "isValidated": false
   }
  },
  "shippingMethodCode": "d0fb5d2e4fc047d596aaada300c05893",
  "shippingMethodName": "Flat Rate",
  "auditInfo": {
   "updateDate": "2021-09-14T12:14:46.532Z",
   "createDate": "2021-09-14T12:14:08.129Z",
   "updateBy": "355060a60a5e48eeb7f2fb8d92af2ba5",
   "createBy": "355060a60a5e48eeb7f2fb8d92af2ba5"
  }
 },
 "orderDiscounts": [],
 "suggestedDiscounts": [],
 "subtotal": 10,
 "discountedSubtotal": 10,
 "discountTotal": 0,
 "discountedTotal": 10,
 "shippingTotal": 15,
 "shippingSubTotal": 15,
 "shippingTaxTotal": 0,
 "handlingTaxTotal": 0,
 "itemTaxTotal": 0,
 "taxTotal": 0,
 "feeTotal": 0,
 "total": 10,
 "lineItemSubtotalWithOrderAdjustments": 10,
 "shippingAmountBeforeDiscountsAndAdjustments": 15,
 "lastValidationDate": "2021-09-14T12:14:42.613Z",
 "extendedProperties": [],
 "discountThresholdMessages": [],
 "auditInfo": {
  "updateDate": "2021-09-14T12:15:03.682Z",
  "createDate": "2021-09-14T12:14:08.162Z",
  "updateBy": "355060a60a5e48eeb7f2fb8d92af2ba5",
  "createBy": "355060a60a5e48eeb7f2fb8d92af2ba5"
 }
}