Contents

Basic information

The basic principle of the API is very simple. GTIN (EAN) is the unique key that's used to look up products.

You send in a list of the GTINs of the products you want to retrieve information or media from, and we return the data for any matching products.


Access to the API

We have a limited trial account you can use to test the API with in the beginning. See the Trial section.

After you've tested the API, you need your own account. To get one, you need to contact us and give us some basic information about who you are and what you intend to use the service for. After that you'll need to sig a contract regulating how the service can be used, abuse and such.
Once this is done, you'll recieve the account details needed for using the service. There are generally sent out the same day the contract is signed.
In some rare cases, you'll need to have a specialized API developed. If that's the case, contact us for more information.

There are several versions of this API. The latest live one is 1.81, also called v181.


Available information

The standard API have two main methods for retrieving product data.

GetProductData, that returns a basic collection of product information that fits most applications.

GetCustomProductData, that lets you decide what information you want and gives you access to a much bigger set of data to choose from.

GetProductData returns an object containing:

   
   GTIN
   Supplier article number
   Image URL
   Supplier name
   Brand name
   Product name
   Weight / volume
   Content declaration
   Nutrition
   Product markings
   Allergy information
   Custom Fields


The information you can choose from in GetCustomProductData, is:

   Images (Sent in the form of encrypted URLs. You can ask for images in multiple sizes/formats in each request)
   Product name
   Supplier name
   Brand name
   Content declaration
   Nutrition
   Weight / volume
   Product markings (currently about 35 possible markings, like Svanen, Särnär glutenfri, Nyckelhålet, Krav etc.)
   About 20 different article numbers (For example GTIN, DUN, Supplier article number, ICA-number, COOP-number, Servera-number etc.)
   Width
   Height
   Depth
   Allergy information
   Durability
   Recycling
   Storage instructions
   Product description
   Sale text
   Category tree connections (for suppliers)
   Custom Fields
   NutritionPerServing
   OrderableLevels


To make the data lookup go as fast as possible, only request the information you actually need.


SOAP / JSON / SSL

Our API handles both JSON and SOAP requests, and it will reply with data in the same format as it has been called. So if you request data with JSON, the reply will be in JSON.

SSL (https) is mandatory. Any request made to http will be redirected to https.

Methods

   StringResult Get24hSecurityKey(string username, string password)
   ProductResult GetProductData(string[] gtinVec, ImageThumbFormat thumbFormat, ImageThumbSize thumbSize, string langTargetmarketLimit, string secKey)
   StringResult GetCustomProductDataFieldNames(string secKey)
   ArbitraryProductResult GetCustomProductData(string[] gtinVec, string[] requestedDataFields, string langTargetmarketLimit, string secKey)
   StringResult GetAllAvailableGTINs(string langTargetmarketLimit, string secKey)
   ChangedProductGTINResult GetChangedProductsGTINs(DateTime changedAfter, string secKey)


Return types

The API will always return an object containing three variables:

   XXXResult {
       bool Success;
       string ErrorMessage;
       XXX Data;
   }

Success tells you if the call was successful. If Success == false, ErrorMessage will contain a description of the error that has occurred. Data is a strongly typed variable containing hte requested data (if Success == true).

For a more detailed description of the return types, see the "Return types" chapter


Authentication

Get24hSecurityKey checks your user credentials and returns a security key valid for 24 hours. This security key should be cached in your solution and sent in evey subsequent request you make to the API.

Every time the method is called, a new key is generated.

To prevent brute force attacks and keep the logs somewhat readable, there is a limit to 50 security keys per user every 24 hours. So the recieved key needs to be cached and reused.


Available GTINs

GetAllAvailableGTINs returns a string vector with all GTINs you have access to.


Changed/Deleted products

For us and the suppliers it's of the utmost importance that all information from us, that you show to your users, is correct and updated.

GetChangedProductsGTINs returns a list of GTINs of the products that has been changed or deleted since the date/time you supply the metod with, and what kind of change it is (New/changed or deleted).

If the data is cached or stored in a database on your side, it's vital that this method is called at regular intervals to keep the data updated and accurate.

As soon as a product is marked as updated (TypeOfChange.Updated), its product data and images must be downloaded again.

If it's marked as deleted (TypeOfChange.Deleted), any product information or images you've recieved from us must be deleted.


GTIN

You supply the GetProductData and GetCustomProductData methods with a list of GTINs belonging to the products you want information about.

