Каталог Market --- внешний API чтения

Обновлено: Aug 31, 2026Раздел: Интеграции

Полная справка по API: https://api.mailoo.app/docs/v1

Используйте интеграцию Market для управления минимальным каталогом товаров в панели управления Mailoo и предоставления JSON только для чтения вашей витрине или BFF. Каждый внешний маршрут требует ``X-API-Key``; анонимного доступа нет.

Для массового редактирования через CSV (экспорт → редактирование → загрузка → предпросмотр/применение) см. market-catalog-csv{.interpreted-text role="doc"}.

Обзор

  • Авторизация: Заголовок ``X-API-Key`` в каждом запросе.
  • Область действия: Ключи RESTRICTED требуют ``market.external-read`` в allowedOperations (ключи FULL тоже подходят).
  • Маршрутизация: GET {baseUrl}/api/v1/market/{projectUid}/integrations/{integrationId}/… --- тот же формат пути, что и у ресурсов панели управления в /projects/{uid}/integrations/{id}/market/…, без префикса projects.
  • Владение: API-ключ должен принадлежать владельцу проекта; projectUid и integrationId должны ссылаться на ACTIVE интеграцию типа MARKET.
  • Видимость: Товары имеют статус DRAFT/PUBLISHED/ARCHIVED. Внешний API списка/детали/вариантов возвращает только товары со статусом PUBLISHED. API панели управления по-прежнему доступен для всех статусов владельцам.
  • IndexNOW: Опциональный config.indexNow на интеграции Market --- Mailoo отправляет POST с идентификаторами товаров на ваш URL уведомлений при публикации/изменении; вы отправляете URL в IndexNOW (indexnow-notify{.interpreted-text role="doc"}). Строки контента содержат indexNowStatus.
  • Поля товара (панель управления): Каноническое описание товара и варианта хранятся в attributes; опциональные наложения по языкам --- в locales.<code> (name и attributes для каждой записи). name --- атрибут базового типа в записях товара; sku --- обязательный атрибут базового типа, отмеченный как ось варианта (хранится на вариантах, используется при поиске effective-price).
  • JSON товара (только внешний список/деталь/варианты): Ответы не содержат объект locales. attributes товара и каждого варианта уже объединены для одного языка: базовые значения из записи панели управления плюс наложение для разрешённой локали (name по локали отображается на ключ атрибута name). Разрешённая локаль = опциональный параметр locale=<code> если задан и валиден (например, en, de, pt-br); иначе ``defaultCatalogLocale`` из config интеграции MARKET (задаётся на экране Редактировать интеграцию в панели управления); если не задан --- ``en``. Невалидная locale возвращает 400.
  • Поля товара (внешние): После объединения те же правила, что и в панели управления, применяются к плоским attributes (включая семантику name / sku выше).
  • Формат ID: Идентификаторы ресурсов MARKET в параметрах запроса и полезных нагрузках ответов принимают и возвращают значения cuid и cuid2 (например, id тегов/товаров/прайс-листов/поставщиков).
  • Типы цен: См. Типы цен и итоговая цена ниже. Для выбора прайс-листа интеграторы должны настроить и отправить ключ типа цены (параметр ``kind`` в ``GET .../effective-price``). Id типа цены (CUID) --- внутренний (присутствует в некоторых JSON); он не является основным внешним идентификатором.
  • Флаг корневого элемента интегратора: Объекты тегов содержат useAsRoot. Интеграторы могут использовать теги с useAsRoot = true как корневые элементы каталога верхнего уровня (допускается несколько тегов), даже если parentTagId не null.

Иконки тегов: Объекты тегов могут содержать iconMediaId. Для получения байтов изображения используйте интеграцию Images с ключом, допускающим ``image.external-read`` (см. images{.interpreted-text role="doc"}).

Примечание к отображению в панели управления Mailoo: useAsRoot предназначен для логики меню внешнего интегратора. Отображение и порядок дерева тегов в панели управления Mailoo остаются прежними и следуют существующим правилам иерархии.

Переменные окружения BFF

