Contents |
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.
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 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.
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.
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.
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
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.
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.
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. |
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.