Adaco Inventory Guide

Introduction

The Inventory resources enable you to manage stock levels across outlets using near real-time inventory data. Inventory items are typically either products or menu items (recipes).

Quick Facts

Integration type HTTP REST with JSON
Authentication Basic Authentication
Availability Fourth Inventory for Hotels customers
Testing Test environment is available on request
More information Visit the Adaco Web API Reference 

Updates

You can find any updates on the Release Notes page for the Adaco Web API.

Get Access

Before integrating, you'll need to set up a new user account in Fourth with access rights to the API and resource.

Requests to the these resources are not restricted by property. This means you can retrieve inventory data for properties in which the user account is not present.

Step 1: create a user group 

Create a user group for accessing the API within one of your Fourth properties — we recommend the central purchasing property where there is one. 

The Group Details screen in Adaco. Under "Edit Permissions", the "API Access" option is ticked.

For security, we also recommend that you limit the user group to access to just the “Inventory API”. The setting for this is within the user group's Property > API Access sub-section. Select the View option to provide access to GET requests, and Create for POST requests.

Screenshot of the "Access Rights" pop-up window, with the Inventory API row highlighted.

Step 2: create a user account

In the same property, create a user account. Assign it to the API user group.

Resources

Base path

The base path for all requests is made of:

  • The domain name, referenced as <ROOT> below. The domain may be to the Fourth cloud or to a customer-owned domain.
  • A customer-specific customerName — this is the same name in the URL that is used when the customer logs into Fourth Inventory for Hotels. You must also add "Service" to the name; e.g. "acmeService".

That is:

<ROOT>/{customerName}Service/WebApi/

ItemInventory endpoints

GET request

GET <ROOT>/{customerName}Service/WebApi/ItemInventory/{propertyNumber}/{itemNumber}

This endpoint gets the stock levels in each outlet for the specified item. Benefits include:

  • Keeping track as to whether a specific item is low in stock across some or all outlets.
  • Checking the stock levels in outlets marked as "inventory outlets", which are normally the store rooms from which other outlets can requisition items.

Visit the Adaco Web API Reference to explore this endpoint. You can find an example response below.

OutletInventory endpoints

GET request

GET <ROOT>/{customerName}Service/WebApi/OutletInventory/{propertyNumber}/{outletNumber}

This endpoint gets near real-time inventory levels for an outlet. Benefits include:

  • Validating the opening and period-end stock to reconcile against physical stock
  • Monitoring the use of products within recipes to assess the popularity of products during the period

You can find an example response below.

POST request

POST <ROOT>/{customerName}Service/WebApi/OutletInventory/{propertyNumber}

This endpoint creates an inventory adjustment. Benefits include:

  • Ensuring that stock movements not recorded in Adaco on busy days are easily updated via an integrated third-party system
  • Flexibility to either send in the exact value of the stock on-hand, or an adjustment by addition/subtraction

You can find an example request and response below.

Visit the Adaco Web API Reference to explore these endpoints.

Getting the property number

You’ll need the property number of the relevant Adaco properties before you can make an API request. You can find the property numbers in the Fourth UI. 

Screenshot with the Property Number highlighted

Product Details

Product Details allow the recording of specific information about the product. By creating multiple Product Details, the information can differ but is still assigned to one product, allowing you to have variants of the same product (or ingredient). The overall nutrition and ingredient details should otherwise be the same. This is often used for a product that is available in different pack sizes; for example, tinned tomatoes, fresh vegetables, branded products like Nutella, and drinks can come in multiple volumes and packages.

If an outlet has a product with two or more Product Details, then the API will return each Product Detail as a separate record in the response. Each record is identified by the DetailNumber field in response.

Purchase and inventory units

These endpoints return data about both purchase and inventory units. Please ensure that you read and understand the field descriptions as these unit types are normally for different quantities:

  • Inventory units are most often individual items that you might expect to sell or use in a recipe; for example, 1 bottle of wine, 200mls of soup, 1 onion
  • Purchase units are most often larger volumes that outlets use when purchasing from suppliers; for example, a case of wine, 5 litres of soup, 15kgs of onions

