Validating Adverts (Products)

In this example we take you through how to validate products have been created correctly and can be made available for sale.

What you’ll learn

  • The advert (product) creation workflow
  • The states of an advert
  • How to check the validity of an advert using the API

Product Creation Flow

The high-level product creation flow is shown below:

Product Flow

These steps can be summarized as follows:

StepMandatoryDescriptionDepends On
1: Create productYesMandatory step in creating a core product entityN/a
2: Catalog RulesDetermined by OperatorSet of automated validation rules that can apply to a product, e.g. Product must have at least 3 images.Step 1
3: Product VettingDetermined by OperatorOperator controlled workflow for final product approvalStep 1
Step 2

As illustrated above, an advert will not move into the vetting flow unless both a product has been successfully created and Catalog Rules have been satisfied.

Product State

The state of a product is determined by a number of factors, and again we advise that you read the Product Creation Primer for a detailed overview of those factors. Ultimately though, there are 2 high-level states:

  • Online / Published: These products are available for Sale
    • All steps in the creation flow have “passed”
  • Offline / Unpublished: These products are not available for sale
    • One or more steps in the creation flow have “failed”

In the remaining sections we’ll look at each of the product creation steps, and look at the ways you can use the API to validate whether each of those steps has been successful.

Validating Products

In this section we’ll look at how you can determine if a product is valid (or not) at each of the steps in the product creation flow.

Step 1 - Product Creation via API

Step 1

Note: In this section we are focused only on Step 1 - core product creation. So when we talk about an advert being correctly (or incorrectly) created at this step, we are not including whether Catalog Rules and or Vetting have passed. We are talking only about basic, core product creation.

When attempting to create a product using the API, you can experience 3 types of result:

  1. Product successfully created
  2. Product created but is invalid
    • This means that a product entity was created, but there are some issues with it e.g. required Option Types have not been assigned, zero stock for variants etc.
  3. Product does not get created - validation errors are thrown by the API
    • Mandatory fields were not correctly supplied

Let’s look at each of the result types in turn.

1: Product Successfully Created

An example of the advertUpsert mutation is shown below where all the criteria are in place to successfully create a core advert entity.

Mutation

mutation updateAdvertTitleById($input: AdvertUpsertMutationInput!) {
  advertUpsert(input: $input) {
    advert {
      id
      legacyId
      published
      displayable
      errorMessages {
        field
        messages
      }
      variants(displayableOnly: false) {
        nodes {
          id
          optionValuesInclusiveValid
        }
      }
    }
    errors {
      messages
      field
    }
  }
}

Variables

{
  "input": {
    "attributes": {
      "brandId": "QnJhbmQtMQ==",
      "taxonId": "VGF4b24tNzc0",
      "title": "Incredible Plastic Towels",
      "description": "Boston's most advanced compression wear technology increases muscle oxygenation, stabilizes active muscles",
      "price": "945.00",
      "attemptAutoPublish": true,
      "images": [
        {
          "sourceUrl": "https://picsum.photos/seed/wSOFam/640/480"
        }
      ],
      "advertOptionValues": [
        {
          "optionValueId": "T3B0aW9uVmFsdWUtMzU1MQ=="
        }
      ],
      "variants": [
        {
          "countOnHand": 990,
          "sku": "EZto",
          "variantOptionValues": [
            {
              "optionValueId": "T3B0aW9uVmFsdWUtNDY5Ng=="
            },
            {
              "optionValueId": "T3B0aW9uVmFsdWUtNzQwMg=="
            }
          ]
        }
      ]
    }
  }
}

This returns the following payload:

{
  "data": {
    "advertUpsert": {
      "advert": {
        "id": "QWR2ZXJ0LTEwMDAwMDIwOQ==",
        "legacyId": 100000209,
        "publsihed" : true,
        "displayable" : false,
        "errorMessages": [],
        "variants": {
          "nodes": [
            {
              "id": "VmFyaWFudC0yMjQ=",
              "optionValuesInclusiveValid": true
            }
          ]
        }
      },
      "errors": null
    }
  }
}
What are we seeing?

