Only this pageAll pages
Powered by GitBook
1 of 18

Apps

Loading...

Loading...

Detailed reference

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Changelog

Loading...

Loading...

Dictionaries

Extend the configuration via dictionaries

Sometimes you need to choose from a predefined list of values already existing in the system.

In the configuration, it's not always easy as the values may be related to the configuration itself. The values may be available only after configuring the API Key in step one etc, therefore, cannot be part of the regular schema.

That's where the dictionaries come in handy. There are two sources of the configuration. The data either come from Ergonode or App.

The source of the dictionary is recognized via prefixes ergonode: and app:

Ergonode

List of available dictionaries

  • ergonode:attributes

    • it is possible to limit types of attributes thanks to query parameters - egonode:attributes?type=TEXT,TEXT_AREA

  • ergonode:categories

  • ergonode:languages

App

In App, you can define as many custom dictionaries as you require.

In Manifest configuration_schema you reference the dictionary by the conjunction of app: prefix and dictionary ID suffix i.e app:attributes.

App provides custom dictionaries by implementing /dictionary/{dictionary} endpoint.

For the above example, the endpoint would be /dictionary/attributes.

The dictionary endpoint will be called only when the step of the configuration schema it is defined in is generated, so you have all the configuration from earlier steps at your disposal during dictionary retrieval(like API keys, etc).

The endpoint response for the valid dictionary should return

{
  "dictionary": [
    {
      "id": "value",
      "label": "Presentation label"
    }
  ]
}

Type validation

If your mapping requires extra type validation, you may provide type for every entry that matches the mapping context. For multiple types, separate them with |.

{
  "dictionary": [
    {
      "id": "description",
      "label": "Description attribute",
      "type": "TEXT_AREA|TEXT"
    }
  ]
}

Merging values

By default, mapping on dictionary items allows only one value to be mapped to a specific attribute. It is possible to override this behavior by declaring the possibility of merging via allows_merging flag.

If enabled, the App is responsible for merging value itself according to internal strategy - flag only changes the behavior of the mapper, and values are provided to App the same way.

{
  "dictionary": [
    {
      "id": "description",
      "label": "Description attribute",
      "allows_merging": true
    }
  ]
}

File download endpoint

If an App purpose it to generate a file as a result of synchronization enable any of the following features based on your needs

  • synchronization_file_download

  • synchronization_file_download_latest

To allow access to Ergonode for generated files you need to implement the following endpoint.

/download-file

Use synchronization_id claim from JWT to determine which file should be returned.

The endpoint should return the binary file.

The response can contain content-type and content-length HTTP headers so that those are passed to the browser on file download.

Breaking changes

The list of recent and upcoming breaking changes

The list of recent and upcoming breaking changes At Ergonode we strive to make your integration process as fluent as possible but from time to time we discover a flaw in the design of the schema or introduce a really cool new feature that is impossible to be provided in a fully-compatible non-breaking way.

In such a situation, we will always aim to not break your integration with the new release and provide you with a transition period of approximately 3-months to adjust your consumer. Every such change shall be communicated in the following list.

Changelog

The changelog is a list of recent changes to Apps.

2025-04-10

  • synchronization_scheduler feature added

2024-05-14

  • Apps release 🎉

Quick start

Overview of steps required to create an App

To create an App you only need basic development knowledge.

You are not limited to a specific programming language, only to the possibilities granted via Ergonode by its communication interfaces.

To develop a functional App you only need to follow a couple of simple steps.

Create a Manifest

Manifest reference

Firstly, you need to provide a Manifest - a simple JSON file determining the basic App behavior and possibilities it is utilizing.

The file is required for the app registration process for the New App to display in your available apps tab.

Prepare authentication

Authentication reference

Next up it's essential to keep your data safe. To achieve that you should have a functional authentication system.

The App has to be able to identify itself so that Ergonode will know it can perform the API operation it is trying to execute. It is also crucial that when the App receives a request it can determine whether it comes from a trustworthy source.

On App installation, Ergonode exchanges handshake with App with data allowing authentication of the following requests performed, assuring that no 3rd party is tempering the data.

Handle your configuration

Configuration reference

Some Apps require dedicated configuration. Once provided by Manifest App users will be able to set up their App within Ergonode to make it work appropriately to the installation needs - App should validate and persist your configuration.

Implement event endpoints

Event endpoints reference

If your App needs to know about basic events create specific events handling.

Implement your business logic

It is up to you what exactly the App will do.

Implement synchronization endpoints

This step is optional and depends on your intent.

Synchronization endpoints reference

If your App is meant to generate a file, integrate the data into the eCommerce system, or has a similar purpose forcing having the long-running process to handle all of the data, rather than implementing the entire synchronization yourself, let Ergonode orchestrate the process and focus only on the development of your business logic.

Host your application

Host your application on an HTTP server of choice.

For development and testing use tools like ngrok to make quicker code iterations.

Ergonode currently does not provide hosting for custom Apps.

Register your App

Once all set up register your App in the available apps tab.

To refresh the App Manifest, it has to be changed and reapplied just like on App registration. Ergonode does not refresh it automatically.

Remember also, on refreshment, to raise the version - otherwise, changes won't be applied as described in the version restrictions description.

Click on New app button
Fill your Manifest URL

Authentication

Description of concepts behind authentication system and how to prepare your own security.

Requests between Ergonode and App are signed using tokens.

These tokens allow the safe exchange of custom data(claims) between two parties.

The token contains claims describing the context of the execution - what specific installation the token is referencing etc.

The token is always available in X-APP-TOKEN HTTP header with a signature signed using HMAC SHA-256.

Note you should not be forced to implement JWT authentication on your own.

There are multiple well-developed and acknowledged open-source solutions to handle the issue.

Handshake

To authenticate the token firstly, on App installation, a handshake is exchanged.

Handshake is a request to [POST] /handshake path of .

It contains two important pieces of information:

  • X-APP-TOKEN HTTP header

  • shared secret in the request body

This secret should be persisted, i.e. the database of choice, and kept by your App safe with information about the app installation it belongs to and the Ergonode API URL retrieved from claims:

  • app_installation_id

  • api_url

All the following requests by Ergonode to an App should be authenticated using this secret.

You should also encrypt the shared secret within the persistent storage so it is not easily retrievable.

The response status has to be 2xx to process installation appropriately.

Authentication of the incoming request

Steps to verify whether the request is coming for a specific App installation from Ergonode

  1. obtain the token from X-APP-TOKEN HTTP header of the request

  2. extract the app_installation_id claim from the JWT without signature verification

  3. retrieve shared secret persisted on the handshake

  4. verify JWTs signature using HMAC SHA-256 algorithm and the shared secret

Authenticate the request to Ergonode