You can find a list of unit codes and descriptions on the Units of Measure page.

Request header

For all requests, you must provide your authentication details using Basic authentication in the header. Example header:

POST /acmeService/WebApi/OutletInventory/123  HTTP/1.1
Host: instance.example.com
Authorization: Basic VXNlcm5hbWU6cGFzc3dvcmQ=
Content-Type: application/json
Field Description
Authorization Your Inventory API username and password, separated by a colon, and then base64 encoded. Your ID and password are case-sensitive. 
Content-Type The data format you are using for POST request. Options are: application/json, text/json.

Example response to GET ItemInventory

This example shows a product with multiple product details.

{
    "ProductNumber": 1224,
    "ProductDescription": "PEPPERONI SLCD",
    "ProductDetails":[
        {
            "DetailNumber": 1,
            "InventoryUnitCode": "BG",
            "InventoryUnitDescription": "BAG",
            "InventoryDescription": "5lb bag, 2 bags per case",
            "Outlets":[
                {
                    "OutletNumber": 101,
                    "OutletName": "CATERING WAREHOUSE",
                    "HasInventory": true,
                    "OnHand": 24.05
                },
                {
                    "OutletNumber": 1103,
                    "OutletName": "PIZZA PLACE",
                    "HasInventory": false,
                    "OnHand": 16.875
                },
                {
                    "OutletNumber": 1137,
                    "OutletName": "CHIP HOUSE",
                    "HasInventory": true,
                    "OnHand": 0
                }
            ]
        },
        {
            "DetailNumber": 2,
            "InventoryUnitCode": "LB",
            "InventoryUnitDescription": "POUND",
            "InventoryDescription": "lb",
            "Outlets":[
                {
                    "OutletNumber": 101,
                    "OutletName": "CATERING WAREHOUSE",
                    "HasInventory": true,
                    "OnHand": 2.05
                },
                {
                    "OutletNumber": 1133,
                    "OutletName": "PIZZA PLACE",
                    "HasInventory": false,
                    "OnHand": 13.255
                },
                {
                    "OutletNumber": 1127,
                    "OutletName": "CHIP HOUSE",
                    "HasInventory": true,
                    "OnHand": 10
                }
            ]
        }
    ]
}

Example response to GET OutletInventory

The following examples are for a product (first) and menu item (second).

Example for product, including recipe usage and on order data

{
    "PageSize": 20,
    "Page": 1,
    "Total": 4,
    "Items": [
        {
            "OutletNumber": 222,
            "OutletName": "Pizza Place",
            "ItemNumber": 116,
            "DetailNumber": 2,
            "ItemType": "P",
            "ItemDescription": "BAG POPCORN BULK",
            "InventoryUnitCode": "EA",
            "InventoryUnitDescrition": "EACH",
            "InventoryDescription": "500 BAG",
            "PurchaseUnitCode": "CA",
            "PurchaseUnitDescription": "CASE",
            "PurchaseDescription": "500 BAG CASE",
            "OutletReorder": 12,
            "PurchaseReorder": 6,
            "OutletPar": 24,
            "PurchasePar": 12,
            "OnHand": 127,
            "OnOrder": 24,
            "RecipeUsage": 15,
            "InventoryCost": 1.75,
    
        }
    ]
}

Example for recipe made at the outlet

{
    "PageSize": 20,
    "Page": 1,
    "Total": 4,
    "Items": [
        {
            "OutletNumber": 103,
            "OutletName": "Pizza Place",
            "ItemNumber": 173,
            "DetailNumber": 0,
            "ItemType": "R",
            "ItemDescription": "Cheeseburger and chips",
            "InventoryUnitCode": "EA",
            "InventoryUnitDescrition": "EACH",
            "InventoryDescription": "1 each",
            "PurchaseUnitCode": "EA",
            "PurchaseUnitDescription": "EACH",
            "PurchaseDescription": "12 x each",
            "OutletReorder": 0,
            "PurchaseReorder": 0,
            "OutletPar": 0,
            "PurchasePar": 0,
            "OnHand": 0,
            "OnOrder": 0,
            "RecipeUsage": 0,
            "InventoryCost": 0
        }
    ]
}