Совпадают с панелью Подключение на странице интеграции (те же имена). Типичные серверные переменные:

  • MAILOO_MARKET_API --- базовый URL API (например, https://api.mailoo.app или ваш dev-хост), без завершающего слэша.
  • MAILOO_MARKET_API_KEY --- API-ключ с ``market.external-read`` (или FULL).
  • MAILOO_MARKET_PROJECT_UID --- UID проекта.
  • MAILOO_MARKET_INTEGRATION_ID --- Id интеграции Market (CUID).

Эндпоинты (все GET, все требуют X-API-Key)

Замените {baseUrl}, {projectUid}, {integrationId} и параметры пути по необходимости.

  • GET {baseUrl}/api/v1/market/{projectUid}/integrations/{integrationId}/types --- Типы товаров и атрибуты.
  • GET …/products --- Все опубликованные товары (варианты и привязки к тегам включены; объединённая локаль, без locales). Опциональный параметр ``locale`` (см. Обзор).
  • GET …/products/{productId} --- Один товар (тот же контракт).
  • GET …/products/{productId}/variants --- Только варианты; каждая строка имеет объединённые attributes и не содержит locales. Опциональный ``locale``.
  • GET …/products/{productId}/price-range --- Для одного опубликованного товара: итоговая цена за единицу по вариантам (minAmount / maxAmount совпадают для одного типа цены) плюс опциональная товарная строка и агрегат мин/макс по этим числам. Параметры: ``kind`` или ``priceKindId`` (те же правила, что и у ``effective-price``), опциональные ``measurementUnitId``, ``at`` (ISO datetime), опциональный ``currency`` (ISO 4217, три буквы). Разрешение валюты: если ``currency`` не указан, валюта ответа берётся из первой подходящей строки прайса при обходе активных листов в хронологическом порядке (``effectiveAt`` по возрастанию) и строк по порядку ``id``; для слияний используются только строки в этой валюте (другие валюты игнорируются). Если ``currency`` указан, участвуют только строки в этой валюте; поле ``currency`` ответа дублирует запрос даже при отсутствии подходящих строк (суммы будут null).
  • GET …/tags и GET …/tags/{tagId} --- Теги каталога.
  • GET …/price-lists и GET …/price-lists/{priceListId} --- Внутренние прайс-листы.
  • GET …/effective-price --- Определение цены за единицу для товара и типа цены. Используйте параметр ``kind=<priceKindKey>`` (см. Типы цен и итоговая цена). Опциональные: productId / variantId / measurementUnitId / at (ISO datetime). (Параметр ``priceKindId`` принимается для внутренних вызовов или BFF; внешний код каталога должен использовать ``kind``.)
  • GET …/suppliers --- Поставщики.
  • GET …/suppliers/{supplierId}/products --- Товары поставщика.
  • GET …/suppliers/{supplierId}/price-lists и GET …/suppliers/{supplierId}/price-lists/{supplierPriceListId} --- Прайс-листы поставщика.
  • GET …/units --- Единицы измерения.
  • GET …/packagings --- Упаковки.
  • GET …/packaging-equivalences --- Эквиваленты упаковок.

Ответы используют { success: true, data: … }. Для внешних списка/детали/вариантов товаров data соответствует форме с объединённой локалью выше (без locales). Аутентифицированный GET /api/v1/projects/…/market/products… панели управления по-прежнему возвращает полную форму редактора, включая locales.

Типы цен и итоговая цена

  • В базе данных тип цены имеет стабильный строковый ``key`` (уникальный для интеграции MARKET, например, base) и внутренний ``id`` (CUID). Ключ --- это то, что следует настроить в окружении / CMS («используйте base для прайс-листов»).
  • Получение цены из внешнего или витринного кода: вызовите ``GET {baseUrl}/api/v1/market/{projectUid}/integrations/{integrationId}/effective-price`` с ``kind=<key>`` и ``productId=`` (и опциональными ``variantId``, ``measurementUnitId``, ``at``). Это поддерживаемый контракт: ``kind`` --- ключ типа цены, а не CUID. Все активные прайс-листы с effectiveAt <= at объединяются в хронологическом порядке (старые сначала, новые накладываются): для заданного ``variantId`` выигрывает новейшая строка для конкретного варианта по SKU; если ни один лист после не переопределяет этот SKU, старая строка остаётся. Если строки по SKU нет вообще, применяется новейшая товарная строка (``variantId`` null). Если ``variantId`` не указан, учитываются только товарные строки (выигрывает новейшая).
  • ``priceKindId`` в строке запроса предназначен только для вызывающих, которые уже хранят CUID (инструменты Mailoo, BFF, зеркалирующие внутренние id). Не используйте параметр priceKindId как основной внешний API для выбора прайс-листа --- используйте ``kind``.
  • Ответы могут содержать ``priceKindId`` и ``priceKindKey`` (и аналогичные у строк прайс-листа). Вы можете читать ``priceKindKey``; ``priceKindId`` в ответах --- для корреляции и не предназначен для жёсткого кодирования в публичном фронтенде.
  • Жизненный цикл прайс-листа (пути чтения): GET …/price-lists и полезные нагрузки списка включают ``archivedAt`` и ``endsAt`` при наличии. ``GET .../effective-price``, ``GET .../products/{productId}/price-range`` и ценообразование при оформлении заказа учитывают только листы, которые не архивированы и, если ``endsAt`` задан, только когда момент ``at`` запроса строго раньше ``endsAt``. Вызовы панели управления могут PATCH / DELETE внутренние прайс-листы через ``/api/v1/projects/.../market/price-lists/{priceListId}`` (Bearer); удаление отклоняется с 409, если измерения какой-либо строки фигурируют в существующей строке заказа для этой интеграции.

Типы значений атрибутов товара (/types)

GET …/types возвращает атрибуты типов с:

  • valueType: STRING, NUMBER, BOOLEAN, ENUM, SELECT, MD_TEXT
  • options: string[] | null

SELECT --- строгий списочный тип. Для SELECT options --- непустой список допустимых значений, и значения атрибутов товара/варианта должны точно совпадать с одним из них.

``MD_TEXT`` и изображения: Любой атрибут товара или варианта с valueType: MD_TEXT может содержать встроенные изображения Mailoo в синтаксисе markdown (например, ![alt](url)). При записи панель управления нормализует URL изображений Mailoo в этих полях; JSON товаров и вариантов из ответов GET также нормализует вложенные строки, чтобы интеграторы видели каноническую форму публичного встраивания (…/api/v1/images/public/content?id=<mediaId>). Для получения байтов изображения по этому URL используйте API Images с ключом, допускающим ``image.external-read`` (см. images{.interpreted-text role="doc"}).

``MD_TEXT`` и HTML (только внешний GET каталога): На внешних маршрутах GET …/products, GET …/products/{productId} и GET …/products/{productId}/variantsX-API-Key и опубликованным товаром) API добавляет параллельное строковое поле <attributeKey>Html рядом с каждым Markdown-значением для атрибутов, объявленных как MD_TEXT на любом из связанных типов товара, в объединённых attributes товара и каждого варианта (отдельного дерева locales в ответе нет). Используйте оригинальный ключ для исходного Markdown (как content блога) и …Html для серверного HTML (как htmlContent блога). Ответы GET /projects/…/market/… панели управления не содержат эти поля *Html, чтобы не создавать лишнюю нагрузку при редактировании.