Steps to create an appropriate token authenticating in Ergonode API

  1. establish the App installation ID

  2. create claims - at the very minimum the following claims are required: app_installation_id, nbf(not before), iat(issued at), exp(expiration time)

  3. retrieve, persisted on the handshake, shared secret

  4. create JWT with signature signed with HMAC SHA-256 algorithm and the shared secret

  5. send the token as X-APP-TOKEN HTTP header to Ergonode API URL from handshake

Alternatively, on the requests from the Ergonode, i.e. in synchronization context, you can reuse the token provided by the Ergonode.

Apps

This doc provides an overview of Ergonode Apps framework.

Apps allow you to extend the user interface of Ergonode - thanks to them you can do more in one place, eventually making handling your data and processes easier.

What's an App?

The App is a small custom web application (microservice) that resolves your business-specific problem.

It can generate a CSV file, integrate your data with an external eCommerce system, or even modify the data within Ergonode to i.e. automate some of the data management processes.

The App communicates with Ergonode through its public application programming interfaces (APIs) - you don't modify Ergonode code itself but instead provide a separate web application that, together with your Ergonode instance, removes obstacles on your path.

Since the App is a standalone application you are not limited to any specific programming language - you can develop it in PHP, Java, Python, JavaScript, and many more.

It can be written by yourself, by an agency you work with, or you can use one of the native Apps provided by the Ergonode team itself.

How can I start with the development of my custom App?

Follow this , or contact one of our trusted partners or your development agency.

Design considerations

Good practices and tips

Security

Carefully approach multitenant Apps. Since Apps can handle multiple installations, even on multiple Ergonode instances, query your persistent data with that in mind to prevent data security leaks.

Get to know the section well.

Concurrency

The App should be highly available to accept multiple requests simultaneously.

To increase the performance of the synchronization process Ergonode can send a couple of requests at one time concurrently. Not only from one instance but also during one synchronization, especially if there's a lot of data being integrated.

Use cache

If your app needs extra PIM data fetched by Ergonode APIs, especially during synchronization, cache it internally for at least the length of the process. It shall significantly speed up your processing.

{
  "shared_secret": "your_app_installation_shared_secret"
}
JWT
your App
documentation
authentication

Data flow

Overview of the data flow during synchronization process

Synchronization start

This phase is optional based on events the App subscribes to.

Status: Preparing

Data preparation

This is the internal phase.

Status: Preparing

Apps engine gathers the data required for the synchronization process.

This process can take some time. Its length depends on the amount of data to process and the cache size needed to be built.

Most usually it'll be the longest on the first run of the synchronization and significantly shorter on the following one, even if it is the same full synchronization.

Planning

This is the internal phase.

Status: Preparing

In this phase synchronization plan is created resulting in entries in the history created and put in Planned status.

Attributes synchronization

This phase is optional based on events the App subscribes to.

Status: Execution

Firstly, as Attributes are the base of the Ergonode data model, their data is synchronized.

Categories synchronization

This phase is optional based on events the App subscribes to.

Status: Execution

As a second Categories data is delivered to an App.

Products synchronization

This phase is optional based on events the App subscribes to.

Status: Execution

As products utilize all of the previously synchronized resources they are the last ones to be sent.

Products relations synchronization

This phase is optional based on events the App subscribes to.

Status: Execution

Lastly, relation data is being sent - variants, children assignment, and product relation attribute values.

This is to avoid circular reference problems where product A depends on B and B on A - all the products should exist in this phase already.

Relations payloads are always provided with product_updated events.

Synchronization end

This phase is optional based on events the App subscribes to.

Status: Finished

Event endpoints

React on events occurring in your App

There are several endpoints corresponding to events in the Manifest configuration.

Each endpoint receives an appropriate X-APP-TOKEN HTTP header with JWT that allows authentication of the caller.

/consume/app_installed

subscribed event: app_installed

example payload:

{
  "name": "app_installed"
}

/consume/app_uninstalled

subscribed event: app_uninstalled

example payload:

{
  "name": "app_uninstalled"
}

Synchronization endpoints

Next to App events, there are multiple Synchronization events for which there are respective endpoints to be implemented.

A full reference can be found here

Manifest

Full reference of Manifest file.

Manifest is a simple JSON file that describes your App behavior and its possibilities.

To refresh the App Manifest, it has to be changed and reapplied - just like on App registration. Ergonode does not refresh it automatically.

Remember also, on refreshment, to raise the version - otherwise, changes won't be applied as described in the version restrictions description.

Contained fields:

name

A simple text describing under what name your app is visible in the Ergonode available Apps tab.

This value should allow easy identification of what your App does.

Restrictions:

  • has to exist and not be empty

  • the length of the name is limited to 30 chars and should be no less than 3 chars.

description

A simple text describing the purpose fulfilled by the App.

Restrictions:

  • has to exist and not be empty

  • the length of the name is limited to 200 chars and should be no less than 20 chars.

version

A semantic version of your manifest. It does not necessarily need to address your application version but rather the content of the Manifest file.

Restrictions:

  • has to exist and not be empty

  • when reapplying the Manifest for App update the changes may only be applied when the new version is higher than the existing ones - otherwise, changes will not take effect.

compatible

A semantic version that the current version of the Manifest is compatible with.

This allows Apps Engine to determine whether an App administrator should take action and reconfigure the App for the new needs - fill out previously not existing configuration fields etc.

So for instance if, on the App update, the compatible field is higher than the last version(i.e. compatible = 1.1.0 and last version = 1.0.0) of the App installations will be set in Configuration required status - implicating the need for action and disallowing following actions with an App.

Restrictions:

  • has to exist and not be empty

  • the value cannot be greater than the version field

configuration_schema

A list of JSON schemas to allow building simple or multi-step configuration forms.

Detailed reference can be found here.

features

A list of predefined features App utilizes:

synchronization

Enables the ability to run synchronization via the App.

The synchronization is always run as delta via this feature - meaning only changes from the last synchronization will be sent to the App.

  • on first synchronization, all data state is being transferred to an App

  • on every following synchronization, the App receives only data that has changed from the previous execution

synchronization_full

Enables the ability to run full synchronization via the App.

All configured within App data is synchronized via this feature disregarding delta - which means the entire data state is transferred to the App on every synchronization.

This feature is always a secondary option in the Run synchronization split button if enabled together with synchronization.

Run synchronization split button

synchronization_file_download

Claims that the App generates a file as the result of the synchronization - for the enabled feature every finished synchronization will receive a file download button within the right-hand menu as well as a Download last synchronization button in the main App menu

Download file buttons

When this feature is enabled follow the File download endpoint reference.

synchronization_file_download_latest

This feature enables Copy feed URL button. This URL is publicly available, with no need for authorization.

Copy feed URL button

