Marketplaces / Tesco (on Marketplacer) / Tesco Technical Scope / Tesco Product Management / Tesco Product Create

Tesco Product Create

Version Date Created / Updated Notes
v1.0 Hristiyan Georgiev Initial version
v1.1 12/05/2025 Hristiyan Georgiev mapping changes
v1.2 12/052025 Hristiyan Georgiev Added logic for adding new products to variation

The purpose of this page is to give good understanding in how the product listing will work with Tesco.

One important thing to note is that Tesco calls their products “Adverts” and “Variants”. An advert is basically the product and variant is all the variations of this product. A little bit of info :

An Advert has the following characteristics:

  • Adverts are created, and placed for sale on the Marketplace by a Seller.
  • An Advert has a large number of attributes (full list here)
  • Adverts must have at least 1 Variant, as it’s at the variant level that concepts such as Inventory, SKUs and Barcodes reside. This means that we need to create a logic and even if we have a single product, we need to list it as variant. It should not be required for the single product to have variation group id in Hemi.
  • Adverts can be created via a number of different methods, in this scope we will focus on how we can create Adverts using the GraphQL advertUpsert mutation
  • Adverts can be either:
    • “Online” - essentially available for purchase or
    • “Offline” - cannot be purchased. This may be because the Seller doesn’t wish to sell that product any longer, or there may be a problem with the way the advert has been created, making it invalid - e.g. it has not been assigned to a category
  • The Advert (and the way it can be created) is tightly coupled to the categorization that it’s placed into. Tesco calls this categorization hierarchy the “Taxonomy”.

API Docs : https://api.marketplacer.com/docs/seller-api/examples/products/howto_addproducts_latest/

All triggers, validations etc. are as per our abstraction for Product Creation. Product Listing general requirements

<1.2> We also want to add an additional trigger which should be Listings Tesco > Add Variant Product = false </v1.2>

<v1.2> Since adding variants to an existing variation group requires a separate API call, our product creation logic needs an additional step. When we select all products based on the abstraction triggers, we need to check if any product(s) in the variation already have a Channel Item ID. If they do, it indicates that we are adding a variant rather than creating a new listing on Tesco, and we must prepare the product accordingly. To facilitate this, we will introduce a new flag in the Listings Tesco table called Add Variant Product. Once we’ve performed the necessary checks and confirmed that the product is a variant, we will set this flag to true and leave the Listings > List/Update the Whole Item status as Pending.

</v1.2>

<v1.1>GraphQL Query </v1.1>:

mutation AdvertCreate($input: AdvertUpsertMutationInput!) {
    advertUpsert(input: $input) {
        status
        advert {
            id              
            displayable          
            catalogRulesErrors{
                        errorMessage
                        fieldName
                        objectId                        
                    }
            variants(displayableOnly: false)
            {
                nodes {
                    sku
                    id
                    displayable

                }
            }
        }
        errors {
            field
            messages            
                }

    }
}

Query Variables :