Only GTIN containing 8 or 13 digits are considered valid. Every sent in GTIN results in an object being returned, containing the requested data, in the same order that the GTINs were sent in. Every object contains a MatchFound property, indicating if a matching product could be found in our system or not.

To keep the responsetimes relatively low, it's not recommended to look up more that 500 GTINs in each request.


Two different methods for requesting product information


GetProductData

A simple method that you supply lith a list of the GTINs you want to look up and the type of image you want,and a product object is returned conatining a basic set of product data that would be sufficient in most situations:

   GTIN
   Supplier article number
   Image URL
   Supplier name
   Brand name
   Product name
   Weight / volume
   Content declaration
   Nutrition
   Product markings
   Allergy information
   Custom Fields


GetCustomProductData

This method gives you the ability to customize and pick and choose exactly what information you want in each call. It gives you the ability to choose from a much wider array of data, and the ability to request for instance all images of the product (not just the main image) and even those images in several different sizes/formats in each request.

To get the "names" of the available fields, you call GetCustomProductDataFieldNames.

Images

Images can be downloaded in three different formats:

   Jpeg
   Png, 24 bit (with transparency)
   Tiff

and in several different sizes:

   50 px
   75 px
   100 px
   150 px
   200 px
   250 px
   300 px
   400 px
   500 px
   750 px
   1000 px *
   1500 px *
   2000 px *
   Original size *
   * These sizes are disabled by default, but can be unlocked for your account by contacting us


GetCustomProductData

You supply GetCustomProductData with a string detailing what kind of image you want, from a number of predefined options. It's structured like "Image_Format_Size". Format is entered like Jpg, Png or Tif. The size entered like px100, px200, px500. So Image_Png_px400 would give you a link to the main product image in Png-format, 400 pixels wide and/or high. Image_Jpg_px100 would give you a link to an image thats 100 pixels wide and/or high in Jpeg format.

It's be possible to request for instance both "Image_Png_px75" and "Image_Png_px300" in GetCustomProductData, and recieve links to both images in every request.


Image & AllImages

In GetCustomProductData there are two ways to request images. Image returns a link to the primary product image.

But a product can have an unlimited number of images attached. That's why AllImages exists

If you request AllImages_Jpg_px200, it would return all images belonging to the product, with the URL to the image and some additional data about it (for example the type of image, like ProductImage or AssortmentImage), and information about angle, which side is visible primarily and so on. The additional data conatins all the information necessary for creating a GS1 file name, if that's something you need.

More information can be found in the class and structure listings at the end of the document.

Category tree

Suppliers can create category trees in OPV Online, and attach products to the different categories. The API has a number of methods that allows suppliers to retrieve these trees and its attached products. Before you can collect the category tree, you need to contact us in order for you to get your MenuId, required by these functions.

Every product can be attached to any number of categories. Every category han have an infinite number of sub-categories, in an infinite depth.

   MenuItemResult GetMenuTree(string language, string secKey)
   MenuItemResult GetMenuSubItems(long parentMenuItemId, string language, string secKey)
   ArbitraryProductResult GetMenuCustomProductData(long menuItemId, string[] requestedDataFields, string langTargetmarketLimit, string secKey)


GetMenuTree returns the entire tree at once.

GetMenuSubItems allows you to traverse the tree, by returning all the sub-categories of a certain category.

GetMenuCustomProductData works just like GetCustomProductData, but returns the product data of all products under a certain category instead of doing i GTIN lookup.


Custom Fields

Suppliers often have a need for very specific data that's relevant to their own business, but might not be for others. A cheese manufacturer might for instance need data about where and how long the cheese has been stored, and how strong the taste is. Data that would be irrelevant for most other suppliers.
This is the problem that Custom Fields is supposed to solve. It gives the suppliers the ability to add their own fields of different types (like textboxes, radiobuttons, drop down menus and such) with their own rules and validations, for entering additional data about a product not covered by the standard data fields.

CustomFields returns all available custom fields for the current product in a simple Name-Value type of structure.

Old products might not have any custom-fields available or even only have a subset of the total set available for the company, so a Custom Field that exists on one product, might be empty or null on another.

EU 1169/2011

By the end of 2014 EU regulation 1169/2011 became active. This requires anyone selling for instance food, no matter if it's online or in a store, to give the customer access to some basic information:

1 a) The name of the food