When this feature is enabled follow the File download endpoint reference as for synchronization_file_download feature.

synchronization_scheduler

This feature enabled Scheduler tab

Scheduler tab

events

Events the App subscribes to. Available:

app_installed

App installed by the user in the Ergonode interface.

app_uninstalled

App uninstalled by the user in the Ergonode interface.

attribute_created

Attribute created.

Currently available only in the synchronization context.

In the synchronization context, the event also contains detailed updated payloads.

attribute_updated

Attribute updated.

Currently available only in the synchronization context.

attribute_deleted

Attribute deleted.

Currently available only in the synchronization context.

category_created

Category created.

Currently available only in the synchronization context.

In the synchronization context, the event also contains detailed updated payloads.

category_updated

Category updated.

Currently available only in the synchronization context.

category_deleted

Category deleted.

Currently available only in the synchronization context.

product_created

Product created.

Currently available only in the synchronization context.

In the synchronization context, the event also contains detailed updated payloads. It also represents the access granted event i.e. when the product is added to a segment.

product_updated

Product updated.

Currently available only in the synchronization context.

product_deleted

Product deleted.

Currently only available in the synchronization context.

In the synchronization context, it also represents the access revoked event i.e. when the product is removed from a segment.

synchronization_started

The new synchronization process started.

synchronization_ended

The synchronization process ended.

write_access

Claims that the App requires the write access to fulfill its purpose.

A user is notified of the request on App installation.

When installed the App can use Ergonode API write capabilities to manipulate the data.

Restrictions:

  • currently the field cannot be changed after the initial App registration

icon

Icon allows easy identification of the App within available and installed Apps.

Restrictions:

  • value has to be represented as a Base64 image i.e. data:image/jpeg;base64,*

  • value cannot be greater than 10 KB

url

Base URL under which the App is available. When not provided a host of the Manifest Url is taken as this parameter.

Restrictions:

  • a valid URL string

  • currently, the value of the field cannot be changed after the initial App registration

Example Manifest

{
    "name": "My custom App",
    "description": "My custom App description",
    "version": "0.1.0",
    "compatible": "0.0.0",
    "configuration_schema": [
        {
            "title": "Connection",
            "type": "object",
            "properties": {
                "apiKey": {
                    "type": "string",
                    "title": "API key",
                    "propertyOrder": 1
                }
            },
            "required": [
                "apiKey"
            ]
        },
        {
            "title": "Settings",
            "type": "object",
            "properties": {
                "setting": {
                    "type": "string",
                    "title": "Specific app setting",
                    "propertyOrder": 1
                }
            },
            "required": [
                "setting"
            ]
        }
    ],
    "features": [
        "synchronization",
        "synchronization_full",
        "synchronization_file_download",
        "synchronization_file_download_latest"
    ],
    "events": [
        "product_created",
        "product_updated",
        "product_deleted",
        "synchronization_ended",
        "app_uninstalled"
    ],
    "icon": "data:image/webp;base64,UklGRsAFAABXRUJQVlA4ILQFAACwLQCdASoIAQgBPpFIoEulpKMhpFb4OLASCWVu4XSZz39d84G2/0j8LdH4ePpu0QehvzAP0K/3n9P6yHmF/mHTO/2XqHf9XqQPQg6WT9h8I9/zM4Xvm7T3+A3tolr1x1F/0Vg126lTUDoHQOgdA6B0DoHQOgdA6B0DoHQOgdA6B0DoHQOgdA6B0DoHQOgdA5YhX87OLqYej4fP5yIn49Vg7edLvNQxEgQalakkLTjaZWmeCj7XffMI5IYlK0H7LlDHGTFhS6vTeEXq852czXpeRmXsxCi0hiYJG9C0UX9wFkJ6wZ03+QSxDeEXuSwN4yHW16ggnSEcEzOCjkjao/OEhI842ptUF9WjpAQeuoViARullbWalo2TJGbhF7ozRavokygEYnRBuOaQQEt+I8zEZWgn7Saf6ji042l91UXREER4l7n0b5uWuySXuk6B0DrCpKm1NqbU2ptTam1NqbU2ptTam1NqbU2ptTam1NqbU2ptTalgAAD+9lD2Zb//9dx/53H/ncZmgmWCEYsIAAAABy+r8RtwslSEv0snvKmjf+wInhYkZ69wA2wOi6vvlXllaLZ/sfosQCfzDV3EhuLH28SIxv98IkoczaEb3+Pl/8lD7E5cjhfvtAqKX91uhXb/8+/jjTWZYnNDQcQpn7Qffa8BGDhmqHf/M7cbE1vw/J5Kvr6Ds9cY1vA/6oJs/W/ARFbYq46Wx7Cmni4CYRb9V7U2kNsAddu0pNKwqOmkJpIX91ey5qNB4dRRyvDmAMF8ftrTHLXBJXZvtFHIIQOWL8iyHGTBt7DyAeLbHpUatyM+BJPKf8P1fA/HxZ9ats9DiutJzmji9J0V68O26e8IJEfNt7aZe2yLrDPDpGUk/2hf+HncO7gxhdoMOUgx2pMGdkYG4lVEZM0RL3QQcS4F1rN5z+6DmeJ5QhrmEbZuomukKdaYrwLcEQOV4RJ2exJt2rh6tC7Vn1ghzf8Ju3d+zm0AU79dzF3aVBmQMgP7MJ+Jxyb3ZCnuWtoG+o1hIs3/UTjIAYfTcrU2YC0/tamgX46tYj/3wTjU73Foj+ihuOQfk0Jo1GjTBMU8mnJ4A2JMmrIPKskquNRO09Kyo64HIvPDuggb8efQoVvLEyvn1wKzChX5nmvAENOVMBlHEsM+1b4h+8+v+W9Drw9X8o1wL04LfPxnBlY/8hYE3j1tlvjbo0J3YuV4Ih8vfSb63IcJmPYhRdHXavcpv+WMVV//86gH9wjqzpqdZG+Twfz722biI4aGAxGfeH/O/CILnplEgyXU0eySpNflC2JJso1d5Eyiue8eMje4XM65KS/8Kz16F2wYJgGfjlC+YgYXnlaX8x29FYeIiWivNG3zvahM5k3LTiE90cgsZnPH0rCLRe8OuHIFGfeWCv7exWhddIf3YekuFg4n+HY999JzYmmgFBwooH/lM16Z/MdSU7JG9ahFC4ywu0MxIfhQyOSvt05rW6ts7L6QmJ+gFjKsod3FudEjADbQHjhVN/Gb1KtyknKXXrTr6hmgEuFb3TD0tyv4MXxJuoTaeiWcygV7WMVtJWfb80UcJ4f+NQe7OJp5P7H7w5e4jMSIMbQFGEkyOzi0xw+FFdOso6ZQaqqBdqeCB9Z9TdrQDwdfihNr4H+BGO4xrdLk3jAGRACNjN+yBZx5TYeRjWTDFZXpnnUlECHlvCzRtWAuZf19qOKzDsy4ypJ/N28ilq4e9TEykVocV5tnf5pGcHSza3EAWyBrWTEPj7k3JetvhbuASW2M2BRBZk+GtI3RsTSYQnWV77fANxGp1vIufGmRj/QxF9w1umS/BUIpkt9TAArXG6USpH7V9AFA377J8BS6p8wMUmcsHCymFOuM1l0BnEnGP+o13bk9uwHkx2k4g6AMXR6ZnnzdQhlB0lsHhdGROJnO3dxQgKhcojXvVPlrLX8whSU6gAAAAAAAAA=="
}

