> ## Documentation Index
> Fetch the complete documentation index at: https://docs.molin.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Product Sync with Schema.org

> Sync your products using Schema.org structured data format

This spec describes a JSON API served via HTTP that uses the [Schema.org](https://schema.org/) structured data format for product synchronization, with header-based authentication.

## Intro

We chose the [Schema.org](https://schema.org/) format with [JSON-LD spec](https://json-ld.org/) because it's the web standard for structured data that most e-commerce platforms already use. If you visit any product page hosted by Shopify, WooCommerce, or other major platforms, you'll find a `type="application/ld+json"` script tag encoding product information in Schema.org format. This API leverages that same standardized structure to make product data integration seamless.

## Endpoints

We require the following endpoints if you are creating a custom API for Molin.

### Retrieving all products

We need an endpoint that we can call frequently to download your products.

#### Spec

* Content-Type must be **application/json**

* The body must contain a list of products encoded as a JSON string

* Each product object must be of type [schema.org/Product](https://schema.org/Product)

#### Example

GET [https://petshop.com/molin/products.json](https://petshop.com/molin/products.json)

```json theme={null}
[
  {
    "@context": "http://schema.org",
    "@type": "Product",
    "name": "Coffee Lenoa | Espresso",
    "description": "The best coffee beans imported from...",
    "brand": "Coffee Lenoa",
    "image": "//lenoacoffeeandshop.hu/cdn/shop/files/...",
    "url": "https://lenoacoffeeandshop.hu/products/lenoa-espresso",
    "offers": {
      "@type": "AggregateOffer",
      "priceCurrency": "USD",
      "lowPrice": "12.99",
      "highPrice": "19.99",
      "itemCondition": "http://schema.org/New",
      "availability": "http://schema.org/InStock",
      "offerCount": "2",
      "offers": [
        {
          "@type": "Offer",
          "name": "Coffee Lenoa | Espresso — 1000 gramm",
          "availability": "http://schema.org/InStock",
          "priceCurrency": "USD",
          "price": "19.99"
        },
        {
          "@type": "Offer",
          "name": "Coffee Lenoa | Espresso — 250 gramm",
          "availability": "http://schema.org/InStock",
          "priceCurrency": "USD",
          "price": "12.99"
        }
      ]
    },
    "additionalProperty": [
      {
        "@type": "PropertyValue",
        "name": "Origin",
        "value": "Ethiopia"
      },
      {
        "@type": "PropertyValue",
        "name": "Roast Level",
        "value": "Medium"
      },
      {
        "@type": "PropertyValue",
        "name": "Caffeine Content",
        "value": "High"
      }
    ]
  }
]
```

The above response contains 1 product along with price and stock information.

<Note>You don’t have to use an [schema.org/AggregateOffer](https://schema.org/AggregateOffer), you can just return a single [schema.org/Offer](https://schema.org/Offer).</Note>

#### Mandatory fields

* **name**

* **offers** (so we can extract a price — must include **price** and **priceCurrency**)

* **url** (so we can uniquely identify the product)

#### Optional but recommended fields

* **image**
* **sku** or **mpn** (set at the **Product** level — see [Product identifiers](#product-identifiers) below)

### Handling of multiple offers

Each entry in your feed represents **one indexed product row**. If a `Product` contains multiple `Offer` entries (or an `AggregateOffer` wrapping multiple inner offers), Molin only reads the **first offer** to extract `price`, `priceCurrency`, `availability`, and `mpn`. **All subsequent offers are ignored.**

If you sell product variants (different sizes, colors, SKUs, etc), you must return them as **separate `Product` objects** in the feed, each with its own `url`, `price`, and identifiers. Putting variants inside the `offers` array will cause every variant except the first to be silently dropped.

### Product identifiers

Molin stores a single `sku` value per product, used for keyword search (e.g. when a customer searches by part number). The value is resolved in this priority order:

1. `Product.sku` (top-level) — **recommended**
2. `Product.mpn` (top-level) — fallback when `sku` is missing
3. `offers[0].mpn` — fallback for feeds that put MPN only on the first offer
4. A `PropertyValue` named `MPN`/`SKU` inside `additionalProperty`

<Warning>
  Setting `mpn` on each `Offer` variant inside a single `Product` does **not** work for variant lookup. Only the first offer's `mpn` is read, and the rest are dropped. To make every MPN searchable,
  return each variant as its own top-level `Product` with the MPN set at the Product level (or inside its first/only `Offer`).
</Warning>

GTIN values are read from `gtin14`, `gtin13`, `gtin12`, `gtin8`, or `gtin` at the Product level (in that order of preference).

### Providing extra attributes

You can include additional product attributes using the `additionalProperty` field from the [schema.org](https://schema.org/) specification. This allows you to provide custom properties that aren't part of the standard Product schema.

The `additionalProperty` field accepts an array of [PropertyValue](https://schema.org/PropertyValue) objects, each containing:

* **name** - The property name (e.g., "Origin", "Material", "Size")
* **value** - The property value (e.g., "Ethiopia", "Cotton", "Large")

#### Example usage

```json theme={null}
"additionalProperty": [
  {
    "@type": "PropertyValue",
    "name": "Origin",
    "value": "Ethiopia"
  },
  {
    "@type": "PropertyValue",
    "name": "Roast Level",
    "value": "Medium"
  },
  {
    "@type": "PropertyValue",
    "name": "Caffeine Content",
    "value": "High"
  }
]
```

This is particularly useful for:

* Product specifications (dimensions, materials, etc.)
* Custom categorization
* Brand-specific attributes
* Technical details that customers might ask about

<Note>Molin uses these additional properties to provide more detailed and accurate responses about your products when customers ask specific questions.</Note>

<Note>There is a limit of 20 attributes per product. Molin will ignore any additional attributes beyond this limit.</Note>

<Note>Product descriptions are limited to 4000 characters. Longer descriptions will be truncated.</Note>

### Retrieving 1 product

This is an optimization that we have not built yet.

If you have lots of products, it is more efficient to also offer a single product endpoint. For example, if a customer asks Molin about the stock of 1 specific product, we will update only this specific product (quicker than downloading all of them).

## Authentication

You may add header-based authentication that relies on a simple shared secret (token).

### Example

If you give us the token `M123ABC`, we will include the following header in all requests:

```
Authorization: M123ABC
```