{
  "input": {
    "attributes": {
      "brandId": "QnJhbmQtNA==",
      "taxonId": "VGF4b24tMjgw",
      "title": "Noviq Test",
      "description": "adasdasfrthrthrthrt",
      "productFeatures": ["Super Best, Very Good", "Amazin' Feature2", "the best feature3"],
      "shippingParcelAttributes": {
        "weight": 100,
        "massUnit": "g",
        "length": 5,
        "width": 6,
        "depth": 15,
        "distanceUnit": "cm"
      },
      "saleType": "BUY_ONLINE",
      "attemptAutoPublish": true,
      "images": [
        {
          "sourceUrl": "https://images.puma.net/images/398352/03/fnd/GBR/"
        },
        {
          "sourceUrl": "https://images.puma.net/images/398672/02/sv01/fnd/GBR/"
        },
        {
          "sourceUrl": "https://images.puma.net/images/398672/02/sv02/fnd/GBR/"
        }
      ],
      "advertOptionValues": [
        {
          "optionValueId": "T3B0aW9uVmFsdWUtNzM0"
        },
        {
          "optionValueId": "T3B0aW9uVmFsdWUtNzI4"
        },
        {
          "optionValueId": "T3B0aW9uVmFsdWUtNzI5"
        },
        {
          "optionTypeId": "T3B0aW9uVHlwZS0yNTA=",
          "textValue": "asdasda"
        }
      ],
      "variants": [
        {
          "description": "Its amazing!",
          "sku": "123456",
          "barcode": "6723827005389",
          "price": "99",
          "salePrice": "39",
          "countOnHand": 99,
          "images": [
            {
              "sourceUrl": "https://images.puma.net/images/398672/02/fnd/GBR/"
            }
          ],
          "variantOptionValues": [
            {
              "optionValueId": "T3B0aW9uVmFsdWUtNzMy"
            },
            {
              "optionValueId": "T3B0aW9uVmFsdWUtNzIz"
            },
            {
              "optionValueId": "T3B0aW9uVmFsdWUtNzI0"
            },
            {
              "optionTypeId": "T3B0aW9uVHlwZS0yNTI=",
              "textValue": "Very not cool Color"
            }
          ]
        },
        {
          "description": "Its amazing!",
          "sku": "654321",
          "price": "99",
          "salePrice": "59",
          "countOnHand": 88,
          "images": [
            {
              "sourceUrl": "https://images.puma.net/images/398352/03/fnd/GBR/"
            }
          ],
          "variantOptionValues": [
            {
              "optionValueId": "T3B0aW9uVmFsdWUtNzMx"
            },
            {
              "optionValueId": "T3B0aW9uVmFsdWUtNzIz"
            },
            {
              "optionValueId": "T3B0aW9uVmFsdWUtNzI0"
            },
            {
              "optionTypeId": "T3B0aW9uVHlwZS0yNTI=", "textValue": "Very not cool Color"
            }
          ]
        }
      ]
    }
  }
}

Variables Mapping :

Tesco Field Integration Requried Hemi Mapping Hemi Notes
input
attributes
brandId Yes Product > Brand

OR Product Account > Item Specifics | Product Accountis with priority. We need to send the Id of the brand as per the taxonomy. | | | taxonId | | | Yes | Product Account > Primary Category ID | We need to send the Id of the category | | | title | | | Yes | Product Account > Title | Since we keep this info on Product Account level, we might have a case where we have a variation and we have different titles across the variations. In this case we want to pick the Proudct Account with the lowest ID and use its title. | | | description | | | Yes | Product Account > Description | | | | productFeatures | | | No | Product Account Tesco > Product Features | New field! This should be a field with the possibility to “add more features”. Similar to the Item/Variation specifics fields. The only difference is that here we will have only one box for input instead of two. Then each row needs to be separated by comma in the payload. | | | shippingParcelAttributes | | | | | | | | | weight | | Conditional | Product > Weight | We need to send this together with massUnit. If we are not sending weight, we need to exclude massUnit from the payload | | | | massUnit | | Conditional | | Hardcoded as “g” . We only need to send it when sending weight | | | | length | | Conditional | Product > Length | We need to send either all 3 of length, width and depth or none of them. | | | | width | | Conditional | Product > Width | We need to send either all 3 of length, width and depth or none of them. | | | | depth | | Conditional | Product > Height | We need to send either all 3 of length, width and depth or none of them.. | | | | distanceUnit | | Conditional | | Hardcoded as ‘cm’ . We want to exclude this if we are not sending any measurements. | | | saleType | | | No | Product Account Tesco > Sale Type | New field! Should be an enumeration dropdown with the following options :

  • Buy Online sent as BUY_ONLINE
  • Buy Online & Click and Collect sent as BUY_ONLINE_OR_CLICK_AND_COLLECT
  • Click and Collect sent as CLICK_AND_COLLECT Our default option should be BUY_ONLINE and if we don’t have a record in Product Account Tesco, we send BUY_ONLINE as default when creating products. | | | attemptAutoPublish | | | No | Product Account Tesco > Auto Publish | New field. Should be a radio button with options “Yes” and “No”. When Yes is selected we send true When No is selected we send false Default option should be “Yes” | | | images | | | | | | | | | sourceUrl | | Yes | (6) All Images (Leading + Additional) | As per the images abstraction - Images Handling Additional Explanation | | | advertOptionValues | | | | | | | | | optionValueId | | Conditional | Product Account > Item Specifics | We need to send the ID corresponding to the item specific value. We send either this or optionTypeId and this depends on if we have FREE_TEXT field type in the taxonomy or not. | | | | optionTypeId | | Conditional | Product Account > Item Specifics | We need to send the ID corresponding to the item specific name. We only need to send this if we have FREE_TEXT field type in the taxonomy | | | | textValue | | Conditional | Product Account > Item Specifics Value | This becomes required if we are sending optionTypeId | | | variants | | | | | | | | | description | | No | Product Account > Description | The design template logic remains so if we have design template we pick as per abstraction. | | | | sku | | No | Product > SKU | | | | | barcode | | No | Product Account > Marketplace EAN OR Product > EAN OR Product > Barcode OR Product > MPN OR Product >UPC | Product Account is with priority. The priority in the other fields is : EAN, Barcode, MPN, UPC | | | | price | | No | Product Account > RRP | We need to have a validation and if we have RRP less than or equal to Price we need to return an internal error as Tesco would not allow this. The RRP always has to be higher than the Price. If we don’t have RRP at all in Hemi, we just exclude it from the payload. | | | | salePrice | | No | Product Account > Price | We need to have validation and we should not be able to send negative price or price equal to 0 | | | | countOnHand | | No | Product Account > Quantity | | | | | images | | | | | | | | | sourceUrl | No | (2) Main Image | We want to have a logic and only send the Main image for the specific variant Images Handling Additional Explanation | | | | variantOptionValues | | | | | | | | | optionValueId | Conditional | Product Account > Variation Specifics | Same logic as above. | | | | | optionTypeId | Conditional | Product Account > Variation Specifics | Same logic as above. | | | | | textValue | Conditional | Product Account > Variation Specifics Value | Same logic as above. |