Synchronization

Let Ergonode orchestrate the synchronization process and worry only about your business logic

At the heart of every PIM-including ecosystem, there's data integration.

A need to keep your data well-organized thanks to PIM and easily accessible to eCommerce system(s).

We do realize that creating the synchronization process is not an easy task. At the very minimum, it most usually requires:

  • a queue system like RabbitMQ, Amazon SQS, etc

  • a worker

  • well-developed and robust logic to handle the process

    • progress state

    • both process and entry statuses

    • history

    • frontend application displaying all that data

  • persistent storage i.e. database

  • business logic implementation

which is a lot and usually not that easy to do, especially, in the distributed environment of multiple services.

That's why at the core of the Apps engine we created an entire synchronization process orchestration with the intent of you only being left with implementing your desired business logic and the minimum effort needed around it.

Need only an incremental export of data you have recently changed? We've got you covered.

Does it have to handle full export each time? It's possible with an App.

Creating synchronization App

To start with a synchronization App enable at least one of those features in your Manifest.

  • synchronization - enables the basic integration. During every synchronization, you'll only receive data changed from the last iteration

  • synchronization_full - enables the possibility of running full integration on every execution

Both features may be enabled next to each other or you can pick just one.

If your App generates a file, enable features according to your needs

  • synchronization_file_download - enables file download for each synchronization

  • synchronization_file_download_latest - generates constant URL under last-generated file will be available for feed-like functionalities

Finally, you need to develop your business logic in and if your App utilizes this feature.

synchronization endpoints
file download endpoint
With both features enabled they are available via split button with full synchronization as a secondary choice.

Configuration schema

Overview of how to build App configuration

Introduction

App most usually requires some sort of configuration specific to its installation.

Below you can find the Ergonode CSV App configuration.

Ergonode CSV App configuration

Such configuration can be achieved by setting up the configuration_schema section in the Manifest file.

The value is a list of JSON Schemas allowing you to build a form.

Not full JSON Schema is yet covered in Ergonode.

If you have a case of a feature that suits your needs and is a part of the JSON Schema standard we may consider extending our implementation.

Please contact the respective Ergonode representative with the details.

The reason it's a list is fairly simple - in some scenarios, at first you need to input i.e. API Token, then choose which attributes from the App you'd want to configure - a piece of information that is only accessible once authorized within the system App is handling.

So the following steps in the form(following JSON Schemas) will be built once you correctly fill the current step.

Example schema

{
  // (...) rest of the Manifest file
  "configuration_schema": [
    {
      "title": "Configuration",
      "type": "object",
      "properties": {
        "simpleText": {
          "type": "string",
          "title": "Just a text",
          "propertyOrder": 1
        },
        "secret": {
          "type": "string",
          "title": "Secret password",
          "widget": "password",
          "propertyOrder": 2
        },
        "multilineText": {
          "type": "string",
          "title": "Multiline text",
          "widget": "textarea",
          "propertyOrder": 2
        },
        "simpleEnum": {
          "type": "string",
          "title": "Simple Enum",
          "enum": [
            "1",
            "2"
          ],
          "options": {
            "enum_titles": [
              "one",
              "two"
            ]
          },
          "propertyOrder": 4
        },
        "enumMultiple": {
          "items": {
            "type": "string",
            "enum": [
              "one",
              "two"
            ],
            "options": {
              "enum_titles": [
                "Choice one",
                "Choice two"
              ]
            }
          },
          "minItems": 1,
          "uniqueItems": true,
          "type": "array",
          "title": "Multiple choice enum",
          "propertyOrder": 6
        },
        "dictionaryErgonodeEnum": {
          "type": "string",
          "title": "Dictionary Ergonode Enum",
          "options": {
            "enum_dictionary": "ergonode:languages"
          },
          "propertyOrder": 7
        },
        "dictionaryAppEnum": {
          "type": "string",
          "title": "Dictionary App Enum",
          "items": {
            "type": "string",
            "options": {
              "enum_dictionary": "app:app-dictionary"
            },
            "minItems": 1,
            "uniqueItems": true
          },
          "propertyOrder": 8
        },
        "attributeMapping": {
          "type": "array",
          "title": "Attribute mapping",
          "items": {
            "title": "prototype",
            "type": "object",
            "properties": {
              "ergonode": {
                "type": "string",
                "title": "ergonode",
                "propertyOrder": 1
              },
              "app": {
                "type": "string",
                "title": "app",
                "propertyOrder": 2
              }
            },
            "required": [
              "ergonode",
              "app"
            ]
          },
          "widget": {
            "type": "mapper",
            "options": {
              "ergonodeDictionary": "ergonode:attributes",
              "appDictionary": "app:attributes"
            }
          },
          "propertyOrder": 9
        }
      },
      "required": [
        "simpleText",
        "secret",
        "multilineText",
        "simpleEnum",
        "enumMultiple",
        "dictionaryErgonodeEnum",
        "dictionaryAppEnum",
        "attributeMapping"
      ]
    }
  ]
}

Ok, so a lot is going on in this example. Let's break it down field by field.

simpleText

This is the simplest case.

It's a plain text that your user can input. It'll be displayed as a regular HTML text input.

secret

The secret is very similar to the previous simple text example but as with any other sensitive data you do not always want it visible constantly on your configuration. That's why a password widget is added - it results in a hidden secret text.

multilineText

Sometimes one-line text won't suffice. You can extend the text presentation thanks to the textarea widget.

simpleEnum

Another frequent case is a limited of values from which a user can choose. That's where the enum comes into play. There are 2 additional special fields added here

  • enum - represents the actual valid value list to choose from

  • options.enum_titles - represents the presentational titles list to be displayed to the user in dropdown

enumMultiple

An extended example of simple enums is when you have multiple choice allowed. Instead of using type string adjust it to array with string type elements.

dictionaryErgonodeEnum

Here's where things get a bit more kinky. You, again, need a predefined set of values but those values are already there in Ergonode i.e. a list of languages to choose from or an attribute.

