Adaco Product Catalog API Guide

Introduction

This API adds products in Fourth Adaco and retrieves details about the products. 

The catalogue resources let you retrieve anything known about the product, aside from custom fields (used for POS systems). 

The product resource lets you create products. The API can create the underlying reference data on the fly. New segments, categories, sub-categories, accounts and vendors are all created as necessary. 

With this resource you send in human-readable categories, such as “fruit and veg”, rather than codes. The API checks that duplications don’t occur and, by only allowing POST requests, ensures any integrated vendors or partners can’t accidentally overwrite or remove products from your own catalog when their catalog no longer has the item.

Note that, for specific customer scenarios, we have a products API that can create, update and delete products; however, this is not appropriate for general use (and not by partners). The API requires exact category mapping, using codes rather than descriptive text. Talk to your Fourth Implementation Consultant for details about this API.

Quick Facts

Integration type HTTP REST with JSON
Authentication Basic Authentication
Availability Purchase to Pay and Inventory - Hotel customers
Testing Test environment is available on request
More information See the Reference 

Get Access

Before integrating, you’ll need to set up a new user account in Fourth with access rights to the API. The resources you plan to use determine what access rights are required. 

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. 

For security, we also recommend that you limit the user group to access to just the “Product API”. The setting for this is within the user group's Property > API Access sub-section.

Screenshot showing settings location

Set the access rights depending on what you plan to use:

  • catalogue resource requests require View rights as a minimum.
  • product resource requests require Create rights as a minimum.

Step 2: create a user account

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

Step 3: add the user account to any other properties

This step is required if you plan to make catalogue resources requests, as access to these resources are restricted by property. Add the user to each relevant property. 

Requests to the product resource is not restricted by property. The user can create products for properties in which the user account is not present.

Properties and products

In Fourth, each product is part of a business-wide catalog owned by the Central Property (CP). From this catalog, each property has access to just the products they require. 

GET requests for product information are made by property. This is because the primary use cases are all to allow individual properties to manage their products.

When you POST new product information, you'll need to identify the properties that should have access to the product, using AdacoProperties

   "AdacoProperties": [ "BANANASTAND1", "BANANASTAND2" ],

When you add a new product, it's included in the CP catalog, regardless of whether the CP is included in the array.

To add a product to all properties, leave the array empty:

   "AdacoProperties": [ ],

Note that if you add the same product separately for two different properties, there will be two instances of the product in Fourth.  

Getting the property number

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

Adaco screenshot showing the Prperty Number and Property Identifier 1 fields highlighted

Note that for product requests, you can enter either the property number or Property Identifier 1 as the value for AdacoProperties.

Property-specific amendments

Normally the data about a product, such as its categorization, is common to all properties. However, there is one setting you can control using CPControl. This determines whether the CP or individual properties are in charge of pricing and purchase ordering.

"CPControl": "Partial",

Options are:

  • Full — prices and purchase orders (POs) are managed by the CP
  • Partial — prices are managed by the CP; while POs are managed by the property
  • None — prices and POs are managed by the property (this is the default)

Product categories

Products are categorized using a three-level hierarchy:

  • Segment — The primary category; for example, "beverages".
  • Category — The next level of category; for example, "wine". 
  • Sub category — The last category level; for example, "red wine". 

When creating a product, you must include all three categories. The product resource lets you specify these as human readable, rather than as codes, for example:

   "SegmentName": "beverage",
   "CategoryName": "wine", 
   "SubCategoryName": "red wine",

The API will check the categorization details sent in and, as necessary, create new segments, categories or sub categories on the fly. Matches are looked for in both the parent and grandparent property. The matching:

  • is case insensitive
  • ignores special characters, "and" and "&"
  • ignores the sequence of words

For example, an existing sub category called “Fruit & Vegetables” would match against “FRUIT AND VEGETABLES”; or an existing category of  “wines & liquors”, would match against “Liquors / wines” and “Wines – Liquors”. However, if the words change from singular to plural, or are shortened, we cannot match these. For example, "Fruits" and "Fruit" will not match. 