The important things to look for in the response are as follows:

FieldObjectWhat does it mean?
idadvertAn advert object has been created. Note that this alone does not mean that an advert is Online / Published, we need to look at the other fields we’ve queried.
errorMessagesadvertThis will return errors when (for example) required option values have not been supplied.

As this collection is empty - we have no errors of this type.
optionValuesInclusiveValidvariantThis boolean flag tells us whether the variant “is valid” it takes into consideration factors such as:

- A countOnHand of more than 0
-Sale price is less than the price
-Valid option values have been provided

And others - refer to the variant object definition for more detail.
errorsvalidationErrorThese are validation errors at the API call level, and will include things like missing mandatory fields. If you see errors of this nature (see Scenario 3 - Product does not get created) then the advert entity will not have been created.
publishedadvertThis is true because it is the sellers intent to publish the advert (see attemptAutoPublish field in the variable payload)
displayableadvertThis is false because although the advert has been correctly created at this stage, Catalog Rules and Vetting still need to be performed.

Important: If Catalog Rules and Vetting were not being used in this scenario, this advert would be in an Online / Published state as all other core criteria have been met.

2: Product created as invalid

In this second scenario, we’ll replay the same advertUpsert call, but will change the following details:

  • Omit the required advertOptionValues
  • Set the variant countOnHand to 0

Mutation

mutation updateAdvertTitleById($input: AdvertUpsertMutationInput!) {
  advertUpsert(input: $input) {
    advert {
      id
      legacyId
      published
      displayable
      errorMessages {
        field
        messages
      }
      variants(displayableOnly: false) {
        nodes {
          id
          optionValuesInclusiveValid
        }
      }
    }
    errors {
      messages
      field
    }
  }
}

Variables

{
  "input": {
    "attributes": {
      "brandId": "QnJhbmQtMQ==",
      "taxonId": "VGF4b24tNzc0",
      "title": "Incredible Plastic Towels",
      "description": "Boston's most advanced compression wear technology increases muscle oxygenation, stabilizes active muscles",
      "price": "945.00",
      "attemptAutoPublish": true,
      "images": [
        {
          "sourceUrl": "https://picsum.photos/seed/wSOFam/640/480"
        }
      ]
      "variants": [
        {
          "countOnHand": 0,
          "sku": "EZto",
          "variantOptionValues": [
            {
              "optionValueId": "T3B0aW9uVmFsdWUtNDY5Ng=="
            },
            {
              "optionValueId": "T3B0aW9uVmFsdWUtNzQwMg=="
            }
          ]
        }
      ]
    }
  }
}

This returns the following payload:

{
  "data": {
    "advertUpsert": {
      "advert": {
        "id": "QWR2ZXJ0LTEwMDAwMDIxMg==",
        "legacyId": 100000212,
        "publsihed" : false,
        "displayable" : false,
        "errorMessages": [
          {
            "field": "option_type_100",
            "messages": [
              "Style must be selected"
            ]
          }
        ],
        "variants": {
          "nodes": [
            {
              "id": "VmFyaWFudC0yMjc=",
              "optionValuesInclusiveValid": false
            }
          ]
        }
      },
      "errors": null
    }
  }
}
What are we seeing?

The important things to look for in the response are as follows:

FieldObjectWhat does it mean?
idadvertAn advert object has been created. This alone does not mean that the advert is valid. This point is illustrated by the fact we now have various error conditions attached to the advert.
errorMessagesadvertAs we have omitted a mandatory option type, this collection is populated with that error detail.
optionValuesInclusiveValidvariantThis is returning a false value now, as the variant does not have a positive stock holding against it.
errorsvalidationErrorThe validation errors at the API call level are still empty as we did end up with a created advert.
publishedadvertEven though the seller has the intent to publish the advert, as the advert is invalid, published is false.
displayableadvertThe advert is invalid on a number of levels, therefore published is false

3: Product does not get created

In this final scenario at the Product Creation step, the API level validations will kick in and the advert will not be created. We do this by replaying the same advertUpsert mutation and:

Mutation

mutation updateAdvertTitleById($input: AdvertUpsertMutationInput!) {
  advertUpsert(input: $input) {
    advert {
      id
      legacyId
      published
      displayable
      errorMessages {
        field
        messages
      }
      variants(displayableOnly: false) {
        nodes {
          id
          optionValuesInclusiveValid
        }
      }
    }
    errors {
      messages
      field
    }
  }
}

Variables

{
  "input": {
    "attributes": {
      "brandId": "QnJhbmQtMQ==",
      "taxonId": "VGF4b24tNzc0",
      "description": "Boston's most advanced compression wear technology increases muscle oxygenation, stabilizes active muscles",
      "price": "945.00",
      "attemptAutoPublish": true,
      "images": [
        {
          "sourceUrl": "https://picsum.photos/seed/wSOFam/640/480"
        }
      ],
      "advertOptionValues": [
        {
          "optionValueId": "T3B0aW9uVmFsdWUtMzU1MQ=="
        }
      ],
      "variants": [
        {
          "countOnHand": 990,
          "sku": "EZto",
          "variantOptionValues": [
            {
              "optionValueId": "T3B0aW9uVmFsdWUtNDY5Ng=="
            },
            {
              "optionValueId": "T3B0aW9uVmFsdWUtNzQwMg=="
            }
          ]
        }
      ]
    }
  }
}

This returns the following payload:

{
  "data": {
    "advertUpsert": {
      "advert": null,
      "errors": [
        {
          "messages": [
            "must be filled in"
          ],
          "field": "title"
        }
      ]
    }
  }
}
What are we seeing?

The important things to look for in the response are as follows:

FieldObjectWhat does it mean?
idadvertN/a An advert was not created so we don’t see this as part of our response
errorMessagesadvertN/a An advert was not created so we don’t see this as part of our response
optionValuesInclusiveValidvariantN/a An advert was not created so we don’t see this as part of our response
errorsvalidationErrorWe’ve actually hit an API level validation issue (missing title) so the errors at this level kick in. As you can see from the response it tells us which field is missing.
publishedadvertN/a An advert was not created so we don’t see this as part of our response
displayableadvertN/a An advert was not created so we don’t see this as part of our response

Step 2 - Catalog Rule Validations

Step 2

In this section we’ll look at how you can obtain any issues related to Catalog Rules. This is in fact very straightforward, all we need to do is add an additional field to our mutation response: catalogRulesErrors as shown below:

Mutation

mutation updateAdvertTitleById($input: AdvertUpsertMutationInput!) {
  advertUpsert(input: $input) {
    advert {
      id
      legacyId
      published
      displayable
      catalogRulesErrors {
        objectId
        fieldName
      }
      requiresVetting
      errorMessages {
        field
        messages
      }
      variants(displayableOnly: false) {
        nodes {
          id
          optionValuesInclusiveValid
        }
      }
    }
    errors {
      messages
      field
    }
  }
}

Variables

{
  "input": {
    "attributes": {
      "brandId": "QnJhbmQtMQ==",
      "taxonId": "VGF4b24tNzc0",
      "title": "Incredible Plastic Towels",
      "description": "Boston's most advanced compression wear technology increases muscle oxygenation, stabilizes active muscles",
      "price": "945.00",
      "attemptAutoPublish": true,
      "images": [
        {
          "sourceUrl": "https://picsum.photos/seed/wSOFam/640/480"
        }
      ],
      "advertOptionValues": [
        {
          "optionValueId": "T3B0aW9uVmFsdWUtMzU1MQ=="
        }
      ],
      "variants": [
        {
          "countOnHand": 990,
          "sku": "EZto",
          "variantOptionValues": [
            {
              "optionValueId": "T3B0aW9uVmFsdWUtNDY5Ng=="
            },
            {
              "optionValueId": "T3B0aW9uVmFsdWUtNzQwMg=="
            }
          ]
        }
      ]
    }
  }
}

This returns the following payload:

{
  "data": {
    "advertUpsert": {
      "advert": {
        "id": "QWR2ZXJ0LTEwMDAwMDIxNQ==",
        "legacyId": 100000215,
        "publsihed" : true,
        "displayable" : false,
        "catalogRulesErrors": [
          {
            "objectId": "17808080",
            "fieldName": "specifications"
          }
        ],
        "requiresVetting": false,
        "errorMessages": [],
        "variants": {
          "nodes": [
            {
              "id": "VmFyaWFudC0yMzA=",
              "optionValuesInclusiveValid": true
            }
          ]
        }
      },
      "errors": null
    }
  }
}
What are we seeing?

The important things to look for in the response are as follows:

FieldObjectWhat does it mean?
idadvertAn Id is returned as an advert was successfully created
errorMessagesadvertWe have no error messages related directly to the core advert
optionValuesInclusiveValidvariantThe variant is valid so this is true
errorsvalidationErrorWe have no API level validation issues so this is null
catalogRulesErrorsadvertWe have a Catalog Rule that stipulates that all products must supply a value for specifications, in this case we have not, therefore we have a response for this collection.
requiresVettingadvertWe have introduced this field to illustrate the point that even though we have advert vetting enabled, this value returns false at this stage because Catalog Rules have failed.
publishedadvertThe core advert was successfully created, and even though we have Catalog Rules errors, published is true.
displayableadvertThe Catalog Rules errors (along with Vetting) continue to make displayable = false

Querying for adverts with failed Catalog Rules

In the previous example we have shown you how to return the value of catalogRulesErrors at the point when you create products. You can also use the advertsWhere query to return all the adverts that have Catalog Rules issues as shown below:

query {
  advertsWhere(withFailedCatalogRules: true, first: 10) {
    totalCount
    pageInfo {
      hasNextPage
      endCursor
    }
    nodes {
      id
      catalogRulesErrors {
        errorMessage
        fieldName
      }
    }
  }
}

Step 3 - Product Vetting Validations

Step 3

In this final section we look at understanding products that:

  • Require Vetting
  • Have failed Vetting

This is covered in more detail in this article.

Requires Vetting

In this scenario we’ll replay the advertUpsert mutation and ensure:

  • We provide all core attributes to pass Step 1
  • We provide specifications to ensure Step 2 (Catalog Rules) pass

Mutation

mutation updateAdvertTitleById($input: AdvertUpsertMutationInput!) {
  advertUpsert(input: $input) {
    advert {
      id
      legacyId
      published
      displayable
      catalogRulesErrors {
        objectId
        fieldName
      }
      requiresVetting
      vetted
      vettingRejected
      vettingRejectedReason
      errorMessages {
        field
        messages
      }
      variants(displayableOnly: false) {
        nodes {
          id
          optionValuesInclusiveValid
        }
      }
    }
    errors {
      messages
      field
    }
  }
}

Variables

{
  "input": {
    "attributes": {
      "brandId": "QnJhbmQtMQ==",
      "taxonId": "VGF4b24tNzc0",
      "title": "Incredible Plastic Towels",
      "description": "Boston's most advanced compression wear technology increases muscle oxygenation, stabilizes active muscles",
      "specifications": "Something",
      "price": "945.00",
      "attemptAutoPublish": true,
      "images": [
        {
          "sourceUrl": "https://picsum.photos/seed/wSOFam/640/480"
        }
      ],
      "advertOptionValues": [
        {
          "optionValueId": "T3B0aW9uVmFsdWUtMzU1MQ=="
        }
      ],
      "variants": [
        {
          "countOnHand": 990,
          "sku": "EZto",
          "variantOptionValues": [
            {
              "optionValueId": "T3B0aW9uVmFsdWUtNDY5Ng=="
            },
            {
              "optionValueId": "T3B0aW9uVmFsdWUtNzQwMg=="
            }
          ]
        }
      ]
    }
  }
}

This would return the following payload:

{
  "data": {
    "advertUpsert": {
      "advert": {
        "id": "QWR2ZXJ0LTEwMDAwMDIxNw==",
        "legacyId": 100000217,
        "publsihed" : true,
        "displayable" : false,
        "catalogRulesErrors": null,
        "requiresVetting": true,
        "vetted": false,
        "vettingRejected": false,
        "vettingRejectedReason": null,
        "errorMessages": [],
        "variants": {
          "nodes": [
            {
              "id": "VmFyaWFudC0yMzI=",
              "optionValuesInclusiveValid": true
            }
          ]
        }
      },
      "errors": null
    }
  }
}
What are we seeing?

The important things to look for in the response are as follows:

FieldObjectWhat does it mean?
idadvertAn Id is returned as an advert was successfully created
errorMessagesadvertWe have no error messages related directly to the core advert
optionValuesInclusiveValidvariantThe variant is valid so this is true
errorsvalidationErrorWe have no API level validation issues so this is null
catalogRulesErrorsadvertAs we have supplied a value for specifications we no longer have any Catalog Rule violations.
requiresVettingadvertVetting has been opted into by this operator, so this advert now progresses to that flow. This illustrated by the fact that the value of requiresVetting is true
vettedadvertThis advert has not been vetted yet, therefore this value is false
vettingRejectedadvertThis advert has not been rejected via vetting, therefore this value is false
vettingRejectedReasonadvertThis advert has not been rejected via vetting, therefore this value is null
publishedadvertThe core advert was successfully created, and even though this advert needs to be vetted, published is true.
displayableadvertVetting still needs to occur, so displayable is false.

It’s worth pointing out at this stage that as someone responsible for creating adverts (usually a Seller) there is not much more we can do at this point to move the product along the flow. It is now with the operator to look at the product and vet accordingly.

Querying for adverts that require vetting

In the previous example we have shown you how to return the value of requiresVetting at the point when you create products. You can also use the advertsWhere query to return all the adverts that require vetting as shown below:

query {
  advertsWhere(requiresVetting: true, first: 10) {
    totalCount
    pageInfo {
      hasNextPage
      endCursor
    }
    nodes {
      id
      requiresVetting
      vetted
      vettingRejected
      vettingRejectedReason
      vettingRejectedAt
    }
  }
}
Failed Vetting

In this scenario we are not dealing with advert creation as that has already been conducted. Here we are looking at simply how to query products that have been rejected via vetting. Again you can do this with the advertsWhere query:

query {
  advertsWhere(rejectedViaVetting: true, first: 10) {
    totalCount
    pageInfo {
      hasNextPage
      endCursor
    }
    nodes {
      id
      requiresVetting
      vetted
      vettingRejected
      vettingRejectedReason
      vettingRejectedAt
      published
      displayable
    }
  }
}

This would return the following if there were any adverts rejected via vetting:

{
  "data": {
    "advertsWhere": {
      "totalCount": 1,
      "pageInfo": {
        "hasNextPage": false,
        "endCursor": "MQ"
      },
      "nodes": [
        {
          "id": "QWR2ZXJ0LTEwMDAwMDIxNw==",
          "requiresVetting": false,
          "vetted": false,
          "vettingRejected": true,
          "vettingRejectedReason": "The image quality does not adhere to our listing guidelines.",
          "vettingRejectedAt": "2025-03-25T13:12:32+01:00",
          "published" : true,
          "displayable": false
        }
      ]
    }
  }
}
What are we seeing?

The important things to look for in the response are as follows:

FieldObjectWhat does it mean?
requiresVettingadvertThe advert is currently rejected, therefore it does not currently require the operator to vet it until it is resubmitted.
vettedadvertWhile the advert has started its pass through the vetting cycle (and been rejected) it is not in a finalized vetted state.
vettingRejectedadvertThis advert has been rejected so this value is true
vettingRejectedReasonadvertThe operator has provided a reason as to why the advert was rejected. This would need to be rectified, and the advert resubmitted to vetting.
vettingRejectedAtadvertThis is just a timestamp specifying when the advert was rejected.
publishedadvertThe core advert was successfully created, and even though this advert was rejected at vetting, published is true.
displayableadvertVetting still needs to occur, so displayable is false.