A field options.enum_dictionary is added - in short, you tell the interpreter to render a list of languages from Ergonode via a dictionary ergonode:languages - this is resolved by Ergonode.

Ergonode dictionaries reference can be found here.

dictionaryAppEnum

Another possibility is that the predefined list is not part of Ergonode data but the App or system it integrates with. The App may define multiple dictionaries with different data.

App dictionaries reference can be found here.

Note this time options.enum_dictionary is wrapped into items - this changes the select to multi-select in the dropdown list.

attributeMapping

Occasionally, especially on synchronization Apps, you'll need attributes mapping from Ergonode to an external system. This is possible by combining two things.

A structure definition that can carry on the mapping

[
  {
    "ergonode": "attribute_one",
    "app": "attribute_two"
  }
]

and mapper widget capable of making it easier to map the data in the configuration user interface.

Widgets

Widgets are dedicated UI components that allow a custom presentation of a specific field.

password

Changes displaying of the plain text in HTML text input into password input.

password widget

textarea

Presents text input in the form of textarea input.

textarea widget

mapper

Allows for drag-and-drop mapping of the attributes and handles validation of their types.

The widget accepts 2 options:

  • ergonodeDictionary - saying where from claim data - this option is mandatory

  • appDictionary - this field is optional - if not present the Ergonode mapping will be possible to textual values

Attribute mapping widget overview
Attributes mapping widget details

Configuration

Configure your App in Ergonode

If the App requires configuration and defines configuration form in configuration_schema it has to appropriately handle the values.

It has to be able to validate and persist the configuration and also provide the configuration for reading.

To do so 2 endpoints implementation is necessary.

Each endpoint receives an appropriate X-APP-TOKEN HTTP header with JWT that allows authentication of the caller.

[GET] /configuration

The endpoint should return all so-far persisted schemas in update configuration steps in the form of an array of data - according to your schema.

For example for a simple configuration schema

{
  "configuration_schema": [
    {
      "title": "Connection",
      "type": "object",
      "properties": {
        "token": {
          "type": "string",
          "title": "API token",
          "widget": "password",
          "propertyOrder": 1
        }
      },
      "required": [
        "token"
      ]
    },
    {
      "title": "Settings",
      "type": "object",
      "properties": {
        "setting1": {
          "type": "string",
          "title": "setting 1",
          "propertyOrder": 1
        },
        "setting2": {
          "type": "string",
          "title": "setting 2",
          "propertyOrder": 1
        }
      },
      "required": [
        "text"
      ]
    }
  ]
}

the expected response would be

[
  {
    "token": "secret API token"
  },
  {
    "setting1": "user input text 1",
    "setting2": "user input text 2"
  }
]

Every object represents single form step/one configuration schema.

Preserving the configured objects under the same indexes as their definitions in the configuration schema is mandatory.

[POST] /configuration

The endpoint accepts, validates, and persists in the configuration step.

payload

{
  "index": 0,
  "configuration": {
    "text": "user input text"
  }
}
{
  "type": "object",
  "properties": {
    "index": {
      "type": "integer",
      "description": "The index of the configuration step from configuration schema."
    },
    "configuration": {
      "type": "object",
      "description": "The form data according to configuration schema."
    }
  },
  "required": [
    "index",
    "configuration"
  ]
}

All 2xx responses will be treated as success.

Error handling

The forms are validated against configuration schema but not all validation can be performed based on JSON schema.

I.e. API token can only be verified in the App.

Though the frontend application performs configuration validation on every step it is a good practice to perform full validation in the App as well to limit any potential errors.

To assign a custom error message to specific user form fields it is required to return an error response with code 422 and the payload

{
  "title": "Form validation errors.",
  "detail": "Token for API is not valid",
  "violations": [
    {
      "propertyPath": "token",
      "title": "Token for API is not valid",
      "template": "Token for %parameter% is not valid",
      "parameters": {
        "%parameter%": "API"
      }
    }
  ]
}
{
  "type": "object",
  "properties": {
    "title": {
      "type": "string",
      "description": "The title of an error."
    },
    "detail": {
      "type": "string",
      "description": "Detailed errors description."
    },
    "violations": {
      "type": "array",
      "description": "The list of violations.",
      "items": {
        "type": "object",
        "properties": {
          "propertyPath": {
            "type": "string",
            "description": "Path to the field the violation applies"
          },
          "title": {
            "type": "string",
            "description": "Ready to use message"
          },
          "template": {
            "type": "string",
            "description": "Template of the message"
          },
          "parameters": {
            "type": "object",
            "description": "Parameters of the template"
          }
        }
      }
    }
  },
  "required": [
    "index",
    "configuration"
  ]
}

All other error codes will be treated with a generic error message.

Synchronization endpoints

A place where the synchronization business logic is implemented

Synchronization endpoints have a very similar role to message handlers in a message queue system.

They receive the event(message) with data and react to its payload.

There are several endpoints corresponding to events in the Manifest configuration.

Each endpoint receives an appropriate X-APP-TOKEN HTTP header with JWT that allows authentication of the caller.

JWT for all synchronization endpoint requests is extended with an extra context claim synchronization_id.

This claim is a UUID and allows identification of the synchronization process.

Every endpoint should respond with a 2xx HTTP code to be treated as a success.

Synchronization endpoints

All the endpoints follow the semantic [PUT] /consume/{event}.

synchronization.events contains an array of detailed payloads containing actual changes - the full list can be found here.

Those events allow retrieving information about what has changed and reacting to that change.

This field for DELETED events is always an empty array.

/consume/attribute_created

subscribed event: attribute_created

example payload:

{
  "name": "attribute_created",
  "resource_id": {
    "id": "Attribute_code",
    "type": "attribute_code"
  },
  "synchronization": {
    "resource_customs": null,
    "events": []
  }
}

/consume/attribute_updated

subscribed event: attribute_updated

/consume/attribute_deleted

subscribed event: attribute_deleted

example payload:

{
  "name": "attribute_deleted",
  "resource_id": {
    "id": "Attribute_code",
    "type": "attribute_code"
  },
  "synchronization": {
    "resource_customs": {"your": "custom"},
    "events": []
  }
}

/consume/category_created

subscribed event: category_created

example payload:

{
  "name": "category_created",
  "resource_id": {
    "id": "Category_code",
    "type": "category_code"
  },
  "synchronization": {
    "resource_customs": null,
    "events": []
  }
}

/consume/category_updated

subscribed event: category_updated

example payload:

{
  "name": "category_updated",
  "resource_id": {
    "id": "Category_code",
    "type": "category_code"
  },
  "synchronization": {
    "resource_customs": {"your": "custom"},
    "events": []
  }
}

/consume/category_deleted

