arrow-left

Only this pageAll pages
gitbookPowered 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...

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.

hashtag
Create a Manifest

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.

hashtag
Prepare authentication

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.

hashtag
Handle your configuration

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.

hashtag
Implement event endpoints

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

hashtag
Implement your business logic

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

hashtag
Implement synchronization endpoints

This step is optional and depends on your intent.

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.

hashtag
Host your application

Host your application on an HTTP server of choice.

circle-info

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

circle-info

Ergonode currently does not provide hosting for custom Apps.

hashtag
Register your App

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

circle-info

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 restrictions description.

Manifest reference
Authentication reference
Configuration reference
Event endpoints reference
Synchronization endpoints reference
ngrokarrow-up-right
version
Click on New app button
Fill your Manifest URL

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:

hashtag
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

hashtag
App

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

In Manifest 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.

circle-info

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

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

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

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.

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

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

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

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.

circle-info

documentation

ergonode:languages

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

hashtag
Handshake

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

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

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.

hashtag
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

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

JWTarrow-up-right
{
  "dictionary": [
    {
      "id": "value",
      "label": "Presentation label"
    }
  ]
}
{
  "dictionary": [
    {
      "id": "description",
      "label": "Description attribute",
      "type": "TEXT_AREA|TEXT"
    }
  ]
}
{
  "dictionary": [
    {
      "id": "description",
      "label": "Description attribute",
      "allows_merging": true
    }
  ]
}
{
  "shared_secret": "your_app_installation_shared_secret"
}

Manifest

Full reference of Manifest file.

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

circle-info

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 restrictions description.

Contained fields:

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

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

hashtag
version

A semantic versionarrow-up-right 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.

hashtag
compatible

A semantic versionarrow-up-right 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

hashtag
configuration_schema

A list of JSON schemasarrow-up-right to allow building simple or multi-step configuration forms.

Detailed reference can be found here.

hashtag
features

A list of predefined features App utilizes:

hashtag
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

hashtag
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

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

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

hashtag
synchronization_scheduler

This feature enabled Scheduler tab

Scheduler tab

hashtag
events

Events the App subscribes to. Available:

hashtag
app_installed

App installed by the user in the Ergonode interface.

hashtag
app_uninstalled

App uninstalled by the user in the Ergonode interface.

hashtag
attribute_created

Attribute created.

Currently available only in the synchronization context.

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

hashtag
attribute_updated

Attribute updated.

Currently available only in the synchronization context.

hashtag
attribute_deleted

Attribute deleted.

Currently available only in the synchronization context.

hashtag
category_created

Category created.

Currently available only in the synchronization context.

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

hashtag
category_updated

Category updated.

Currently available only in the synchronization context.

hashtag
category_deleted

Category deleted.

Currently available only in the synchronization context.

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

hashtag
product_updated

Product updated.

Currently available only in the synchronization context.

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

hashtag
synchronization_started

The new synchronization process started.

hashtag
synchronization_ended

The synchronization process ended.

hashtag
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

hashtag
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

hashtag
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

hashtag
Example Manifest

version
{
    "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=="
}

Changelog

The changelog is a list of recent changes to Apps.

hashtag
2025-04-10

  • synchronization_scheduler feature added

hashtag
2024-05-14

  • Apps release 🎉

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.

Design considerations

Good practices and tips

hashtag
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 authentication section well.

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

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

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

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

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

Event endpoints

React on events occurring in your App

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

circle-info

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

hashtag
/consume/app_installed

subscribed event:

example payload:

hashtag
/consume/app_uninstalled

subscribed event:

example payload:

hashtag
Synchronization endpoints

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

Data flow

Overview of the data flow during synchronization process

hashtag
Synchronization start

This phase is optional based on the App subscribes to.

Status: Preparing

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

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

hashtag
/download-file
circle-info

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.