Example request to POST OutletInventory

This example shows an adjustment to a product and menu item at the same outlet.

[
    {
        "OutletNumber": 200,
        "EffectiveDate": "2022-07-13",
        "AdjustmentType": 2,
        "AdjustmentItems": [
            {
               "ItemNumber": 646,
               "ProductDetailNumber": 1,
               "ItemType": "P",
               "Quantity": 101,
               "QuantityType": "C",
               "ItemComment": "Stock check correction"
           },
           {
               "ItemNumber": 637,
               "ProductDetailNumber": 0,
               "ItemType": "R",
               "Quantity": -12,
               "QuantityType": "A",
               "ItemComment": "Over production"
           }
       ]
    }
]

See Response to POST requests for an example response.

Response to GET requests

Successful requests

Successfully submitted requests receive an HTTP 200 OK response.

You can find a full description and example in the Adaco Web API Reference.

Unsuccessful requests

Unsuccessful requests receive an HTTP 400-599 response, with an error message in the response body.

Response to POST requests

Successful requests

Successfully submitted requests receive an HTTP 200 OK response.

If there were no issues with the request, then the response will be similar to the following example:

[
    {
        "TransactionNumber": 345,
        "Status": "Created",
        "Errors": null,
        "UnMatchedItems": null  
    },
]

Status: Not Created results

If Adaco could not action the adjustments for an outlet, then the response Status value will be Not Created. The Errors field will include a description that will include details to help you resolve the issue. The types of issues that can cause this type of error are:

  • Specified property does not exist
  • Outlet <number> does not exist in specified property
  • Specified date is in a closed period
  • Specified date is in the future
  • No valid adjustment items

For example:

[
    {
        "TransactionNumber": null,
        "Status": "Not Created",
        "Errors": [
            "Outlet <number> does not exist in specified property",
            "Specified date is in a closed period"
        ],
        "UnMatchedItems": null
    }
]

Status: Created with Unmatched Items results

If the Status value is Created with Unmatched Items, then one or more items in the request were excluded from the adjustment. The types of issues that can create this status include:

  • Unrecognised Item Type
     — this must be either “P” or “R”
  • Unrecognised Item Number
  • Item not listed in specified outlet
  • Insufficient Stock
    — stock levels are checked when the option to validate stock at the outlet level is selected within Adaco
  • Invalid account (see the Invalid accounts section below)

The field UnMatchedItems will contain the item number, plus product detail number for products, and an error message describing what the issue is. For example (where 123 is the item number):

[
    {
        "TransactionNumber": 346,
        "Status": "Created with Unmatched Items",
        "Errors": null,
        "UnMatchedItems": [
            "123: Unrecognised Item Type",
  ]

Only items listed as UnMatchedItems are excluded in the requisition request. All other items have had their adjustment successfully actioned by Adaco.

Invalid accounts

An item must have an account associated with it, otherwise it is excluded from the adjustment transaction. The following logic applies:

  1. Adaco checks the Outlet Guide for an account. If one is found, this is used for the item.
  2. If an account is not found in the guide and “outlet accounts” is enabled, then Adaco checks whether the account for the product (or recipe) master is valid for the outlet:
    • If so, then this account is used.
    • If not, the item is excluded from the adjustment transaction.
  3. If an account is not found in the guide and “outlet accounts” is not enabled, then Adaco uses the account from the product master.

Unsuccessful requests

As well as the above error messages for 200 requests, other types of unsuccessful requests receive an HTTP 400-599 response, with an error message in the response body. These match standard HTTP status codes.