subscribed event: category_deleted

example payload:

{
  "name": "category_deleted",
  "resource_id": {
    "id": "Category_code",
    "type": "category_code"
  },
  "synchronization": {
    "resource_customs": {"your": "custom"},
    "events": []
  }
}

/consume/product_created

subscribed event: product_created

example payload:

{
  "name": "product_created",
  "resource_id": {
    "id": "Product SKU",
    "type": "sku"
  },
  "synchronization": {
    "resource_customs": null,
    "events": []
  }
}

/consume/product_updated

subscribed event: product_updated

example payload:

{
  "name": "product_updated",
  "resource_id": {
    "id": "Product SKU",
    "type": "sku"
  },
  "synchronization": {
    "resource_customs": {"your": "custom"},
    "events": []
  }
}

/consume/product_deleted

subscribed event: product_deleted

example payload:

{
  "name": "product_updated",
  "resource_id": {
    "id": "Product SKU",
    "type": "sku"
  },
  "synchronization": {
    "resource_customs": {"your": "custom"},
    "events": []
  }
}

/consume/synchronization_started

subscribed event: synchronization_started

example payload:

{
    "name": "synchronization_started"
}

/consume/synchronization_ended

subscribed event: synchronization_ended

example payload:

{
    "name": "synchronization_started"
}

Resource customs

In the above examples, you might have noticed the resource_customs field.

This mechanic allows small pieces of information such as JSON to be stored in the engine.

The most common use case for this would be storing the ID of the eCommerce entity next to the resource. It'll be passed with every following request so it's easier for your business logic to create the requests to the eCommerce system without the need of reaching to the database.

To persist the custom in the response of the endpoint add the following payload

{
  "resource_customs": {
    "id": 1,
    "other_relevent_information": "here"
  }
}

The resource custom will only be persisted on 2xx(non-204) HTTP responses.

Errors handling

Handling of the request payload may fail.

In such case, there are a couple of scenarios:

  • failed connection(including 502 and 503 HTTP errors) - the event will be retried a couple of times before aborting completely

  • 5xx and 3xx HTTP responses

    • for a single resource, the event is not retried as of an internal error and the history entry is marked with a generic message

    • for a synchronization error event is going to be retried eventually leading to the failed synchronization status

  • 4xx HTTP responses

    • for a single resource

      • a custom behavior may be applied via an appropriate response

        {
          "custom_message": "your custom error message",
          "retryable": false
        }

        retryable parameter determines whether the event can be retried. Otherwise, the fail and custom error message will be applied immediately. The message should not exceed the length of 256 chars. If the length is exceeded the message will be truncated.

      • the event is not retried and the history entry is marked with a generic message

    • for a synchronization error event is going to be retried eventually leading to the failed synchronization status

Synchronization events

Detailed events containing data changes.

Attributes

attribute_created

included with events: attribute_created

{
  "type": "attribute_created",
  "payload": {
    "code": "attribute_code",
    "type": "TEXT",
    "scope": "local",
    "name": [
      {
        "language": "en_GB",
        "value": "This is the attribute name"
      }
    ]
  }
}
{
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "description": "The type of synchronization event."
    },
    "payload": {
      "type": "object",
      "description": "The detailed payload of the synchronization event.",
      "properties": {
        "code": {
          "type": "string"
        },
        "type": {
          "type": "string",
          "enum": ["DATE", "FILE", "GALLERY", "IMAGE", "MULTI_SELECT", "NUMERIC", "PRICE", "PRODUCT_RELATION", "SELECT", "TEXT_AREA", "TEXT", "UNIT"]
        },
        "scope": {
          "type": "string",
          "enum": ["local", "global"]
        },
        "name": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "language": {
                "type": "string"
              },
              "value": {
                "type": "string"
              }
            },
            "required": [
              "language",
              "value"
            ]
          }
        }
      },
      "required": [
        "code",
        "type",
        "scope",
        "name"
      ]
    }
  },
  "required": [
    "type",
    "payload"
  ]
}

attribute_name_changed

included with events: attribute_created, attribute_updated

{
  "type": "attribute_name_changed",
  "payload": {
    "name": [
      {
        "language": "en_GB",
        "value": "This is the new attribute name"
      }
    ]
  }
}
{
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "description": "The type of synchronization event."
    },
    "payload": {
      "type": "object",
      "description": "The detailed payload of the synchronization event.",
      "properties": {
        "name": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "language": {
                "type": "string"
              },
              "value": {
                "type": "string"
              }
            },
            "required": [
              "language",
              "value"
            ]
          }
        }
      },
      "required": [
        "name"
      ]
    }
  },
  "required": [
    "type",
    "payload"
  ]
}

attribute_option_code_changed

included with events: attribute_created, attribute_updated

{
  "type": "attribute_option_code_changed",
  "payload": {
    "option_code": "option_code",
    "previous_option_code": "previous_option_code"
  }
}
{
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "description": "The type of synchronization event."
    },
    "payload": {
      "type": "object",
      "description": "The detailed payload of the synchronization event.",
      "properties": {
        "option_code": {
          "type": "string"
        },
        "previous_option_code": {
          "type": "string"
        }
      },
      "required": [
        "option_code",
        "previous_option_code"
      ]
    }
  },
  "required": [
    "type",
    "payload"
  ]
}

attribute_option_created

included with events: attribute_created, attribute_updated

{
  "type": "attribute_option_created",
  "payload": {
    "option_code": "option_code",
    "option_name": [
      {
        "language": "en_GB",
        "value": "This is the option name"
      }
    ]
  }
}
{
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "description": "The type of synchronization event."
    },
    "payload": {
      "type": "object",
      "description": "The detailed payload of the synchronization event.",
      "properties": {
        "option_code": {
          "type": "string"
        },
        "option_name": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "language": {
                "type": "string"
              },
              "value": {
                "type": "string"
              }
            },
            "required": [
              "language",
              "value"
            ]
          }
        }
      },
      "required": [
        "option_code",
        "option_name"
      ]
    }
  },
  "required": [
    "type",
    "payload"
  ]
}

attribute_option_deleted

included with events: attribute_updated

{
  "type": "attribute_option_deleted",
  "payload": {
    "option_code": "option_code"
  }
}
{
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "description": "The type of synchronization event."
    },
    "payload": {
      "type": "object",
      "description": "The detailed payload of the synchronization event.",
      "properties": {
        "option_code": {
          "type": "string"
        }
      },
      "required": [
        "option_code"
      ]
    }
  },
  "required": [
    "type",
    "payload"
  ]
}

attribute_option_name_changed

included with events: attribute_created, attribute_updated