Example success response :

{
    "data": {
        "advertUpsert": {
            "status": 200,
            "advert": {
                "id": "QWR2ZXJ0LTEwMDEwMjIwNA==",
                "displayable": false,
                "catalogRulesErrors": [
                    {
                        "errorMessage": "Product description must be greater than 10 characters",
                        "fieldName": "description",
                        "objectId": "3976020"
                    }
                ],
                "variants": {
                    "nodes": [
                        {
                            "sku": "12345666",
                            "id": "VmFyaWFudC0xMzA3NTc=",
                            "displayable": true
                        },
                        {
                            "sku": "321123",
                            "id": "VmFyaWFudC0xMzA3NTg=",
                            "displayable": false
                        }
                    ]
                }
            },
            "errors": null
        }
    }
}

Example error response :

{
    "data": {
        "advertUpsert": {
            "status": 200,
            "advert": null,
            "errors": [
                {
                    "field": "description",
                    "messages": [
                        "must be filled in"
                    ]
                }
            ]
        }
    }
}

Response Mapping :

Integration Field Hemi Mapping Hemi Notes
data
advertUpsert
status N/A
advert
id Product Account > Channel Item ID
<v1.1>displayable This is a boolean flag. We need to create a logic :

If this is true we set the Product Account > Product Status = Product Published If this is false we set the Product Account > Product Status = Product Created</v1.1> | | | | | <v1.1>catalogRulesErrors | | | | | | | | | | errorMessage | | Product Account > Update Item Error | If we receive this field and errors object with errors, we want to store all errors together. This error should be stored across all products in the variation. | | | | | | fieldName | | N/A | | | | | | | objectId | | N/A | </v1.1> | | | | | variants | | | | | | | | | | nodes | | | | | | | | | | sku | | With this field we will know which SKU to assign the id to. | | | | | | | id | Product Account Tesco > Variant ID | | | | | | | | <v1.1>displayable | | This is a boolean flag. We need to create a logic : If we receive this as false or if we have 0 quantity for the product, we set Product Account > Listing Status = Inactive In any other case, we set Product Account > Listing Status = Active</v1.1> | | | | errors | | | | | | | | | | field | | | Product Account > Update Item Error | We need to concatenate field and messages and store them together. <v1.1>If we receive an error into catalogRulesErrors > errorMessage we need to store all errors together.</v1.1> | | | | | messages | | | Product Account > Update Item Error | We need to concatenate field and messages and store them together. <v1.1>If we receive an error into catalogRulesErrors > errorMessage we need to store all errors together.</v1.1> |

