Validating Adverts (Products)
14 minute read
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 Primer
We advise that you read the Product Creation Primer guide before continuing with this article.Product Creation Flow
The high-level product creation flow is shown below:
These steps can be summarized as follows:
Step | Mandatory | Description | Depends On |
---|---|---|---|
1: Create product | Yes | Mandatory step in creating a core product entity | N/a |
2: Catalog Rules | Determined by Operator | Set of automated validation rules that can apply to a product, e.g. Product must have at least 3 images. | Step 1 |
3: Product Vetting | Determined by Operator | Operator controlled workflow for final product approval | Step 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
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:
- Product successfully created
- 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.
- 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:
Field | Object | What does it mean? |
---|---|---|
id | advert | An 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. |
errorMessages | advert | This 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. |
optionValuesInclusiveValid | variant | This 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. |
errors | validationError | These 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. |
published | advert | This is true because it is the sellers intent to publish the advert (see attemptAutoPublish field in the variable payload) |
displayable | advert | This 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:
Field | Object | What does it mean? |
---|---|---|
id | advert | An 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. |
errorMessages | advert | As we have omitted a mandatory option type, this collection is populated with that error detail. |
optionValuesInclusiveValid | variant | This is returning a false value now, as the variant does not have a positive stock holding against it. |
errors | validationError | The validation errors at the API call level are still empty as we did end up with a created advert. |
published | advert | Even though the seller has the intent to publish the advert, as the advert is invalid, published is false . |
displayable | advert | The 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:
- Omit the
title
field - this is a mandatory field- For a list of mandatory fields please refer to this article on
advertUpsert
- For a list of mandatory fields please refer to this article on
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:
Field | Object | What does it mean? |
---|---|---|
id | advert | N/a An advert was not created so we don’t see this as part of our response |
errorMessages | advert | N/a An advert was not created so we don’t see this as part of our response |
optionValuesInclusiveValid | variant | N/a An advert was not created so we don’t see this as part of our response |
errors | validationError | We’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. |
published | advert | N/a An advert was not created so we don’t see this as part of our response |
displayable | advert | N/a An advert was not created so we don’t see this as part of our response |
HTTP Status of 200OK?
One point worth mentioning is that from a GraphQL perspective, HTTP Status codes are not generally used to identify errors with the request, so even in this example we are getting a HTTP 200 OK response.
For more detail on this subject, please refer to our GraphQL Best Practices Playbook.
Step 2 - Catalog Rule Validations
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:
Field | Object | What does it mean? |
---|---|---|
id | advert | An Id is returned as an advert was successfully created |
errorMessages | advert | We have no error messages related directly to the core advert |
optionValuesInclusiveValid | variant | The variant is valid so this is true |
errors | validationError | We have no API level validation issues so this is null |
catalogRulesErrors | advert | We 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. |
requiresVetting | advert | We 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. |
published | advert | The core advert was successfully created, and even though we have Catalog Rules errors, published is true . |
displayable | advert | The 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
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:
Field | Object | What does it mean? |
---|---|---|
id | advert | An Id is returned as an advert was successfully created |
errorMessages | advert | We have no error messages related directly to the core advert |
optionValuesInclusiveValid | variant | The variant is valid so this is true |
errors | validationError | We have no API level validation issues so this is null |
catalogRulesErrors | advert | As we have supplied a value for specifications we no longer have any Catalog Rule violations. |
requiresVetting | advert | Vetting 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 |
vetted | advert | This advert has not been vetted yet, therefore this value is false |
vettingRejected | advert | This advert has not been rejected via vetting, therefore this value is false |
vettingRejectedReason | advert | This advert has not been rejected via vetting, therefore this value is null |
published | advert | The core advert was successfully created, and even though this advert needs to be vetted, published is true . |
displayable | advert | Vetting 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:
Field | Object | What does it mean? |
---|---|---|
requiresVetting | advert | The advert is currently rejected, therefore it does not currently require the operator to vet it until it is resubmitted. |
vetted | advert | While the advert has started its pass through the vetting cycle (and been rejected) it is not in a finalized vetted state. |
vettingRejected | advert | This advert has been rejected so this value is true |
vettingRejectedReason | advert | The operator has provided a reason as to why the advert was rejected. This would need to be rectified, and the advert resubmitted to vetting. |
vettingRejectedAt | advert | This is just a timestamp specifying when the advert was rejected. |
published | advert | The core advert was successfully created, and even though this advert was rejected at vetting, published is true . |
displayable | advert | Vetting still needs to occur, so displayable is false . |