# Taxonomy

The taxonomy is the channel's product format, registered in Mirakl Connect.
It has two parts:

* **product types**, which set the category a product belongs to and the attributes a product of that category must carry,
* **taxonomy rules**, which redefine the requirement level of an attribute for a specific case.


This page covers the two calls that register the taxonomy, the structure each call expects, what the Catalog Transformer reads from it, and how to keep it in step with the channel.

## Business context

Registering the taxonomy is a **required step**.
Mirakl Connect uses the [Catalog Transformer](/content/product/connect-channel-platform/getting-started/concepts-and-glossary) to turn a seller's products into channel products, and the Catalog Transformer transforms against the registered taxonomy.
Until the taxonomy exists there is nothing to transform against, so no product reaches the channel.

Its completeness then sets the accuracy of the result.
The Catalog Transformer infers each channel attribute from the seller's own data, and the taxonomy is everything it knows about the target.

Once the taxonomy and the [catalog configuration](/content/product/connect-channel-platform/developer-guide/catalog-configuration) are complete, the seller can synchronize products to the channel through the [catalog flow](/content/product/connect-channel-platform/developer-guide/catalog-flow).

## Activation by Mirakl

Mirakl must activate the taxonomy in the Catalog Transformer, and train the model on it.
**That step is not self service, and only the Mirakl Partner team can do it.**

Request the activation from your contact in the Mirakl Partner team, and give them the channel ID.
Register the complete taxonomy before you ask, because the training reads the taxonomy that is registered at that moment.

Until the Mirakl Partner team completes the activation, the Catalog Transformer transforms no product for the channel.
Your connector then receives no [ProductUpsertEvent](/content/product/connect-channel-platform/webhooks/webhook/webhooks/productupsertevent), whatever your taxonomy holds and whatever product status you report.

Once the initial activation is done, Mirakl Connect pushes the registered taxonomy to the Catalog Transformer on a recurring daily schedule, **and not on each call**.

## How it works

Your connector reads the taxonomy from the channel, then registers it in Mirakl Connect with two calls.

```mermaid
sequenceDiagram
    box rgb(219,234,254) Mirakl
    participant P as Mirakl Connect
    end
    box rgb(209,250,229) Middleware
    participant I as Channel Connector
    end
    box rgb(254,240,199) Channel
    participant C as Channel
    end

    I->>C: Fetch the taxonomy
    C-->>I: Categories, attributes, and their constraints
    I->>P: createTaxonomyRule (the rules the product type needs)
    P-->>I: 202 Accepted, the rules are stored
    I->>P: upsertProductType (references those rules)
    P-->>I: 202 Accepted, the upsert is queued
    P->>P: Read the payload and store the product type
    Note over P: The registered taxonomy feeds the Catalog Transformer
```

Three properties of this flow shape everything below.

* **The rules come first.**
A product type references a rule by its `id`.
Mirakl Connect drops a reference to a rule it does not hold yet, and it reports nothing back, so the rule never applies.
Register the rules of a product type before you register the product type.
* **One call carries one product type, in full.**
`upsertProductType` replaces the product type, so a call that omits an attribute removes that attribute.
* **The two calls do not behave the same way.**
`createTaxonomyRule` validates its payload during the request, so a malformed rule fails the call with a `400`.
`upsertProductType` queues its payload, so a problem inside the content surfaces after the `202`.