1 b) The list of ingredients

1 c) Any ingredient or processing aid listed in Annex II or derived from a substance or product listed in Annex II causing allergies or intolerances used in the manufacture or preparation of a food and still present in the finished product, even if in an altered form.

1 d) The quantity of certain ingredients or categories of ingredients.

1 e) The net quantity of the food

1 f) The date of minimum durability or the ‘use by’ date (Not required in online stores)

1 g) Any special storage conditions and/or conditions of use

1 h) The name or business name and address of the food business operator referred to in Article 8(1)

1 i) The country of origin or place of provenance where provided for in Article 26

1 j) Instructions for use where it would be difficult to make appropriate use of the food in the absence of such instructions

1 k) With respect to beverages containing more than 1,2 % by volume of alcohol, the actual alcoholic strength by volume

1 l) A nutrition declaration


All this information can be retireved through our API by calling GetCustomProductData and requesting:

SupplierName

TrademarkName

ProductName

WeightVolume (Net weight / volume)

Content

Nutrition

CompanyAddress

Storage (Instructions for storing the product)

Manual (Instructions for how to use the product)

Origin

PercentageOfAlcoholByVolume


[allergen]

Another requirement in EU 1169/2011 is that allergens are marked in bold style in all content declarations. So in many content declarations you'll see [allergen][/allergen]-tags. When writing out the content declaration, you need to do a replace to translate the [allergen] tags to a suitably styled HTML element.

Example:

Water, [allergen]flour[/allergen], yeast, [allergen]milk powder[/allergen], salt

needs to be transformed into

Water, flour, yeast, milk powder, salt

Trial

The easiest way to get familiar with how the APi works, is by calling it. So we've created a trial account that can be freely used to request data from a limited number of products.

The latest version of the API can be found here:

https://api.opv.se/WS/General/v181/ProductService.asmx


The user credentials to use with the Get24hSecurityKey-function is:

User name: WebService_TrialAccount

Password: NtcNOMZ8z0?_f2


It's the production service you're using, but with a limited account. So once you get the real user account, you only need to swtich the username and password, and you'll get access to alot more data.

We've developed a simple software that you can use to test the API: https://api.opv.se/WS/General/v181/WebServiceTester.zip The file conatins both the source code (in C#), and the runnable version (in the bin\release-folder).

To get a list of what GTINs you have access to, run the program and press the "Get all GTINs" button.


For a more in-depth look at what happens behind the scenes, WebServiceStudio works well.

Calling the API

SOAP

The API describes how to call it using SOAP through the WSDL specification. Go to the API in an ordinary web browser, and click on the different methods, and you'll get a specification of how to call it and what data will be returned.

JSON

You call the different methods of the API through: https://api.opv.se/WS/General/v181/ProductService.asmx/{function_name}

For example:

https://api.opv.se/WS/General/v181/ProductService.asmx/Get24hSecurityKey

In the call, set dataType to "json" and contentType to "application/json; charset=utf-8".

As data, send in a JSON encoded object. For example {"username": "MyUsername", "password": "MyPassword"}


JSON code example:

   $.ajax(
   {
           type: "POST",
           dataType: "json",
           contentType: 'application/json; charset=utf-8',
           url: "https://api.opv.se/WS/General/v181/ProductService.asmx/GetCustomProductData",
           data: "{ 'gtinVec': [ '5701211106172', '6411200106685' ], 
                    'requestedDataFields': [ 'SupplierName', 'TrademarkName', 'ProductName', 'WeightVolume', 'Image_Jpg_px200' ], 
                    'langTargetmarketLimit': 'sv-SE', 'secKey': '"+ secKey +"' }",
           success: function (r) {
           if (r == null || r.d == null) {
               alert('Unknown error');
           }
           else if (!r.d.Success) {
               alert(r.d.ErrorMessage);
           }
           else {
               var data = r.d;
        // Start processing the data
           }
       }
   });

Note: In this example Javascript is used with jQuery to illustrate a call to the API. But because of cross site scripting-limitations, this won't work in reality. But most languages would do this in a similar way.

If you want load data from the API with Javascript, you need to create a "proxy" in the backend that passes on the call to our API and returns the data to the frontend.

Call data

Fields that can be requested through GetCustomProductData

Field Datatype Information
AllergyInfo AllergyInfo[] Returns information about allergens in the product.

Contains 0: No, 1: Trace amounts, 2: Yes.