{
  "type": "attribute_option_name_changed",
  "payload": {
    "option_code": "option_code",
    "option_name": [
      {
        "language": "en_GB",
        "value": "This is the new option name"
      }
    ]
  }
}
{
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "description": "The type of synchronization event."
    },
    "payload": {
      "type": "object",
      "description": "The detailed payload of the synchronization event.",
      "properties": {
        "option_code": {
          "type": "string"
        },
        "option_name": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "language": {
                "type": "string"
              },
              "value": {
                "type": "string"
              }
            },
            "required": [
              "language",
              "value"
            ]
          }
        }
      },
      "required": [
        "option_code",
        "option_name"
      ]
    }
  },
  "required": [
    "type",
    "payload"
  ]
}

Categories

category_created

included with events: category_created

{
  "type": "category_created",
  "payload": {
    "code": "category_code",
    "name": [
      {
        "language": "en_GB",
        "value": "This is the category name"
      }
    ]
  }
}
{
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "description": "The type of synchronization event."
    },
    "payload": {
      "type": "object",
      "description": "The detailed payload of the synchronization event.",
      "properties": {
        "code": {
          "type": "string"
        },
        "name": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "language": {
                "type": "string"
              },
              "value": {
                "type": "string"
              }
            },
            "required": [
              "language",
              "value"
            ]
          }
        }
      },
      "required": [
        "code",
        "name"
      ]
    }
  },
  "required": [
    "type",
    "payload"
  ]
}

category_name_changed

included with events: category_created, category_updated

{
  "type": "attribute_name_changed",
  "payload": {
    "name": [
      {
        "language": "en_GB",
        "value": "This is the new category name"
      }
    ]
  }
}
{
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "description": "The type of synchronization event."
    },
    "payload": {
      "type": "object",
      "description": "The detailed payload of the synchronization event.",
      "properties": {
        "code": {
          "type": "string"
        },
        "name": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "language": {
                "type": "string"
              },
              "value": {
                "type": "string"
              }
            },
            "required": [
              "language",
              "value"
            ]
          }
        }
      },
      "required": [
        "code",
        "name"
      ]
    }
  },
  "required": [
    "type",
    "payload"
  ]
}

Products

product_added_to_category

included with events: product_created, product_updated

{
  "type": "product_added_to_category",
  "payload": {
    "category_code": "category_code"
  }
}
{
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "description": "The type of synchronization event."
    },
    "payload": {
      "type": "object",
      "description": "The detailed payload of the synchronization event.",
      "properties": {
        "category_code": {
          "type": "string"
        }
      },
      "required": [
        "category_code"
      ]
    }
  },
  "required": [
    "type",
    "payload"
  ]
}

product_binding_added

included with events: product_created, product_updated

{
  "type": "product_binding_added",
  "payload": {
    "attribute_code": "attribute_code"
  }
}
{
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "description": "The type of synchronization event."
    },
    "payload": {
      "type": "object",
      "description": "The detailed payload of the synchronization event.",
      "properties": {
        "attribute_code": {
          "type": "string"
        }
      },
      "required": [
        "attribute_code"
      ]
    }
  },
  "required": [
    "type",
    "payload"
  ]
}

product_binding_removed

included with events: product_updated

{
  "type": "product_binding_removed",
  "payload": {
    "attribute_code": "attribute_code"
  }
}
{
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "description": "The type of synchronization event."
    },
    "payload": {
      "type": "object",
      "description": "The detailed payload of the synchronization event.",
      "properties": {
        "attribute_code": {
          "type": "string"
        }
      },
      "required": [
        "attribute_code"
      ]
    }
  },
  "required": [
    "type",
    "payload"
  ]
}

product_child_added

included with events: product_created, product_updated

{
  "type": "product_child_added",
  "payload": {
    "child_sku": "Child sku",
    "child_quantity": 2
  }
}
{
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "description": "The type of synchronization event."
    },
    "payload": {
      "type": "object",
      "description": "The detailed payload of the synchronization event.",
      "properties": {
        "child_sku": {
          "type": "string"
        },
        "child_quantity": {
          "type": "integer"
        }
      },
      "required": [
        "child_sku",
        "child_quantity"
      ]
    }
  },
  "required": [
    "type",
    "payload"
  ]
}

product_child_quantity_changed

included with events: product_created, product_updated

{
  "type": "product_child_quantity_changed",
  "payload": {
    "child_sku": "Child sku",
    "child_quantity": 2
  }
}
{
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "description": "The type of synchronization event."
    },
    "payload": {
      "type": "object",
      "description": "The detailed payload of the synchronization event.",
      "properties": {
        "child_sku": {
          "type": "string"
        },
        "child_quantity": {
          "type": "integer"
        }
      },
      "required": [
        "child_sku",
        "child_quantity"
      ]
    }
  },
  "required": [
    "type",
    "payload"
  ]
}

product_child_removed

included with events: product_updated

{
  "type": "product_child_removed",
  "payload": {
    "child_sku": "Child sku"
  }
}
{
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "description": "The type of synchronization event."
    },
    "payload": {
      "type": "object",
      "description": "The detailed payload of the synchronization event.",
      "properties": {
        "child_sku": {
          "type": "string"
        }
      },
      "required": [
        "child_sku"
      ]
    }
  },
  "required": [
    "type",
    "payload"
  ]
}

product_created

included with events: product_created

{
  "type": "product_created",
  "payload": {
    "sku": "Product SKU",
    "type": "SIMPLE-PRODUCT",
    "category_codes": ["category_code"]
  }
}
{
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "description": "The type of synchronization event."
    },
    "payload": {
      "type": "object",
      "description": "The detailed payload of the synchronization event.",
      "properties": {
        "sku": {
          "type": "string"
        },
        "type": {
          "type": "string",
          "enum": ["SIMPLE-PRODUCT", "GROUPING-PRODUCT", "VARIABLE-PRODUCT"]
        },
        "category_codes": {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      },
      "required": [
        "sku",
        "type",
        "category_codes"
      ]
    }
  },
  "required": [
    "type",
    "payload"
  ]
}

product_removed_from_category

included with events: product_updated

{
  "type": "product_removed_from_category",
  "payload": {
    "category_code": "category_code"
  }
}
{
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "description": "The type of synchronization event."
    },
    "payload": {
      "type": "object",
      "description": "The detailed payload of the synchronization event.",
      "properties": {
        "category_code": {
          "type": "string"
        }
      },
      "required": [
        "category_code"
      ]
    }
  },
  "required": [
    "type",
    "payload"
  ]
}

product_value_added

included with events: product_created, product_updated