synchronization_file_download
synchronization_file_download_latest
app_installed
app_uninstalled
A full reference can be found here
{
  "name": "app_installed"
}
{
  "name": "app_uninstalled"
}
history
  • frontend application displaying all that data

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

    This is the internal phase.

    Status: Preparing

    Apps engine gathers the data required for the synchronization process.

    circle-info

    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.

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

    hashtag
    Attributes synchronization

    This phase is optional based on the App subscribes to.

    Status: Execution

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

    hashtag
    Categories synchronization

    This phase is optional based on the App subscribes to.

    Status: Execution

    As a second Categories data is delivered to an App.

    hashtag
    Products synchronization

    This phase is optional based on the App subscribes to.

    Status: Execution

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

    hashtag
    Products relations synchronization

    This phase is optional based on 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.

    hashtag
    Synchronization end

    This phase is optional based on the App subscribes to.

    Status: Finished

    events
    events
    events
    events
    events
    events

    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.

    circle-info

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

    hashtag
    [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

    the expected response would be

    circle-info

    Every object represents single form step/one configuration schema.

    circle-info

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

    hashtag
    [POST] /configuration

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

    payload

    All 2xx responses will be treated as success.

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

    circle-info

    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

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

    {
      "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"
          ]
        }
      ]
    }
    [
      {
        "token": "secret API token"
      },
      {
        "setting1": "user input text 1",
        "setting2": "user input text 2"
      }
    ]
    {
      "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"
      ]
    }
    {
      "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"
      ]
    }

    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.

    circle-info

    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.

    hashtag
    Synchronization endpoints

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

    circle-info

    synchronization.events contains an array of detailed payloads containing actual changes - .

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

    This field for DELETED events is always an empty array.

    hashtag
    /consume/attribute_created

    subscribed event:

    example payload:

    hashtag
    /consume/attribute_updated

    subscribed event:

    hashtag
    /consume/attribute_deleted

    subscribed event:

    example payload:

    hashtag
    /consume/category_created

    subscribed event:

    example payload:

    hashtag
    /consume/category_updated

    subscribed event:

    example payload:

    hashtag
    /consume/category_deleted

    subscribed event:

    example payload:

    hashtag
    /consume/product_created

    subscribed event:

    example payload:

    hashtag
    /consume/product_updated

    subscribed event:

    example payload:

    hashtag
    /consume/product_deleted

    subscribed event:

    example payload:

    hashtag
    /consume/synchronization_started

    subscribed event:

    example payload:

    hashtag
    /consume/synchronization_ended

    subscribed event:

    example payload:

    hashtag
    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

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

    hashtag
    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

        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

  • the full list can be found here
    attribute_created
    attribute_updated
    attribute_deleted
    category_created
    category_updated
    category_deleted
    product_created
    product_updated
    product_deleted
    synchronization_started
    synchronization_ended
    {
      "custom_message": "your custom error message",
      "retryable": false
    }
    {
      "name": "attribute_created",
      "resource_id": {
        "id": "Attribute_code",
        "type": "attribute_code"
      },
      "synchronization": {
        "resource_customs": null,
        "events": []
      }
    }
    {
      "name": "attribute_deleted",
      "resource_id": {
        "id": "Attribute_code",
        "type": "attribute_code"
      },
      "synchronization": {
        "resource_customs": {"your": "custom"},
        "events": []
      }
    }
    {
      "name": "category_created",
      "resource_id": {
        "id": "Category_code",
        "type": "category_code"
      },
      "synchronization": {
        "resource_customs": null,
        "events": []
      }
    }
    {
      "name": "category_updated",
      "resource_id": {
        "id": "Category_code",
        "type": "category_code"
      },
      "synchronization": {
        "resource_customs": {"your": "custom"},
        "events": []
      }
    }
    {
      "name": "category_deleted",
      "resource_id": {
        "id": "Category_code",
        "type": "category_code"
      },
      "synchronization": {
        "resource_customs": {"your": "custom"},
        "events": []
      }
    }
    {
      "name": "product_created",
      "resource_id": {
        "id": "Product SKU",
        "type": "sku"
      },
      "synchronization": {
        "resource_customs": null,
        "events": []
      }
    }
    
    {
      "name": "product_updated",
      "resource_id": {
        "id": "Product SKU",
        "type": "sku"
      },
      "synchronization": {
        "resource_customs": {"your": "custom"},
        "events": []
      }
    }
    {
      "name": "product_updated",
      "resource_id": {
        "id": "Product SKU",
        "type": "sku"
      },
      "synchronization": {
        "resource_customs": {"your": "custom"},
        "events": []
      }
    }
    {
        "name": "synchronization_started"
    }
    {
        "name": "synchronization_started"
    }
    {
      "resource_customs": {
        "id": 1,
        "other_relevent_information": "here"
      }
    }

    Configuration schema

    Overview of how to build App configuration

    hashtag
    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 allowing you to build a form.

    circle-info

    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.

    hashtag
    Example schema

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

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

    hashtag
    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 is added - it results in a hidden secret text.

    hashtag
    multilineText

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

    hashtag
    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

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

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

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

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

    hashtag
    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

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

    hashtag
    Widgets

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

    hashtag
    password

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

    hashtag
    textarea

    Presents text input in the form of textarea input.

    hashtag
    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

    JSON Schemasarrow-up-right
    password widget
    textarea widget
    Ergonode dictionaries reference can be found here.
    App dictionaries reference can be found here.
    mapper widget
    password widget
    textarea widget
    Attribute mapping widget overview
    Attributes mapping widget details
    {
      // (...) 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"
          ]
        }
      ]
    }
    [
      {
        "ergonode": "attribute_one",
        "app": "attribute_two"
      }
    ]

    Synchronization events

    Detailed events containing data changes.

    hashtag
    Attributes

    hashtag
    attribute_created

    included with events: attribute_created

    hashtag
    attribute_name_changed

    included with events: ,

    hashtag
    attribute_option_code_changed

    included with events: ,

    hashtag
    attribute_option_created

    included with events: ,

    hashtag
    attribute_option_deleted

    included with events:

    hashtag
    attribute_option_name_changed

    included with events: ,

    hashtag
    Categories

    hashtag
    category_created

    included with events:

    hashtag
    category_name_changed

    included with events: ,

    hashtag
    Products

    hashtag
    product_added_to_category

    included with events: ,

    hashtag
    product_binding_added

    included with events: ,

    hashtag
    product_binding_removed

    included with events:

    hashtag
    product_child_added

    included with events: ,

    hashtag
    product_child_quantity_changed

    included with events: ,

    hashtag
    product_child_removed

    included with events:

    hashtag
    product_created

    included with events:

    hashtag
    product_removed_from_category

    included with events:

    hashtag
    product_value_added

    included with events: ,

    hashtag
    product_value_changed

    included with events: ,

    hashtag
    product_value_removed

    included with events:

    hashtag
    product_variant_added

    included with events:

    hashtag
    product_variant_removed

    included with events:

    attribute_created
    attribute_updated
    attribute_created
    attribute_updated
    attribute_created
    attribute_updated
    attribute_updated
    attribute_created
    attribute_updated
    category_created
    category_created
    category_updated
    product_created
    product_updated
    product_created
    product_updated
    product_updated
    product_created
    product_updated
    product_created
    product_updated
    product_updated
    product_created
    product_updated
    product_created
    product_updated
    product_created
    product_updated
    product_updated
    product_updated
    product_updated
    {
      "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", 
            },
            "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"
      ]
    }
    {
      "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"
      ]
    }
    {
      "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"
      ]
    }
    {
      "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"
      ]
    }
    {
      "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"
      ]
    }
    {
      "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"
      ]
    }
    {
      "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"
      ]
    }
    {
      "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"
      ]
    }
    {
      "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"
      ]
    }
    {
      "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"
      ]
    }
    {
      "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"
      ]
    }
    {
      "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"
      ]
    }
    {
      "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"
      ]
    }
    {
      "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"
      ]
    }
    {
      "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"
      ]
    }
    {
      "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"
      ]
    }
    {
      "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"
      ]
    }
    {
      "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"
      ]
    }
    {
      "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"
      ]
    }
    {
      "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"
      ]
    }
    {
      "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"
      ]
    }
    "
    IMAGE
    "
    ,
    "
    MULTI_SELECT
    "
    ,
    "
    NUMERIC
    "
    ,
    "
    PRICE
    "
    ,
    "
    PRODUCT_RELATION
    "
    ,
    "
    SELECT
    "
    ,
    "
    TEXT_AREA
    "
    ,
    "
    TEXT
    "
    ,
    "
    UNIT
    "
    ]