<v1.1>In all other cases want to update the product statuses as per our abstraction - Product Listing general requirements </v1.1>

If we get an error response we are to store it as per the mapping above, and set the Product Account > List/Update the whole item = Error

<v1.2>Adding new item to variation

We have the possibility to add a new product into an already created variation but this is done through a different mutation and we will need to handle this as well.

The triggers for this call will be : Listings Tesco > Add Variant Product = true Listings > List/Update the Whole Item = Pending

Listings > Channel Item ID = ‘’

Listings > Product Status = Awaiting Creation

GraphQL Query :

mutation addNewVariantToAdvert($input: AdvertUpsertMutationInput!) {
  advertUpsert(input: $input)
   {
    advert {
      id      
      catalogRulesErrors{
                        errorMessage
                        fieldName
                        objectId                        
                    }
      variants(displayableOnly: false) {
        nodes {
          sku  
          id
          displayable                    
        }
      }
    }
    errors {
      messages
      field
    }
  }
}

Query Variables :

{
  "input": {
    "advertId": "QWR2ZXJ0LTEwMDEwMjE4Ng==",
    "attributes": {
      "variants": [
        {
          "description": "Its amazing!",
          "sku": "65431121",
          "price": "99",
          "salePrice": "59",
          "countOnHand": 88,
          "images": [
            {
              "sourceUrl": "https://images.puma.net/images/398352/03/fnd/GBR/"
            },
            {
              "sourceUrl": "https://images.puma.net/images/398352/03/fnd/GBR/"
            },
            {
              "sourceUrl": "https://images.puma.net/images/398352/03/fnd/GBR/"
            },
            {
              "sourceUrl": "https://images.puma.net/images/398352/03/fnd/GBR/"
            }
          ],
          "variantOptionValues": [
            {
              "optionValueId": "T3B0aW9uVmFsdWUtNzMx"
            },
            {
              "optionValueId": "T3B0aW9uVmFsdWUtNzIz"
            },
            {
              "optionValueId": "T3B0aW9uVmFsdWUtNzI0"
            },
            {
              "optionTypeId": "T3B0aW9uVHlwZS0yNTI=",
              "textValue": "Very not cool Color"
            }
          ]
        }
      ]
    }
  }
}

From the variables we pick the advertId from Product Account > Channel Item ID of some other product from this variation group (it should be the same across all products so it doesn’t matter which, but let’s say we pick from the one with the lowest id) and then the rest of the mapping is the same as in the Create call.

Example response :

{
    "data": {
        "advertUpsert": {
            "advert": {
                "id": "QWR2ZXJ0LTEwMDEwMjE4Nw==",
                "catalogRulesErrors": null,
                "variants": {
                    "nodes": [
                        {
                            "sku": "6542226661",
                            "id": "VmFyaWFudC0xMzA3MzQ=",
                            "displayable": false
                        },
                        {
                            "sku": "100kila",
                            "id": "VmFyaWFudC0xMzA3MzU=",
                            "displayable": false
                        },
                        {
                            "sku": "12345666",
                            "id": "VmFyaWFudC0xMzA3MzM=",
                            "displayable": false
                        }
                    ]
                }
            },
            "errors": null
        }
    }
}

The response will show us all the variants within this advert (including the existing ones). The mapping and the error handling is the same as the product creation, with the addition that if we have successfully added the product to variation, we set the Listings Tesco > Add Variant Product to false. If we have received an error, we keep the Listings Tesco > Add Variant Product to true. We also want to incorporate the displayable logic from the Listing Create</v1.2>

Is this article helpful?
0 0 0