Галерея и изображение для списка: Каждый товар может иметь упорядоченный массив ``images`` (объекты с id, mediaId, sortOrder и каноническим ``url``) плюс ``previewMediaId`` и ``previewImageUrl``. Превью --- изображение, рекомендуемое для карточек каталога / списков товаров; если ``previewMediaId`` равен null, интеграторы могут использовать первое изображение галереи. ID медиа галереи валидируются при создании/обновлении (READY медиа, в контексте USER / PROJECT / INTEGRATION владельца для этого каталога). Опциональное масштабирование на лету: добавьте ``w``, ``h``, ``fit``, ``format``, ``q`` к URL публичного контента, как документировано в images{.interpreted-text role="doc"}.

Пример определения атрибута:

{
  "key": "display_type",
  "name": "Display Type",
  "valueType": "SELECT",
  "options": ["LCD", "OLED", "micro-OLED", "QD-LCD"]
}

Примечание о несовместимом изменении: интеграции, ранее использовавшие ENUM как свободную строку, должны перейти на SELECT, если требуется строгий список допустимых значений.

Панель управления: создание товаров из JSON

В панели управления MARKET (раздел Товары) можно подготовить полезные нагрузки товаров в виде JSON:

  • Скопировать шаблон --- копирует текущий черновик товара + подсказки схемы:
  • schemaVersion
  • product (typeIds, attributes, tagIds)
  • template.attributeSchema (ключи, типы значений, обязательность, опции SELECT)
  • template.availableTags (все доступные теги каталога)
  • Вставить данные товара --- переключает форму в режим ввода JSON, куда вы вставляете полезную нагрузку вручную (запрос на чтение буфера обмена не требуется).
  • Создать товар (в режиме JSON) валидирует полезную нагрузку на сервере и создаёт товар при успешной валидации.