Note that allergens will me marked in the content declaration. That's where the customer should look first. These tables is to make allergens searchable, and not eveyone fills in this data.

AssortmentName String The assortment that the product belongs according to our own classification.
AxfoodNr String The Axfood article number.
AxfoodSnabbgrossNo String The Axfood Snabbgross -article number.
BergendahlNr String The Bergendahl article number.
Brand BrandInfo A BrandInfo-object, bontaining the name and our internal id of the brand.
ChangedDate DateTime The date when the products data was last changed.
Company Company The address information of the supplier (GLN, and in CompanyAddress: Name, Street address, Zip code and City).
Content String The content declaration of the product

Contains [allergen][/allergen]-taggs around allergens. These needs to be replaced by for instance <strong>-tags to make them bold and easy for the customer to find.

In the future the [allergen]-tags might contain attributes that would tell what kind of allergen it is (for example [allergen allergenId="5" allergenName="Milk"]), so it's recommended to use regular expressions when doing the replace. Something along the lines of RegEx.Replace(content, "\[allergen.*?\]", "<span class="bold" >");

Cooking String Cooking instructions.
COOPNr String The COOP article number.
CustomFields CustomFieldInfo[] Suppliers can add their own custom fields if they feel something is missing, to extend the number of available data fields and fill in data relevant for their own users or systems. "CustomFields" returns all available custom fields for the current product. Old products might not have any custom-fields available or even only have a subset of the total set available for the company, so a Custom Field that exists on one product, might be empty or null on another.
DafgardNo String The Dafgård article number.
Depth Double The depth of the product in mm.
Description String Product description.
Durability String Information about the durability of the product (like "8 days in the refridgerator").
GTIN String The GTIN number of the product. Might be 8, 13 or 14 digits.
Height Double The height of the product in mm.
ICANr String The ICA article number.
LaunchDate DateTime The date when the product was launched.
Manual String Instructions of how to use the product.
Markings MarkInfo[] Official product markings, like Svanen, Nyckelhålet, Fairtrade etc.

1 Bra Miljöval/Falken 2 Svanen 3 KRAV-märkt 4 Nyckelhålet 5 Särnär naturligt glutenfri 6 Särnär fri från Gluten 7 Särnär fri från Laktos 8 Särnär Laktosreducerad 9 Särnär fri från Soja 10 Särnär Ärtproteinfri 11 Särnär fri från Mjölkprotein 12 Särnär fri från Ägg 13 Sockerfri 14 Särnär 15 Osötat 16 Svenskt sigill 17 Organic 18 EU-blomman 19 Särnär fri från Jordnötter 21 Särnär fri från Fisk 22 Särnär fri från Nöt/Mandel 23 Fairtrade 24 GMO märkt 25 Godk. av Astma & Allergif. 26 Särnär fri från Mjölk 27 Särnär proteinreducerad 28 Särnär laktasenzym 29 Särnär proteinfri 30 Särnär fenylalaninfattig 31 EU-logotype för ekologiska produkter 32 MSC-märkt fisk 33 Steril 34 Höggradigt ren 36 Svensk fågel 37 Smakstyrka 1 38 Smakstyrka 2 39 Smakstyrka 3 40 Smakstyrka 4 41 Smakstyrka 5 42 Smakstyrka 6 43 Smakstyrka 7 44 Smakstyrka 8 45 Smakstyrka 9 46 Smakstyrka 10 47 Smakstyrka 11 48 UTZ-Certified 49 Rainforest Alliance 50 Animal Welfare Approved 51 FSC 52 Svenskt kött 53 Utan tillsatt socker 54 Lågt sockerinnehåll 55 Äkta vara 56 Halal * 57 Kosher * 58 Vegan * 59 Vegetarisk * 60 Fritt från nötkött * 61 Fritt från fläskkött * 62 Hjärtat 63 Från Sverige 65 Svenskt sigill klimatcertifierad 67 ASC Certified 68 Vegecert 69 European Vegetarian Union * 70 Kött från Sverige

* Diet type