{
  "type": "product_value_added",
  "payload": {
    "attribute_code": "attribute_code",
    "attribute_type": "TEXT",
    "attribute_scope": "local",
    "value": [
      {
        "language": "en_GB",
        "translation": "This is the attribute value"
      }
    ]
  }
}
{
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "description": "The type of synchronization event."
    },
    "payload": {
      "type": "object",
      "description": "The detailed payload of the synchronization event.",
      "properties": {
        "attribute_code": {
          "type": "string"
        },
        "attribute_type": {
          "type": "string",
          "enum": ["DATE", "FILE", "GALLERY", "IMAGE", "MULTI_SELECT", "NUMERIC", "PRICE", "PRODUCT_RELATION", "SELECT", "TEXT_AREA", "TEXT", "UNIT"]
        },
        "attribute_scope": {
          "type": "string",
          "enum": ["local", "global"]
        },
        "value": {
          "oneOf": [
            {
              "type": "string",
              "description": "For global attribute of type DATE, SELECT, TEXT_AREA and TEXT"
            },
            {
              "type": "number",
              "description": "For global attribute of type NUMERIC, PRICE and UNIT"
            },
            {
              "type": "array",
              "items": {
                "type": "string"
              },
              "description": "For global attribute of type MULTI_SELECT and PRODUCT_RELATION"
            },
            {
              "type": "object",
              "properties": {
                "url": {
                  "type": "string"
                },
                "path": {
                  "type": "string"
                }
              },
              "required": [
                "url",
                "path"
              ],
              "description": "For global attribute of type IMAGE"
            },
            {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string"
                  },
                  "path": {
                    "type": "string"
                  }
                },
                "required": [
                  "url",
                  "path"
                ]
              },
              "description": "For global attribute of type FILE and GALLERY"
            },
            {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "language": {
                    "type": "string"
                  },
                  "value": {
                    "oneOf": [
                      {
                        "$ref": "#/components/schemas/AttributeValue"
                      }
                    ]
                  }
                },
                "required": [
                  "language",
                  "value"
                ]
              },
              "description": "For local attributes same types as for global wrapped in translation array"
            }
          ]
        }
      },
      "required": [
        "attribute_code",
        "attribute_type",
        "attribute_scope",
        "value"
      ]
    }
  },
  "required": [
    "type",
    "payload"
  ]
}

product_value_changed

included with events: product_created, product_updated

{
  "type": "product_value_changed",
  "payload": {
    "attribute_code": "attribute_code",
    "attribute_type": "TEXT",
    "attribute_scope": "global",
    "value": "This is the attribute value"
  }
}
{
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "description": "The type of synchronization event."
    },
    "payload": {
      "type": "object",
      "description": "The detailed payload of the synchronization event.",
      "properties": {
        "attribute_code": {
          "type": "string"
        },
        "attribute_type": {
          "type": "string",
          "enum": ["DATE", "FILE", "GALLERY", "IMAGE", "MULTI_SELECT", "NUMERIC", "PRICE", "PRODUCT_RELATION", "SELECT", "TEXT_AREA", "TEXT", "UNIT"]
        },
        "attribute_scope": {
          "type": "string",
          "enum": ["local", "global"]
        },
        "value": {
          "oneOf": [
            {
              "type": "string",
              "description": "For global attribute of type DATE, SELECT, TEXT_AREA and TEXT"
            },
            {
              "type": "number",
              "description": "For global attribute of type NUMERIC, PRICE and UNIT"
            },
            {
              "type": "array",
              "items": {
                "type": "string"
              },
              "description": "For global attribute of type MULTI_SELECT and PRODUCT_RELATION"
            },
            {
              "type": "object",
              "properties": {
                "url": {
                  "type": "string"
                },
                "path": {
                  "type": "string"
                }
              },
              "required": [
                "url",
                "path"
              ],
              "description": "For global attribute of type IMAGE"
            },
            {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string"
                  },
                  "path": {
                    "type": "string"
                  }
                },
                "required": [
                  "url",
                  "path"
                ]
              },
              "description": "For global attribute of type FILE and GALLERY"
            },
            {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "language": {
                    "type": "string"
                  },
                  "value": {
                    "oneOf": [
                      {
                        "$ref": "#/components/schemas/AttributeValue"
                      }
                    ]
                  }
                },
                "required": [
                  "language",
                  "value"
                ]
              },
              "description": "For local attributes same types as for global wrapped in translation array"
            }
          ]
        }
      },
      "required": [
        "attribute_code",
        "attribute_type",
        "attribute_scope",
        "value"
      ]
    }
  },
  "required": [
    "type",
    "payload"
  ]
}

product_value_removed

included with events: product_updated

{
  "type": "product_value_removed",
  "payload": {
    "attribute_code": "attribute_code",
    "attribute_type": "TEXT"
  }
}
{
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "description": "The type of synchronization event."
    },
    "payload": {
      "type": "object",
      "description": "The detailed payload of the synchronization event.",
      "properties": {
        "attribute_code": {
          "type": "string"
        },
        "attribute_type": {
          "type": "string",
          "enum": ["DATE", "FILE", "GALLERY", "IMAGE", "MULTI_SELECT", "NUMERIC", "PRICE", "PRODUCT_RELATION", "SELECT", "TEXT_AREA", "TEXT", "UNIT"]
        }
      },
      "required": [
        "attribute_code",
        "attribute_type"
      ]
    }
  },
  "required": [
    "type",
    "payload"
  ]
}

product_variant_added

included with events: product_updated

{
  "type": "product_variant_added",
  "payload": {
    "variant_sku": "Variant SKU",
    "binding_values": [
      {
        "attribute_code": "attribute_code",
        "attribute_type": "SELECT",
        "attribute_scope": "global",
        "value": "attribute_option_code"
      }
    ]
  }
}
{
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "description": "The type of synchronization event."
    },
    "payload": {
      "type": "object",
      "description": "The detailed payload of the synchronization event.",
      "properties": {
        "variant_sku": {
          "type": "string"
        },
        "binding_values": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "attribute_code": {
                "type": "string"
              },
              "attribute_type": {
                "type": "string",
                "enum": ["SELECT"]
              },
              "attribute_scope": {
                "type": "string",
                "enum": ["global"]
              },
              "value": {
                "type": "string"
              }
            },
            "required": [
              "attribute_code",
              "attribute_type",
              "attribute_scope",
              "value"
            ]
          }
        }
      },
      "required": [
        "variant_sku",
        "binding_values"
      ]
    }
  },
  "required": [
    "type",
    "payload"
  ]
}

product_variant_removed

included with events: product_updated

{
  "type": "product_variant_removed",
  "payload": {
    "variant_sku": "Variant SKU"
  }
}
{
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "description": "The type of synchronization event."
    },
    "payload": {
      "type": "object",
      "description": "The detailed payload of the synchronization event.",
      "properties": {
        "variant_sku": {
          "type": "string"
        }
      },
      "required": [
        "variant_sku"
      ]
    }
  },
  "required": [
    "type",
    "payload"
  ]
}