Для массового обновления предпочтительнее загрузка/выгрузка CSV: market-catalog-csv{.interpreted-text role="doc"}.

Ожидаемый формат JSON

{
  "schemaVersion": 1,
  "product": {
    "typeIds": ["<typeId-1>", "<typeId-2>"],
    "attributes": {
      "name": "Vision Pro X Ultra",
      "display-type": "micro-OLED",
      "short_description": "Flagship model.",
      "full_description": "## Vision Pro X Ultra"
    },
    "tagIds": ["<tagId-1>"]
  }
}

Поведение валидации

Серверная валидация --- единственный источник истины (дублирование доменных правил на клиенте не требуется):

  • Панель управления вызывает POST /api/v1/projects/{uid}/integrations/{id}/market/products/validate перед созданием.
  • Эндпоинт использует те же правила валидации, что и создание товара:
  • неизвестные ключи атрибутов отклоняются
  • обязательные атрибуты должны присутствовать
  • типы значений должны совпадать (NUMBER/BOOLEAN/STRING/MD_TEXT/ENUM/SELECT)
  • значения SELECT должны быть одним из объявленных options
  • id тегов должны принадлежать интеграции
  • При неуспешной валидации товар не создаётся и отображается сообщение об ошибке API.

Оформление заказа по e-mail (заказы)

Вышеописанный API чтения каталога не включает корзину: витрина интегратора хранит состояние корзины (например, localStorage) и отправляет снимок при оформлении заказа покупателем.

  • Создание заказа (сервер / BFF): POST {baseUrl}/api/v1/market/{projectUid}/integrations/{integrationId}/orders с заголовком ``X-API-Key`` и областью ``market.order.submit`` (ключи RESTRICTED) или FULL. Тело запроса: e-mail покупателя ``customerEmail``, ``items[]`` (каждая строка: ``productId``, ``quantity`` как десятичная строка, и либо ``priceKindKey`` (ключ интегратора, например base), либо ``priceKindId`` (CUID) --- не оба, опциональные ``variantId`` / ``measurementUnitId``). Mailoo определяет цены за единицу из тех же прайс-листов, что и ``GET .../effective-price``; ответ включает ``accessToken`` и ``accessExpiresAt`` (временный доступ к заказу только для чтения).
  • Чтение заказа (браузер / любой клиент): GET {baseUrl}/api/v1/market/public/orders/{orderId}?token= или заголовок ``X-Market-Order-Token`` --- без API-ключа. Возвращает снимок заказа, пока токен действителен (TTL по умолчанию 72 часа, переопределяемый через ``MARKET_ORDER_ACCESS_TOKEN_TTL_HOURS`` на хосте API). Отсутствующий/невалидный/просроченный токен возвращает 404 (то же сообщение, что и при неверном id).
  • Панель владельца (JWT): список/получение/обновление заказов через /api/v1/projects/{uid}/integrations/{id}/market/orders/… (Bearer).

Полный поток, паттерны BFF и операционные заметки: market-order-email-checkout{.interpreted-text role="doc"}.

Дополнительные материалы

  • OpenAPI: https://api.mailoo.app/docs/v1
  • CSV-процесс в панели управления: market-catalog-csv{.interpreted-text role="doc"}