MartinAndServeraNo
MartinServeraNo
Martin&ServeraNo
String The Martin and Servera article number.
MenigoNo String The Menigo article number.
MenuConnections MenuItem[] For suppliers that use the category tree function. Returns a list of the categories the product is attached to
NordisktVaruNo String The Nordiskt Varu -article number.
Nutrition String The nutritional declaration of the product. This is always per 100 g/ml (measured).
NutritionSeparated Array of NutrientQuantitySeparated The nutritional declaration of the product, separated so that each row is returned separated into name, amount and unit
First each row is devided into a "Nutrient" and a "Quantity" ("Protein 12,7g" is divided into Nutrient: "Protein", Quantity: "12,7g"). These values are suitable if you just want to separate them into a table.
But Quantity is also separated into amount and unit, that can be acessed through the "QuantitySeparated" property. In this example it would mean that you get:
{ Nutrient: "Protein", Quantity: "12,7g", QuantitySeparated: { Amount: 12.7, Unit: "g" } }
NutritionPerServing NutritionPerServing The nutritional declaration of the product per serving. The amount per serving varies. Returns an object containing: Nutrition (String), ServingSizeGram (Integer) and ReferenceRecommendation (String). ReferenceRecommendation is a freetextfield that might be empty, or contain a string hinting of what type of "recommended" nutrient intake it is about, if this information is available.
NutritionPerServingSeparated Array of NutrientQuantitySeparated The nutritional declaration per serving of the product, separated so that each row is returned separated into name, amount and unit
First each row is devided into a "Nutrient" and a "Quantity" ("Protein 12,7g" is divided into Nutrient: "Protein", Quantity: "12,7g"). These values are suitable if you just want to separate them into a table.
But Quantity is also separated into amount and unit, that can be acessed through the "QuantitySeparated" property. In this example it would mean that you get:
{ Nutrient: "Protein", Quantity: "12,7g", QuantitySeparated: { Amount: 12.7, Unit: "g" } }
OrderableLevels Array of OrderableLevel Array of orderable levels (often cases), which contain this GTIN/EAN. Each orderable level has GTIN, a supplier assigned number and number of units. There might be multiple orderable levels. Not all products or suppliers do have this information stored, and if no information is found the array will be empty/null.
Origin String Information about the origin of the product.
PercentageOfAlcoholByVolume String The alcohol contant of the product by volume percent.
ProductId Long The internal id we've assigned to the product. This might change when a product is updated.
PrivabNo String The PrivabNo article number.
ProductName String The name of the product.
Recycling String Instructions for recycling.
Saletext String The suppliers sale text.
Storage String Instructions for storing the product.
SuppArtNr String The suppliers own article number for the product.
SupplierName String The name of the supplier (can also be retrieved through CompanyAddress).
SvenskCaterNo String The Svensk Cater -article number.
SvenskSnabbmatNo String The Svensk snabbmat article number.
BrandName
TrademarkName
String The brand name of the product.
VivLogNo String The VivLog article number.
WeightVolume String The weight / volume of the product.
Width Double The width of the product in mm.
ÖoBNo
OoBNo
String The ÖoB (Överskottsbolaget) article number.
Image_[Format]_[Size] String A link to the primary image in the requested format and size

See the Images section for more information.

AllImages_[Format]_[Size] OPVImage[] Returns all images attached to the product with some additional data about each image.

See the Images section for more information.

Return types.

Result classes

   class OperationResult
   {
       bool Success
       string ErrorMessage
   }
   class ProductResult : OperationResult
   {
       Product[] Data
   }
   class ArbitraryProductResult : OperationResult
   {
       ArbitraryProduct[] Data
   }
   class ChangedProductGTINResult : OperationResult
   {
       ChangedProductGTIN[] Data
   }
    class StringResult : OperationResult
   {
       string Data
   }
    class StringVecResult : OperationResult
   {
        string[] Data;
   }
   public class MenuItemResult : OperationResult
   {
       MenuItem[] Data
   }