`upsertProductType` is **asynchronous**.
A `202 Accepted` response means that Mirakl Connect queued the request, not that it applied the product type.
Refer to [Calling the APIs](/content/product/connect-channel-platform/developer-guide/calling-the-apis#synchronous-and-asynchronous-operations).

## Product types

A product type describes the format of a product: the category it belongs to, and the attributes a product of that category must provide.

Register one with the [upsertProductType](/content/product/connect-channel-platform/rest/connect/openapi3/taxonomy/upsertproducttype) API.
The identity is the `product_type_id` query parameter, which is the channel's own identifier for the product type.
The body holds four nodes:

* `labels`: the name of the product type, one entry per locale.
At least one entry is required.
This is what the seller reads when it picks a category.
* `context.labels`: the position of the product type in the channel's own category tree, such as `Electronics / Mobile Devices / Smartphones`.
It is optional, and the Catalog Transformer reads it.
* `embedded_attributes`: the attributes of the product type.
Read [Attribute types](#attribute-types) below.
* `rules`: the taxonomy rules that apply to the product type, each one referenced as `{ "ref": "<rule id>" }`.
Read [Taxonomy rules](#taxonomy-rules) below.


`upsertProductType` operates as a full replacement, with `PUT`.
Always send the product type's complete definition, because Mirakl Connect removes anything that is absent from the request body.
This covers the attributes, the entries of a value list, and the rule references alike.

### Store-level product types

A product type is registered for the whole channel by default.
The optional `store_id` query parameter registers it for one store instead, which suits a channel that gives each seller its own category set.

Two conditions apply.
The store must already exist in Mirakl Connect, and the call returns a `404` when it does not.
Mirakl enables this granularity for each channel, so ask your contact in the Mirakl Partner team before you use it.

## Attribute types

Every attribute of `embedded_attributes` carries the same five required fields.

* `id`: the channel's own identifier for the attribute, unique inside the product type.
It is the identifier you receive back on the events, so keep it stable.
* `type`: one of `Text`, `Integer`, `Decimal`, `List`, and `Media`.
* `requirement_level`: `REQUIRED`, `RECOMMENDED`, or `OPTIONAL`.
* `labels`: the name of the attribute, one entry per locale, with at least one entry.
* `descriptions`: the help text of the attribute, one entry per locale.
The field is required, and an empty array is valid.


Two optional nodes complete an attribute, and both reach the Catalog Transformer.

* `connect_standard_attributes`: the standard attributes the attribute corresponds to.
Only a few attributes of a product type have one.
Read [Standard attributes](#standard-attributes) below.
* `context`, with `labels` and `path`.
[Catalog Transformer](#catalog-transformer) below says what to put in them.


Each type then adds its own fields.

* **`Text`** accepts `min_length` and `max_length`.
* **`Integer`** accepts `minimum` and `maximum`.
* **`Decimal`** requires `scale`, the number of decimal places, and accepts `minimum` and `maximum`.
* **`List`** requires `available_values`, from 1 to 1000 entries.
Each entry carries an `id` and its `labels`.
* **`Media`** accepts `media_type`, either `IMAGE` or `OTHER`.


A locale is an ISO-639 language, alone or with an ISO-3166 country, such as `en` or `en_US`.

There is no boolean type.
Register a boolean as a `List` of two values, and give each value a label the seller and the Catalog Transformer can read.

Mirakl Connect reads the attributes after it answers the `202`, and it skips an attribute it cannot read instead of failing the call.
An attribute disappears when it misses `id` or `requirement_level`, when a `List` carries no `available_values`, or when a `Decimal` carries no `scale`.
Two attributes that share one `id` also lose one of the two.
In each case the product type is registered without that attribute, and the response already said `202`.

### Example

The product type below carries a title, a colour taken from a closed list, and a main image.

```json
{
  "labels": [
    { "locale": "en_US", "value": "Smartphones" }
  ],
  "context": {
    "labels": [
      { "locale": "en_US", "value": "Electronics / Mobile Devices / Smartphones" }
    ]
  },
  "embedded_attributes": [
    {
      "id": "item_name",
      "type": "Text",
      "requirement_level": "REQUIRED",
      "labels": [{ "locale": "en_US", "value": "Product name" }],
      "descriptions": [{ "locale": "en_US", "value": "The name shown on the product page, without the brand." }],
      "context": {
        "labels": [{ "locale": "en_US", "value": "Basic information / Product name" }],
        "path": "$.item_name[0].value"
      },
      "connect_standard_attributes": ["TITLE"],
      "max_length": 200
    },
    {
      "id": "color",
      "type": "List",
      "requirement_level": "RECOMMENDED",
      "labels": [{ "locale": "en_US", "value": "Colour" }],
      "descriptions": [{ "locale": "en_US", "value": "The dominant colour of the item as the buyer sees it." }],
      "context": {
        "labels": [{ "locale": "en_US", "value": "Appearance / Colour" }],
        "path": "$.color[0].value"
      },
      "available_values": [
        { "id": "NB01", "labels": [{ "locale": "en_US", "value": "Navy blue" }] },
        { "id": "BK01", "labels": [{ "locale": "en_US", "value": "Black" }] }
      ]
    },
    {
      "id": "main_image",
      "type": "Media",
      "requirement_level": "REQUIRED",
      "labels": [{ "locale": "en_US", "value": "Main image" }],
      "descriptions": [{ "locale": "en_US", "value": "The first image of the product page, on a white background." }],
      "context": {
        "labels": [{ "locale": "en_US", "value": "Images / Main image" }],
        "path": "$.main_product_image_locator[0].media_location"
      },
      "connect_standard_attributes": ["IMAGE"],
      "media_type": "IMAGE"
    }
  ]
}
```

## Standard attributes

A **standard attribute** is a product field that Mirakl Connect holds for every channel.
Associate each one with the channel attribute that corresponds to it, in `connect_standard_attributes`.

There are eight.

* `PRODUCT_ID`: the SKU, the seller's own product reference.
* `CATEGORY`: the product type.
* `BRAND`: the brand.
* `TITLE`: the title.
* `DESCRIPTION`: the description.
* `IMAGE`: the main image.
* `GTIN`: the product identifier.
* `VARIANT_GROUP_CODE`: the code that groups the variants of one product.


## Taxonomy rules

A taxonomy rule redefines the **requirement level** of an attribute when a condition holds.

Register rules with the [createTaxonomyRule](/content/product/connect-channel-platform/rest/connect/openapi3/taxonomy/createtaxonomyrule) API.
Rules belong to the channel, not to a product type, and one call sends a batch of them in the `rules` array.
A product type then references the ones it needs, by their `id`.

Each rule holds three fields.

* `id`: your own identifier, and the value a product type references.
* `when`: the condition.
* `then`: the actions to apply when the condition holds.


A condition is one of six forms, and the three that combine can nest to any depth:

* `all`: every nested condition holds.
* `any`: at least one nested condition holds.
* `not`: the nested condition does not hold.
* `isSet`: the named attribute carries a value.
* `equals`: the named attribute equals a string, a number, or a boolean.
* `in`: the value of the named attribute is one of a list.


`then.all` holds the actions, and there is one action.
`redefine` names an `attribute` and the `requirementLevel` to give it.

```json
{
  "rules": [
    {
      "id": "size-required-for-clothing",
      "when": {
        "all": [
          { "in": { "attribute": "item_type", "values": ["shirt", "trousers"] } },
          { "not": { "isSet": { "attribute": "size" } } }
        ]
      },
      "then": {
        "all": [
          { "redefine": { "attribute": "size", "requirementLevel": "REQUIRED" } }
        ]
      }
    }
  ]
}
```

### Two patterns worth copying

**Derive the rules from the channel's own conditional schema.**
A channel that publishes a JSON schema for each product type usually expresses its conditional requirements there.
Translate each of those conditions into a `when`, and each conditional requirement into a `redefine`.
The rules then stay correct without a human maintaining them.

**Keep a group of attributes consistent.**
When the channel groups attributes that only make sense together, emit one rule whose `when` is an `any` of `isSet` over every member, and whose `then` makes every member `REQUIRED`.
A seller who fills one member is then asked for the rest, instead of sending the channel a half-filled group.

### Use a content hash as the rule id

Compute the `id` of a rule from a hash of its content.
This gives you two properties.
Sending the same rule again changes nothing, and two product types that need the same rule reference one rule instead of two.

## Catalog Transformer

The Catalog Transformer is the Mirakl capability that transforms a seller's products into channel products, with the channel's registered taxonomy.
That taxonomy is the only thing that describes the channel to it.
Everything below reaches the model, and anything you leave out does not.

* **`labels`**: the name of the attribute or of the product type.
Write the name the channel itself uses.
When the attribute sits inside a nested structure, put the whole path in the label, such as `Certificate - Issuing body`, because a label that reads `Name` on its own is ambiguous.
* **`descriptions`**: the help text.
Copy the channel's own wording, without shortening it.
This is the richest signal the model gets, because it states what the channel expects rather than what the field is called.
* **`context.labels`**: where the attribute or the product type sits in the channel's own structure, such as `Appearance / Colour`.
It separates two attributes that share a label under different parents.
* **`context.path`**: where the value sits in the channel's payload, as a JSON path such as `$.color[0].value`.
* **`requirement_level`**: what the model must produce, and what it may leave empty.
* **`available_values`**: the closed set the model must choose from.
Give every entry a label, in every locale the sellers write in.
The model matches the seller's wording against the labels, so an entry with only an `id` can be reached only by an exact match on that `id`.
* **The bounds**: `min_length` and `max_length`, `minimum` and `maximum`, and `scale`.
They tell the model the shape of an acceptable value.
* **`media_type`**: whether the attribute expects an image or another kind of file.
* **The standard attributes**: which of Mirakl Connect's own fields feed the attribute.
* **The rules**, with the attributes their conditions and their actions name.


The bounds guide the model, and Mirakl Connect does not enforce them.
A value that exceeds `max_length` is not rejected before it reaches the channel, so the channel is what refuses the product.
Your connector reports that outcome through the [feedback](/content/product/connect-channel-platform/developer-guide/catalog/report-feedback).
The same holds for a required attribute that stays empty.

### Build the taxonomy for the model, not only for the contract

The contract accepts a product type whose attributes carry an id, a bare label, and no description.
The Catalog Transformer then infers from the id and the type alone, and the accuracy drops for every seller on the channel.

Three rules give most of the accuracy.

* **Send the channel's own wording.**
The channel's attribute names and help texts already describe the target.
A wording you write yourself describes your connector instead.
* **Make each label readable on its own.**
The model reads a label without its neighbours, so `Length` needs its parent and its unit, as in `Package dimensions - Length (cm)`.
* **Never send a value list without labels.**
A code the channel invented carries no meaning for a model that reads a seller's catalog.


## Keep the taxonomy in step

A channel's taxonomy changes on its own schedule, and the API has no delete.
Run this loop.

1. **Fetch the channel's taxonomy on a schedule.**
No channel notifies a taxonomy change, so a periodic full read is the only reliable source.
2. **Persist what you fetched, and hash each product type.**
Compute the hash over the product type and everything it references, which is its attributes, their value lists, and its rules.
The push then reads your own database, and a channel outage cannot block it.
3. **Push only what changed.**
Compare the current hash to the hash you last pushed, and skip the product types that match.
A taxonomy of thousands of product types changes a few at a time, so most runs then send no call at all.
4. **Send the rules of a product type before the product type.**
Mirakl Connect drops a reference to a rule it does not hold.
5. **Contain a failure to one product type.**
Log it, leave its pushed hash untouched, and carry on with the rest.
The next run retries it.


Four constraints apply to the calls themselves.

* **Do not send two upserts of the same product type at the same time.**
Mirakl Connect compares the timestamp of an upsert to the last one it stored, and it drops the older one.
Two concurrent upserts of one product type can therefore lose the later content.
* **Honour the published cadence.**
Both operations recommend 5 calls per second and allow 10.
Refer to [Call frequency](/content/product/connect-channel-platform/developer-guide/calling-the-apis#call-frequency--rate-limiting).
* **A product type you stop sending stays registered.**
There is no delete operation, so plan for the sellers who still see a category the channel retired.
* **Both operations are idempotent.**
Sending the same content again is safe, which is what makes a retry safe.
Refer to [Idempotency](/content/product/connect-channel-platform/developer-guide/best-practices#idempotency).


## Common mistakes

### The product type references a rule that does not exist yet

**What it looks like:** one run sends `upsertProductType` with `rules`, and sends `createTaxonomyRule` after it, or in a separate job.

**The failure:** Mirakl Connect keeps only the references it can resolve, and it reports nothing.
The product type is registered, the rules are registered, and no rule applies to the product type.
Nothing in the response or in the feedback says so.

**The fix:** call `createTaxonomyRule` first, then `upsertProductType` in the same run.
Take the rule ids from the rules you just sent, and reference those.

### The upsert carries only what changed

**What it looks like:** an attribute changed its requirement level, so the call sends that one attribute.

**The failure:** `upsertProductType` replaces the product type, so every other attribute, value list entry, and rule reference is removed.
The seller loses the fields, and the Catalog Transformer loses the target.

**The fix:** rebuild the complete definition on every call.
The change detection decides if you call.
It never decides what the body holds.

### A type-specific required field is missing

**What it looks like:** a `List` attribute is sent with an empty `available_values`, or a `Decimal` is sent without `scale`.

**The failure:** the call returns `202`, and Mirakl Connect then skips that attribute.
The product type is registered without it, so the seller is never asked for the value and the channel rejects the product.

**The fix:** validate each attribute against its type before you send it.
Give a `List` at least one value, and a `Decimal` a `scale`.
An attribute whose values you cannot fetch from the channel is better left out of the payload than sent empty.

### The labels and descriptions are empty or generic

**What it looks like:** `labels` repeats the `id`, `descriptions` is `[]`, and the value lists carry ids only.

**The failure:** the Catalog Transformer has the identifier and the type, and infers from those.
The accuracy drops for every seller on the channel, and it drops silently, because a wrong inference still produces a product.

**The fix:** carry the channel's own attribute name, help text, and value labels through your connector, and register them.
Read [Build the taxonomy for the model, not only for the contract](#build-the-taxonomy-for-the-model-not-only-for-the-contract).

### Two attributes share one id

**What it looks like:** the channel's taxonomy exposes the same attribute under two parents, and the mapping flattens both to the same `id`.

**The failure:** Mirakl Connect keeps one and ignores the other.
You do not control which one it keeps.

**The fix:** make the `id` unique inside the product type, by keeping the parent in it.
Then use `context.labels` and `context.path` to say where each one belongs.

## Related pages

* [Catalog configuration](/content/product/connect-channel-platform/developer-guide/catalog-configuration): the other half of the catalog setup, which shapes the offer instead of the product.
* [Catalog flow](/content/product/connect-channel-platform/developer-guide/catalog-flow): what the seller synchronizes once the taxonomy is registered.
* [Create or update products](/content/product/connect-channel-platform/developer-guide/catalog/create-products): how a product the Catalog Transformer produced reaches the channel.
* [Report feedback](/content/product/connect-channel-platform/developer-guide/catalog/report-feedback): how you report a product the channel refused for a missing or invalid attribute.
* [Calling the APIs](/content/product/connect-channel-platform/developer-guide/calling-the-apis): the mechanics the two taxonomy calls share with every other call.
* [REST APIs reference](/content/product/connect-channel-platform/rest/connect/openapi3): the complete schemas of the two taxonomy operations.