Adaco Product & Catalogue Guide
Introduction
The product and catalogue resources enables you to manage products inside Fourth Adaco.
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 that integrated vendors or partners don’t accidentally overwrite or remove products from the customer's catalog if they no longer stock 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 | Fourth Inventory for Hotels customers |
Testing | Test environment is available on request |
More information | See 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. 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.
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 customer-wide catalog. The catalog is "owned" by the customer's 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.
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 — The 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 the customer'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 a code for the unit (e.g. EA, CA, BO).
When sending a POST product request, you can use either the unit codes or the accepted synonyms listed in the units of measure table. The matching is case insensitive.
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
The base path for all requests is:
<ROOT>/{customerName}Service/WebApi/
This is made of:
- The domain name of the Fourth cloud, referenced as <ROOT> above.
- 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".
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 |
Returns all products for the property, for a specific category. |
Catalogue/{propertyNumber}/Segments |
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 } ]