Classes in the result Data property of the Result classes

    class Product
   {
       bool MatchFound      // If a matching GTIN could be found or not
       string GTIN
       string ImageURL
       string Supplier
       string Trademark
       string Name
       string WeightVolume
       string SuppArtNo
       string Content
       string Nutrition
       MarkInfo[] Markings
       AllergyInfo[] Allergies
       Company Company
   }
   class ArbitraryProduct
   {
       bool MatchFound      // If a matching GTIN could be found or not
       string GTIN
       NameValue[] Data
   class NameValue
    {
        string Name      // The requested column name
        object Value     // The value. Might be of varying data types
    }

   class ChangedProductGTIN
   {
       public string GTIN;
       public ChangeType TypeOfChange;
   }
   enum ChangeType
   {
       Updated,    // The information and images should be downloded anew, and replace the existing data
       Deleted     // The information and images retrieved fron us should be deleted in your system
   }

ArbitraryProduct.Data.Value might be of different data types. If not shown below, it will be a string:

   productid            long
   allergyinfo          AllergyInfo[]
   markings             MarkInfo[]
   width                double
   height               double
   depth                double
   launchdate           nullable DateTime
   changeddate          DateTime
   company              Company
   AllImages            OPVImage[]
   NutritionSeparated   NutrientQuantitySeparated[]
   CustomFields         CustomFieldInfo[]
   class AllergyInfo
   {
       int SubstanceId
       string SubstanceName           // Name of the substance, for instance "Gluten" or "Lupin"
       ContainsAllergenic Contains    // An enum indicating wheter it contains the allergen or not. No = 0, Possible = 1, Yes = 2. Possible is what often is rererred to as "Trace amounts"
       string Remark                  // Remark from the supplier for further information
   }
   class MarkInfo
   {
       int MarkId                     // Our internat id of the mark
       string MarkName                // The name of the mark
   }
   class CompanyAddress
   {
       string Name
       string Address
       string Zip
       string City
   }
   class NutritionPerServing
    {
        string Nutrition
        int ServingSizeGram
        string ReferenceRecommendation
    }

   class Company
   {
       string GLN
       CompanyAddress CompanyAddress  
   }
   class CustomFieldInfo
   {
       string Name            // The name of the custom field             
       string Value           // The value of the custom field
   }
   class NutrientQuantitySeparated
   {
       string Nutrient
       string Quantity
       string Comment
       AmountAndUnit SeparatedQuantity
   }

   class AmountAndUnit
   {
       double Amount
       string Unit
   }

    
   class BrandInfo
   {
        long BrandId
        string BrandName
   }

   class OrderableLevel
    {
        string GTIN
        string SupplierAssignedNumber
        int NumberOfUnits
    }

Classes returned when requesting AllImages from GetCustomProductData.

The letters within the parenthesis for the GDSN classes indicates the character it should be replaced with when generating a GDSN file name according to the 2013 standard

   class OPVImage
   {
       public string OPVNo = "";            // our internal id of the iamge. Probably not somehing that's of use to you
       public DBImageType ImagePurpose;     // Type of image. For example Product image, environment image, assortment image etc.
       public string ImageURL = "";         // The URL of the image
       public GDSNImageType? ImageType = null;   // Type of image according to GDSN
       public GDSNImageFacing? Facing = null;    // What primary side is visible
       public GDSNImageAngle? Angle = null;      // If it's taken from the front, or from a left or right angle
       public GDSNInOutPackage? InOutPackage = null;   // If it's photographed inside or out of its packaging
   }
   enum short DBImageType {
       ProductImage
       SpaceImage_front
       SpaceImage_top
       AssortmentImage
       EnvironmentImage
       SpaceImage_DF
       Video
       Audio
       Document
       SpaceImage_Side
   }
   enum GDSNImageType {
       StillShotSingleGTIN (A)
       StillShotSingleGTINWithElements (B)
       StillShotSingleGTINHiRes (C)
       StillShotSingleGTINWithElementsHiRes (D)
       Undetermined (Z)
   }
   enum GDSNImageFacing {
       NotApplicable (0)
       Front (1)
       Left (2)
       Top (3)
       Back (7)
       Right (8)
       Bottom (9)
   }
   enum GDSNImageAngle {
       Center (C)
       Left (L)
       Right (R)
       NoPlunge (N)
   }
   enum GDSNInOutPackge {
       OutOfpackaging (0)
       InPackaging (1)
       Case_ (A)
       Innerpack (B)
       RawOrUncooked (C)
       Prepared (D)
       Plated (E)
       Styled (F)
       Staged (G)
       Held (H)
       Worn (J)
       Used (K)
       Family (L)
       OpenCase (M)
   }

MenuItemResults Every MenuItem has three properties. A MenuItemId that's unique for each category in the tree (used when calling GetMenuCustomProductData), a Name-property containing the name of the category, and a Children-property of the type MenuItem[] containing all of its child categories.

   class MenuItem {
       long MenuItemId
       string Name
       long ParentId
       MenuItem[] Children
   }

Note that Children always is null when calling GetMenuSubItems, since that function only fetches one level at the time.