The API first checks sub category. If it cannot find an existing sub category that matches, it creates a new entry. However, if it finds an existing sub category, it will then check whether the category matches. For example, a new category and sub category are created if the category is "alcoholic" rather than "wines":

   "SegmentName": "beverage",
   "CategoryName": "alcoholic", 
   "SubCategoryName": "red wine", 

Note that we check against sub category and category only. If there are two segments with the same category and sub category, such as:

  • Segment=Retail, category=clothing, sub category=bathrobes
  • Segment=Spa, category=clothing, sub category=bathrobes  

then the API adds the product to the first existing sub-category / category / segment. 

Getting the product categories

You can use a GET request to find the product categories that exist for a property. The request is:

GET <ROOT>/{customerName}Service/WebApi/Catalogue/{propertyNumber}/Segments

The response will have an entry for every sub category, e.g.:

[
{ 
   "SegmentNumber": "121"
   "SegmentName": "BEVERAGES"
   "CategoryNumber": "324"
   "CategoryName": "WINE"
   "SubCategoryNumber": "121323"
   "SubCategoryName": "WHITE WINE"
}
{ 
   "SegmentNumber": "121"
   "SegmentName": "BEVERAGES"
   "CategoryNumber": "324"
   "CategoryName": "WINE"
   "SubCategoryNumber": "121212"
   "SubCategoryName": "RED WINE"
}
]

Product details

Most products come in various sizes, weights, or unit numbers. Each variation is referred to as product detail in the UI. In API requests, this information is delivered in the detail collection. A request can create:

  • A product with one detail in the detail collection.
  • A product with multiple details as an array in the detail collection. This creates a single product with multiple details attached.
  • A product where the detail collection (which is mandatory) is empty. 

Vendors

If desired, you can add a primary vendor as part of a POST product request. Fourth will check whether this is an existing vendor and if not, create a new vendor record.

There are two JSON members you can use to identify the vendor in the request:

  • PrimaryVendorName — Your unique identifier for the vendor, like "Bob's supermarket". For new vendors, you need to include this in the request.
  • PrimaryVendorXReference — Either the "API X Reference” for the vendor, which is normally your business's billing or accounting reference; or the Adaco vendor number, which is shown in the Fourth UI. 

The following table shows the logic that determines whether the request creates a new vendor or uses an existing one (where “Matches” means the value matches an existing vendor). You’ll note that the PrimaryVendorName takes precedence for checking and creating vendors; if you are adding a new vendor, then you must always specify PrimaryVendorName.

PrimaryVendorName PrimaryVendorXReference Outcome
Matches Matches, or
doesn’t match, or
no value in request
Uses existing vendor using Name
Doesn’t match Matches Uses existing vendor using X Reference
Doesn’t match Doesn’t match, or
no value in request
Creates new vendor using Name and X Reference (if provided)
No value in request Matches Uses existing vendor using X Reference
No value in request Doesn’t match, or
no value in request
No vendor information added for the product

General ledger accounts for products

You must include a general ledger account in product requests. Fourth will check whether this is an existing account and if not, create a new account record.

There are two JSON members you can use to identify the vendor in the request:

  • AccountName — Your unique identifier for the account, like "Beverage Inventory". For new accounts, you need to include this in the request.
  • AccountXReference — Either the Fourth account number, which is shown in the Fourth UI; or the "Account Cross Reference” for the account, which is normally the account number from your accounting system. 

The following table shows the logic that determines whether the request creates a new account or uses an existing one (where “Matches” means the value matches an existing account). You’ll note that the AccountName takes precedence. Matching is case insensitive and ignores the word “and” or the “&” character. 

AccountName  AccountXReference  Outcome
Matches Matches, or
doesn’t match, or
no value in request
Uses existing account
Doesn’t match Matches Uses existing account
Doesn’t match Doesn’t match, or
no value in request
Creates new account using AccountName and AccountXReference value (if provided).
No value in request Matches Uses existing account using X Reference
No value in request Doesn’t match Creates new account. Uses AccountXReference value in both the name and reference fields.
No value in request No value in request Request is rejected

Duplicate product requests

If you send in the same request twice, the API will create duplicate product entries. This is deliberate, as it stops the business, partners and vendors from accidentally altering or deleting existing products that are not theirs. 

Adding an existing product to a new property

If you need to add an existing product to a new property, specify ProductNumber in your request. This creates a new version of the product in that property. 

"AdacoProperties": ["68"],
"ProductNumber": 12345,

The request, however, cannot update any details about the existing product. 

Similarly, if you need to add an existing product detail to a new property, specify DetailNumber in your request. This creates a new version of the product in that property. 

"Details": [
   {
   "DetailNumber": 54321,
   "Brand": "Piers and Hordern Gin Company",

Units of Measure

The following JSON members hold unit of measure values: 

  • PurchaseUnit
  • PackUnit
  • SubPackUnit 
  • MicroPackUnit 
  • CatchWeightUnit

In the response to catalogue requests, we return the Fourth code for the unit (e.g. EA, CA, BO). When sending a POST product request, you can use either the Fourth codes or one of the accepted synonyms. The matching is case insensitive.

Unit Description Adaco Unit Code Synonyms

Bottle

BO

bo, bottle, bot, btl, bttl, bt

Bag

BG

bag, bags, bg

Barrel

9B

barrel, br, brl

Dry Gallon

9G

dry gallon

Pinch

9P

pinch

Bucket

BC

bucket, bkt, bckt

Bundle

BD

bundle, bdl, bndl

Basket

BS

basket, bskt

Bushel

BU

bushel, bshl

Box

BX

box, boxes

Centilitre

CL

centiliter, centilitre, centiliters, centilitres

Case

CA

case, cs

Container

CH

container

Can

CN

can

Carton

CT

carton, ctn

Dry Cup

CU

dry cup

Dozen

DZ

dozen, doz

Each

EA

each

Fluid Ounce

FO

fluid ounce, floz, flz

Gallon

GA

gallon, gal, galon

Gram

GR

gram, grm, grms, grs, g

Jar

JR

jar, jars

Jug

JU

jug, jg

Keg

KE

keg

Kilogram

KG

kg, kilo, kilogram, kgs

Cup

L8

cup, cp

Pound

LB

pound, pnd, lbs

Litre

LT

liter, litre, liters, litres, ltr, ltrs, l

Millilitre

ML

milliliter, milliliter, millilitres, milliliters, mls

Ounce

OZ

ounce

Pail

PA

pail

Piece

PC

piece, pcs

Package

PK

package, pkg

Pack

PQ

pack

Pint

PT

pint, pnt

Peck

PY

peck

Dry Quart

QS

dry quart, dry qt

Quart

QT

quart

Rack

RA

rack, rk

Roll

RL

roll

Ream

RM

ream

Sheet

SH

sheet, sht

Sleeve

SL

sleeve, sleave, slv

Set

ST

set

Stick

15

stick, sticks

Tub

T8

tub

Tube

TB

tube

Tin

TIN

tin

To Taste

9Y

taste, to taste

Tray

TY

tray, tr

Bunch

X2

bunch, bnch

Head

X5

head, hd

Leaf

X7

leaf, leaves, lf

Loaf

X8

loaf, loave, loaves

Slice

Y1

slice, slc

Table Spoon

Y2

table spoon, tbl spoon, tbl spn

Tea Spoon

Y3

tea spoon, tea spn, t spoon, tspn

Request header

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

GET /{customerName}Service/WebApi/Catalogue/{propertyNumber}/Products  HTTP/1.1
Host: instance.example.com
Authorization: Basic VXNlcm5hbWU6cGFzc3dvcmQ=
Field Description
Authorization Your Product Catalog 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.

Resources base path

You need to put your customerName in the URL path — this is the same name in the URL that you currently use to log into Fourth Purchase to Pay and Inventory. You also need to add "Service" to the name; e.g. "acmeService". The base path for all requests is:

<ROOT>/{customerName}Service/WebApi/

Catalogue resources

The catalogue resources only accept GET requests. There are query parameters available for filtering results.
The first three resources all return the same fields for each product record, with the records filtered by the request type. 

Resource name Description

Catalogue/{propertyNumber}/Products

Returns all products for the property.

Catalogue/{propertyNumber}/Categories
/{categoryNumber}/Products

Returns all products for the property, for a specific category.

Catalogue/{propertyNumber}/Segments
/{segmentNumber}/Products

Returns all products for the property, for a specific segment.

Catalogue/{propertyNumber}/Segments

Returns the categories for a property, listed by sub category. No other product information is returned.

Getting the segments and categories numbers

To get a SegmentNumber or CategoryNumber use the request: 

GET <ROOT>/{customerName}Service/WebApi/Catalogue/{propertyNumber}/Segments

See the example request below.

Limiting and paging the Catalogue payload

Three catalogue resources return product information:

  • Catalogue/{propertyNumber}/Products
  • Catalogue/{propertyNumber}/Categories/{categoryNumber}/Products
  • Catalogue/{propertyNumber}/Segments/{segmentNumber}/Products

These can potentially deliver a large payload, so by default they are set to return a maximum of 100 products. However, you can alter this with the search.pageSize and search.Page query parameters. 

For example, to limit the product data to 25 products, us the search.pageSize parameter:

GET <ROOT>/{customerName}Service/WebApi/Catalogue/{PropertyNumber}/Products?search.pageSize=25

You can access product data over multiple pages using the search.Page parameter. For example, this request would retrieve the product items from 26-50:

GET <ROOT>/{customerName}Service/WebApi/Catalogue/{PropertyNumber}/Products?search.pageSize=25&search.Page=2

Response to catalogue requests

Successful requests

Successful GET requests receive an HTTP 200 OK response with the data requested in the response body. See the example below.

Unsuccessful requests

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

Product resource

The resources accept POST requests. There are no query parameters. 

Resource name Description
Product Adds a new product and product detail records. 
Accepts POST requests only. 

For information on the request body, see the Reference.

Response to product requests

Successful requests

Successful GET requests receive an HTTP 200 OK response with the full details of the newly created product in the request body.  

Unsuccessful requests

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

Example requests and responses

Example POST product request

This example shows a request where the two properties (68 and 69) have a new Gin added to their catalog. A primary vendor is included. 

POST /{CustomerName}Service/WebApi/Products  HTTP/1.1
Host: instance.example.com
Content-Type: application/json
Authorization: Basic VXNlcm5hbWU6cGFzc3dvcmQ=

{
  "AdacoProperties": ["68","69"],
  "ProductDescription": "DRY LONDON GIN",
  "AccountName": "BEVERAGE INVENTORY",
  "AccountXReference": "46000-02",
  "SegmentName": "ALCOHOLIC BEVERAGES",
  "CategoryName": "ALCOHOLIC SPIRITS",
  "SubCategoryName": "GIN & GIN TYPE DRINKS",
  "CPControl": "None",
  "ApprovalStatus": "Approved",
  "RateScheduleNumber": "CP 5% VAT",
  "NutritionNumber": null,
  "MenuItemClass": "MenuItemClass",
  "YieldPercentage": .99,
  "WeightPerCup": 22,
  "KeyItem": true,
  "Approval": true,
  "Details": [
    {
      "PrimaryVendorName": "Chris's produce",
      "PrimaryVendorXReference": "CP4565",
      "LastVendorXReference": "CP4565",
      "PurchaseInformation": "Case of 12 bottles",
      "Specification": "Specification",
      "PurchaseUnit": "CA",
      "PackSize": 12,
      "PackUnit": "Bottle",
      "SubPackSize": 750,
      "SubPackUnit": "ml",
      "PurchaseCost": 144,
      "Brand": "Piers and Hordern Gin Company",
      "ContractedProduct": true,
      "Status": 1,
      "InventoryUnit": true,
      "InventoryDescription": "1 x 750ml bottle",
      "BinNumber": "Bin 47",
      "LastPurchasedDate": "2018-04-24T06:32:30.622Z",
      "LastPurchasedCost": 150,
      "IsVerified": true,
      "AlternativeDescription": "HOUSE GIN",
      "VPN": "GIN001"
    }
  ]
}

Example response to POST product

The response contains the details of the new product. 

HTTP/1.1 200 OK

[
  {
    "CategoryNumber": 2,
    "SubCategoryNumber": 1,
    "AccountNumber": 2,
    "Allergen": 0,
    "Intolerance": 0,
    "CPControl": 1,
    "DietaryGuidelines": 0,
    "ReceivingCategoryNumber": null,
    "Details": [
      {
        "InventoryBarSymbol": null,
        "InventoryBarCode": null,
        "PrimaryVendorNumber": 3,
        "LastVendorNumber": 3,
        "VPN": null,
        "DetailNumber": 1,
        "PurchaseInformation": "Case of 12 bottles",
        "Specification": "Specification",
        "PurchaseUnit": "CA",
        "PropertyNumber": 69,
        "ProductNumber": 9,
        "PackSize": 12,
        "PackUnit": "BO",
        "SubPackSize": 750,
        "SubPackUnit": "ML",
        "MicroPackSize": null,
        "MicroPackUnit": null,
        "CatchWeight": null,
        "CatchWeightUnit": null,
        "PurchaseCost": 144,
        "Brand": "Piers and Hordern Gin Company",
        "SubProductNumber": null,
        "SubProductDetailNumber": null,
        "SubProductQuantity": null,
        "ContractedProduct": true,
        "ProductIdentifier": null,
        "Producer": null,
        "ParentCompany": null,
        "Status": 1,
        "InventoryUnit": true,
        "InventoryDescription": "1 x 750ml bottle",
        "BinNumber": "Bin 47",
        "LastPurchasedDate": "2017-09-24T06:32:30.622Z",
        "LastPurchasedCost": 150,
        "SellingWeight": null,
        "Sku": null,
        "IsVerified": true,
        "TareWeight": null,
        "ManufacturerNumber": null,
        "ManufacturerProductNumber": null,
        "AlternativeDescription": "HOUSE GIN"
      }
    ],
    "ApprovalStatus": "Approved",
    "PropertyNumber": 69,
    "ProductNumber": 9,
    "ProductDescription": "DRY LONDON GIN",
    "RateScheduleNumber": "CP 5% VAT",
    "NutritionNumber": null,
    "MenuItemClass": "MenuItemClass",
    "YieldPercentage": 1,
    "WeightPerCup": 22,
    "KeyItem": true,
    "Approval": true
  },
  {
    "CategoryNumber": 2,
    "SubCategoryNumber": 1,
    "AccountNumber": 2,
    "Allergen": 0,
    "Intolerance": 0,
    "CPControl": 1,
    "DietaryGuidelines": 0,
    "ReceivingCategoryNumber": null,
    "Details": [
      {
        "InventoryBarSymbol": null,
        "InventoryBarCode": null,
        "PrimaryVendorNumber": 3,
        "LastVendorNumber": 3,
        "VPN": "GIN001",
        "DetailNumber": 1,
        "PurchaseInformation": "Case of 12 bottles",
        "Specification": "Specification",
        "PurchaseUnit": "CA",
        "PropertyNumber": 68,
        "ProductNumber": 9,
        "PackSize": 12,
        "PackUnit": "BO",
        "SubPackSize": 750,
        "SubPackUnit": "ML",
        "MicroPackSize": null,
        "MicroPackUnit": null,
        "CatchWeight": null,
        "CatchWeightUnit": null,
        "PurchaseCost": 144,
        "Brand": "Piers and Hordern Gin Company",
        "SubProductNumber": null,
        "SubProductDetailNumber": null,
        "SubProductQuantity": null,
        "ContractedProduct": true,
        "ProductIdentifier": null,
        "Producer": null,
        "ParentCompany": null,
        "Status": 1,
        "InventoryUnit": true,
        "InventoryDescription": "1 x 750ml bottle",
        "BinNumber": "Bin 47",
        "LastPurchasedDate": "2017-09-24T06:32:30.622Z",
        "LastPurchasedCost": 150,
        "SellingWeight": null,
        "Sku": null,
        "IsVerified": true,
        "TareWeight": null,
        "ManufacturerNumber": null,
        "ManufacturerProductNumber": null,
        "AlternativeDescription": "HOUSE GIN"
      }
    ],
    "ApprovalStatus": "Approved",
    "PropertyNumber": 68,
    "ProductNumber": 9,
    "ProductDescription": "DRY LONDON GIN",
    "RateScheduleNumber": "CP 5% VAT",
    "NutritionNumber": null,
    "MenuItemClass": "MenuItemClass",
    "YieldPercentage": 1,
    "WeightPerCup": 22,
    "KeyItem": true,
    "Approval": true
  }
]

Example GET catalogue segments request

In this example, the request has segmentNumber set to "1". This is for this business's Central Property, which means the requests retrieves all product categories across the entire business. 

GET <ROOT>/{customerName}Service/WebApi/Catalogue/1/Segments

The response will look like:

HTTP/1.1 200 OK

[
{ 
   "SegmentNumber": "26"
   "SegmentName": "Food and Bev"
   "CategoryNumber": "2169"
   "CategoryName": "FROZEN FOOD"
   "SubCategoryNumber": "1"
   "SubCategoryName": "FROZEN SEAFOOD"
}
{ 
   "SegmentNumber": "26"
   "SegmentName": "Food and Bev"
   "CategoryNumber": "2169"
   "CategoryName": "FROZEN FOOD"
   "SubCategoryNumber": "54"
   "SubCategoryName": "FROZEN VEG"
}
{ 
   "SegmentNumber": "26"
   "SegmentName": "Food and Bev"
   "CategoryNumber": "321"
   "CategoryName": "BEERS"
   "SubCategoryNumber": "121212"
   "SubCategoryName": "LAGERS"
}
]

Example GET product by categoryNumber

This example retrieves the products for property "2" and for the category "2169", which is Alcoholic Spirits from our previous example. This will return anything that anything in the categories: FROZEN FOOD > FROZEN SEAFOOD and FROZEN FOOD > FROZEN VEG. 

GET <ROOT>/{customerName}Service/WebApi/Catalogue/2/Categories/2169/Products

The response is an array of products. 

[
  {
    "ProductNumber": 1308,
    "ProductDescription": "FRZ FISH PRAWNS TIGER (PUD) RAW & PEELED 21/25",
    "SegmentNumber": 26,
    "SegmentName": "Food and Bev",
    "CategoryNumber": 2169,
    "CategoryName": "FROZEN FOOD",
    "SubCategoryNumber": 1,
    "SubCategoryName": "FROZEN SEAFOOD",
    "Approval": false,
    "AccountNumber": 75,
    "RateScheduleNumber": " ",
    "CreatedDate": "2005-06-27T00:00:00",
    "CreatedBy": "SYSTEM ADMINISTRATOR",
    "LastUpdateDate": "2017-05-24T02:56:37.3",
    "LastUpdatedBy": "SYSTEM ADMINISTRATOR",
    "NutritionNumber": null,
    "IsAdhoc": false,
    "Allergens": [],
    "Intolerances": [],
    "YieldPercentage": null,
    "WeightPerCup": 0,
    "KeyItem": false,
    "CPControl": 2,
    "DietaryGuidelines": [],
    "ReceivingCategoryNumber": null,
    "ApprovalStatus": "Approved",
    "DetailNumber": 1,
    "PurchaseInformation": "PEELED AND UNVEINED",
    "Specification": "PEELED AND UNVEINED",
    "PurchaseUnit": "BG",
    "PackSize": 1,
    "PackUnit": "EA",
    "SubPackSize": null,
    "SubPackUnit": null,
    "MicroPackSize": null,
    "MicroPackUnit": null,
    "CatchWeight": null,
    "CatchWeightUnit": null,
    "Brand": "",
    "ContractedProduct": false,
    "Status": "Active",
    "InventoryUnit": "True",
    "InventoryDescription": "KILO",
    "BinNumber": null,
    "PrimaryVendorNumber": null,
    "LastPurchasedDate": null,
    "LastPurchasedCost": null,
    "LastPurchaseVendorNumber": null,
    "PurchaseCost": 0,
    "IsRetailProduct": false,
    "SKU": null,
    "IsVerified": false,
    "TareWeight": null,
    "Manufacturer": null,
    "ManufacturerProductNumber": null,
    "AlternativeDescription": null
  }
]