Яндекс Маркет / Partner API / автоматизация / маркетплейсы

Автоматизация Яндекс Маркета через API: интеграция товаров, цен, остатков и заказов

Яндекс Маркет API позволяет автоматизировать каталог, карточки, цены, остатки, заказы, FBS-документы, отзывы, вопросы, чаты и отчёты. Но рабочая интеграция — это не один HTTP-запрос, а цепочка: категории → характеристики → товары → карточки → цены → карантин → остатки → заказы → отгрузки → уведомления. Ниже — архитектура, методы Partner API, JSON-примеры, n8n workflow и список ошибок, которые лучше поймать до запуска.

Коротко. Яндекс Маркет API позволяет автоматизировать каталог, карточки, цены, остатки, заказы, FBS-документы, отзывы, вопросы, чаты и отчёты. Но рабочая интеграция — это не один HTTP-запрос, а цепочка: категории → характеристики → товары → карточки → цены → карантин → остатки → заказы → отгрузки → уведомления. Это третий материал серии по маркетплейсам — рядом Wildberries API и Ozon Seller API.

Для кого эта статья и какой интент закрывает

Для селлера, интегратора или разработчика, которому нужно связать Яндекс Маркет с 1С, МойСклад, CRM, ERP, PIM, Google Sheets, n8n, кастомной базой товаров или собственным backend. Статья не пересказывает документацию — это рабочая архитектура, API-методы, JSON-примеры, n8n workflow и список ошибок, которые лучше поймать до запуска. Если нужна бизнес-картина «зачем и что это даёт», смотрите общий материал автоматизация Яндекс Маркета для селлера; здесь — техническая сторона.

Пользователь хочет понять не «что такое API», а как реально подключить Маркет к своей системе: какие методы нужны, какие данные хранить, где ломается интеграция, как не упереться в лимиты и не сломать карточки, цены и заказы.

Что можно автоматизировать через Яндекс Маркет API

Официальный Partner API помогает управлять ассортиментом и ценами, обрабатывать заказы, обновлять остатки и подключать уведомления о событиях. Базовые разделы: товары, остатки, цены, акции, заказы, FBS-отгрузки, ярлыки, поставки, возвраты, отчёты, отзывы, вопросы, буст продаж, индекс качества, чаты и склады.

КонтурЧто автоматизируемОсновные методы
Категориидерево категорий, листовая категорияPOST /v2/categories/tree
Характеристикиобязательные и фильтруемые параметрыPOST /v2/category/{categoryId}/parameters
Товарысоздание и обновление товаровPOST /v2/businesses/{businessId}/offer-mappings/update
Карточкикатегорийные характеристикиPOST /v2/businesses/{businessId}/offer-cards/update
Цены для кабинетацена во всех магазинахPOST /v2/businesses/{businessId}/offer-prices/updates
Цены магазинаотдельная цена в магазинеPOST /v2/campaigns/{campaignId}/offer-prices/updates
Остаткиостатки FBS, Express, DBSPUT /v2/campaigns/{campaignId}/offers/stocks
Условия продажидоступность, НДС, квантыPOST /v2/campaigns/{campaignId}/offers/update
Заказысписок и статусы заказовPOST /v1/businesses/{businessId}/orders
FBSкоробки, ярлыки, акты, подтверждение отгрузкиметоды FBS, labels, shipments
Уведомлениясобытия по заказам, возвратам, чатам, отзывамAPI-уведомления (webhook)

Главная схема интеграции

Конвейер с ключевой развилкой на ценах (карантин) и единым контуром логов/алертов.

  1. Источник: ERP / CRM / PIM / Google Sheets / 1С → нормализация данных.
  2. Справочники Маркета: /v2/categories/tree, /v2/category/{categoryId}/parameters.
  3. Валидация товара по справочникам и обязательным характеристикам.
  4. Каталог: offer-mappings/update (товар) + offer-cards/update (категорийные характеристики).
  5. Проверка карточки после обновления.
  6. Цены: бизнес-цены и/или цены магазина → проверка карантина.
  7. Остатки: PUT /v2/campaigns/{campaignId}/offers/stocks.
  8. Заказы и FBS → подтверждение отгрузки.
  9. API-уведомления вместо постоянного polling.
  10. Логи, алерты, отчёты по всем шагам.

Ключевая модель данных: businessId, campaignId, offerId

В Яндекс Маркете нельзя строить интеграцию, не разделяя кабинет, магазин и SKU.

ПолеЧто означаетГде используется
businessIdкабинет продавцакаталог, карточки, бизнес-цены, отзывы, вопросы, чаты
campaignIdмагазин/кампания размещенияостатки, заказы, условия продажи, цены магазина, FBS
offerIdваш SKUтовар, цена, остаток, заказные позиции
marketCategoryIdлистовая категория Маркетасоздание и обновление товаров
marketSkuSKU карточки на Маркетепривязка к существующей карточке

Минимальная таблица для интеграции:

{
  "businessId": 123456,
  "campaignId": 7891011,
  "offerId": "NODB-TSHIRT-BLACK-M",
  "vendorCode": "TSHIRT-BLACK-M",
  "marketCategoryId": 7811901,
  "marketSku": null,
  "warehouseId": 111222,
  "lastCatalogSyncAt": "2026-06-13T10:00:00+03:00",
  "lastPriceSyncAt": null,
  "lastStockSyncAt": null,
  "status": "draft"
}
Ошибка, которая ломает интеграции: использовать campaignId там, где нужен businessId, или наоборот. Особенно часто на ценах: цена «для всех магазинов» идёт через businessId, а цена конкретного магазина — через campaignId.

Авторизация: используйте API-Key, не OAuth

Для новых интеграций используйте API-Key. Он привязан к кабинету, действует для всех магазинов кабинета и передаётся в заголовке Api-Key. OAuth в документации помечен как устаревший способ.

Api-Key: {{$credentials.yandexMarket.apiKey}}
Content-Type: application/json

Правила безопасности:

  • не вставляйте API-Key в workflow JSON;
  • не храните ключ в Google Sheets;
  • не публикуйте ключ в скриншотах;
  • используйте credentials / env-переменные;
  • выдавайте токену только нужные доступы, а не всегда all-methods.

Лимиты: почему в Яндекс Маркете важна ошибка 420

Самое важное для production — не больше 4 параллельных запросов. При превышении Маркет возвращает 420 Enhance Your Calm. Дополнительно: в ответах приходят заголовки ресурсных лимитов; успешные 2xx и клиентские 4xx уменьшают лимит; ошибки 5xx лимит не уменьшают; максимальный размер тела запроса — 512 КБ; большие списки нужно передавать батчами и читать через пагинацию.

Практическое правило для n8n/backend:

Concurrency: 1–3 запроса одновременно
Batch size для товаров: 50–100
Batch size для цен: до 500, но лучше 100–300
Batch size для остатков: до 2000, но лучше 500–1000
Retry: только для 420/5xx, не для validation error

Простая логика обработки статусов

200Parse result — успех.
400 / 422Validation error — не повторять, лог по offerId.
420Pause + retry queue (поставить в очередь и повторить).
500 / 502 / 503Backoff + retry.

Автоматизация товаров

Шаг 1. Получить дерево категорий

POST https://api.partner.market.yandex.ru/v2/categories/tree
{
  "language": "RU"
}

Что сохранить (нужна листовая категория — без дочерних):

{
  "categoryId": 7811901,
  "name": "Футболки",
  "isLeaf": true,
  "parentId": 7811888
}

Шаг 2. Получить характеристики категории

POST https://api.partner.market.yandex.ru/v2/category/{categoryId}/parameters?businessId={businessId}

Что важно в ответе: id, name, type, required, filtering, distinctive, multivalue, allowCustomValues, values. Пример нормализованного справочника:

{
  "categoryId": 7811901,
  "parameters": [
    {
      "id": 14871214,
      "name": "Цвет товара",
      "type": "ENUM",
      "required": true,
      "filtering": true,
      "distinctive": true,
      "allowCustomValues": false,
      "values": [
        { "id": 61577, "value": "черный" },
        { "id": 61576, "value": "белый" }
      ]
    }
  ]
}
Практический вывод: характеристики надо кэшировать, но регулярно обновлять. Если Маркет изменит обязательные поля, старый payload начнёт падать.

Шаг 3. Создать или обновить товар

POST https://api.partner.market.yandex.ru/v2/businesses/{businessId}/offer-mappings/update

Метод добавляет товары в каталог и передаёт листовую категорию, категорийные и основные характеристики, цены в кабинете. Для нового товара обязательны:

offerId
name
marketCategoryId
pictures
vendor
description

Пример payload:

{
  "offerMappings": [
    {
      "offer": {
        "offerId": "NODB-TSHIRT-BLACK-M",
        "name": "Футболка мужская хлопковая черная M",
        "marketCategoryId": 7811901,
        "pictures": [
          "https://example.com/images/tshirt-black-main.jpg",
          "https://example.com/images/tshirt-black-back.jpg"
        ],
        "vendor": "Nodbot",
        "description": "Мужская футболка из хлопка. Подходит для повседневной носки, спорта и базового гардероба.",
        "barcodes": [
          "4601234567890"
        ],
        "manufacturerCountries": [
          "Россия"
        ],
        "weightDimensions": {
          "length": 25,
          "width": 20,
          "height": 3,
          "weight": 0.25
        },
        "vendorCode": "TSHIRT-BLACK-M",
        "parameterValues": [
          {
            "parameterId": 14871214,
            "valueId": 61577,
            "value": "черный"
          },
          {
            "parameterId": 14871335,
            "value": "M"
          }
        ],
        "basicPrice": {
          "value": 1490,
          "currencyId": "RUR"
        },
        "purchasePrice": {
          "value": 650,
          "currencyId": "RUR"
        }
      }
    }
  ]
}

Что проверить до отправки

  • offerId уникальный и не использовался для другого товара;
  • offerId без пробелов в начале/конце;
  • категория листовая, обязательные характеристики заполнены;
  • ENUM-значения переданы с правильным valueId;
  • фото доступны по HTTPS;
  • название не длиннее 256 символов, описание — не длиннее 6000;
  • нет рекламных слов и ссылок в описании;
  • габариты, вес и штрихкод заполнены и валидны;
  • batch не превышает 512 КБ.

Шаг 4. Обновить категорийные характеристики

POST https://api.partner.market.yandex.ru/v2/businesses/{businessId}/offer-cards/update
{
  "offersContent": [
    {
      "offerId": "NODB-TSHIRT-BLACK-M",
      "categoryId": 7811901,
      "parameterValues": [
        {
          "parameterId": 14871214,
          "valueId": 61577,
          "value": "черный"
        },
        {
          "parameterId": 14871335,
          "value": "M"
        },
        {
          "parameterId": 200,
          "value": "Футболка Nodbot базовая"
        }
      ]
    }
  ]
}

Что важно: основные параметры товара меняются через offer-mappings/update, категорийные характеристики — через offer-cards/update; обновление в каталоге занимает до нескольких минут; 200 OK не всегда означает, что каждое значение принято — проверяйте карточку после обновления.

Объединение вариантов товара

Для объединения вариантов используется характеристика id: 200 — «Название группы вариантов».

Группа: Футболка Nodbot базовая
Вариант 1: черный / M
Вариант 2: черный / L
Вариант 3: белый / M

Для корректного объединения: товары в одной категории; группа вариантов совпадает; отличительные характеристики различаются; неотличительные — одинаковы.

Автоматизация цен

У Маркета два уровня цен — важно не перепутать businessId и campaignId.

УровеньМетодКогда использовать
Цена для всех магазинов кабинетаPOST /v2/businesses/{businessId}/offer-prices/updatesодна цена для всех магазинов
Цена конкретного магазинаPOST /v2/campaigns/{campaignId}/offer-prices/updatesвключены отдельные цены по магазинам

Цена для всех магазинов

{
  "offers": [
    {
      "offerId": "NODB-TSHIRT-BLACK-M",
      "price": {
        "value": 1490,
        "currencyId": "RUR",
        "discountBase": 1990
      }
    }
  ]
}

Цена для отдельного магазина

{
  "offers": [
    {
      "offerId": "NODB-TSHIRT-BLACK-M",
      "price": {
        "value": 1450,
        "currencyId": "RUR",
        "discountBase": 1990
      }
    }
  ]
}

Защита от ценовых ошибок

Перед отправкой цены в API нужен guardrail:

const minMargin = 0.18;
const purchasePrice = Number($json.purchasePrice);
const newPrice = Number($json.price);
const oldPrice = Number($json.oldPrice || 0);

if (!newPrice || newPrice <= 0) {
  throw new Error('Некорректная цена');
}

const margin = (newPrice - purchasePrice) / newPrice;

if (margin < minMargin) {
  throw new Error(`Маржа ниже минимума: ${(margin * 100).toFixed(1)}%`);
}

if (oldPrice && newPrice < oldPrice * 0.5) {
  throw new Error('Цена снизилась больше чем на 50%, нужна ручная проверка');
}

return [{ json: { ...$json, margin } }];

Карантин цен

Если цена изменилась слишком резко, товар может попасть в карантин. Правильный workflow:

1. Рассчитать экономику и тарифы.
2. Отправить новую цену.
3. Проверить price quarantine.
4. Если цена ошибочная — исправить.
5. Если цена корректная — подтвердить карантин вручную или по строгому правилу.
Не подтверждайте карантин автоматически без проверки маржинальности и истории цены.

Автоматизация остатков

PUT https://api.partner.market.yandex.ru/v2/campaigns/{campaignId}/offers/stocks

Доступно для FBS, Express и DBS. Пример:

{
  "skus": [
    {
      "sku": "NODB-TSHIRT-BLACK-M",
      "items": [
        {
          "count": 42,
          "updatedAt": "2026-06-13T12:00:00+03:00"
        }
      ]
    }
  ]
}

Практические правила

  • SKU должен совпадать символ в символ;
  • 557722 и 0557722 — разные SKU;
  • остаток 0 допустим и означает обнуление;
  • count не должен быть отрицательным;
  • при группах складов передавайте остаток по правилам группы;
  • не отправляйте остатки одновременно из нескольких систем;
  • храните lastStockSyncAt и источник остатка.

Автоматизация условий продажи

POST https://api.partner.market.yandex.ru/v2/campaigns/{campaignId}/offers/update

Можно менять доступность товара в магазине, НДС и продажу квантами. Пример:

{
  "offers": [
    {
      "offerId": "NODB-TSHIRT-BLACK-M",
      "available": true,
      "vat": "VAT_20_120",
      "quantum": {
        "minQuantity": 1,
        "stepQuantity": 1
      }
    }
  ]
}

Автоматизация заказов и FBS

Для новых интеграций ориентируйтесь на актуальный метод:

POST /v1/businesses/{businessId}/orders
Старые методы /v2/campaigns/{campaignId}/orders помечены устаревшими. Старый метод списка заказов должен стать нестабильным с 18 января 2027 и отключиться 12 апреля 2027.

Что хранить по заказу

{
  "orderId": 123456789,
  "status": "PROCESSING",
  "substatus": "STARTED",
  "campaignId": 7891011,
  "businessId": 123456,
  "items": [
    {
      "offerId": "NODB-TSHIRT-BLACK-M",
      "count": 1,
      "price": 1490,
      "vat": "VAT_20_120",
      "hasCis": false
    }
  ],
  "shipmentId": 555444333,
  "createdAt": "2026-06-13T12:10:00+03:00"
}

FBS workflow

  1. Новый заказ → получить детали заказа.
  2. Проверить остаток и маркировку.
  3. Передать коробки / грузоместа.
  4. Передать коды маркировки, если нужны.
  5. Получить ярлыки и сформировать лист подбора.
  6. Подготовить акт.
  7. Подтвердить отгрузку.
  8. Обновить статус в ERP/CRM.

Что не стоит делать полностью автоматически: отменять заказы; переносить заказы; подтверждать спорные возвраты; отправлять неверные коды маркировки; подтверждать отгрузку до физической готовности заказа.

API-уведомления вместо постоянного polling

Маркет может отправлять POST-запросы на ваш URL при событиях: создание/изменение/отмена заказа и его статуса, заявка на отмену, возврат или невыкуп, новый отзыв, новый чат и сообщение в чате, начало/завершение спора.

Требования

  • HTTPS с SSL-сертификатом от официального центра сертификации (self-signed не подходит);
  • проверка IP-диапазонов Маркета;
  • ответ на PING за 1 секунду;
  • ответ на обычное уведомление за 10 секунд.

Минимальный ответ на проверочный PING:

{
  "version": "1.0.0",
  "name": "nodbot-yandex-market-webhook",
  "time": "2026-06-13T12:00:00.000000000Z"
}

n8n workflow: создание товара + цена + остаток

Минимальный импортируемый workflow. Он не заменяет production-интеграцию, но показывает правильную структуру: входной webhook → валидация → создание товара → цена → остаток.

Перед импортом создайте credentials или env-переменные:

YANDEX_MARKET_API_KEY
YANDEX_MARKET_BUSINESS_ID
YANDEX_MARKET_CAMPAIGN_ID
{
  "name": "Yandex Market API - Product Price Stock Sync",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "yandex-market-product-sync",
        "responseMode": "responseNode"
      },
      "id": "WebhookTrigger",
      "name": "Webhook - New Product",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "const item = $json;\n\nconst required = ['offerId', 'name', 'marketCategoryId', 'pictures', 'vendor', 'description', 'price', 'stock'];\nfor (const field of required) {\n  if (item[field] === undefined || item[field] === null || item[field] === '') {\n    throw new Error(`Missing required field: ${field}`);\n  }\n}\n\nif (!Array.isArray(item.pictures) || item.pictures.length === 0) {\n  throw new Error('pictures must be a non-empty array');\n}\n\nif (String(item.offerId).trim() !== String(item.offerId)) {\n  throw new Error('offerId must not contain leading/trailing spaces');\n}\n\nif (String(item.name).length > 256) {\n  throw new Error('name is too long');\n}\n\nif (String(item.description).length > 6000) {\n  throw new Error('description is too long');\n}\n\nif (Number(item.price) <= 0) {\n  throw new Error('price must be positive');\n}\n\nif (Number(item.stock) < 0) {\n  throw new Error('stock must not be negative');\n}\n\nreturn [{ json: item }];"
      },
      "id": "ValidateInput",
      "name": "Validate input",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        240,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.partner.market.yandex.ru/v2/businesses/{{$env.YANDEX_MARKET_BUSINESS_ID}}/offer-mappings/update",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Api-Key",
              "value": "={{$env.YANDEX_MARKET_API_KEY}}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"offerMappings\": [\n    {\n      \"offer\": {\n        \"offerId\": $json.offerId,\n        \"name\": $json.name,\n        \"marketCategoryId\": $json.marketCategoryId,\n        \"pictures\": $json.pictures,\n        \"vendor\": $json.vendor,\n        \"description\": $json.description,\n        \"barcodes\": $json.barcodes || [],\n        \"manufacturerCountries\": $json.manufacturerCountries || [],\n        \"weightDimensions\": $json.weightDimensions,\n        \"vendorCode\": $json.vendorCode || $json.offerId,\n        \"parameterValues\": $json.parameterValues || [],\n        \"basicPrice\": {\n          \"value\": Number($json.price),\n          \"currencyId\": \"RUR\"\n        }\n      }\n    }\n  ]\n}"
      },
      "id": "CreateOrUpdateProduct",
      "name": "Create/update product",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        500,
        0
      ]
    },
    {
      "parameters": {
        "amount": 3,
        "unit": "seconds"
      },
      "id": "WaitAfterProductUpdate",
      "name": "Wait after product update",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        740,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.partner.market.yandex.ru/v2/businesses/{{$env.YANDEX_MARKET_BUSINESS_ID}}/offer-prices/updates",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Api-Key",
              "value": "={{$env.YANDEX_MARKET_API_KEY}}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"offers\": [\n    {\n      \"offerId\": $node['Validate input'].json.offerId,\n      \"price\": {\n        \"value\": Number($node['Validate input'].json.price),\n        \"currencyId\": \"RUR\",\n        \"discountBase\": Number($node['Validate input'].json.discountBase || 0) || undefined\n      }\n    }\n  ]\n}"
      },
      "id": "UpdatePrice",
      "name": "Update business price",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        980,
        0
      ]
    },
    {
      "parameters": {
        "method": "PUT",
        "url": "=https://api.partner.market.yandex.ru/v2/campaigns/{{$env.YANDEX_MARKET_CAMPAIGN_ID}}/offers/stocks",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Api-Key",
              "value": "={{$env.YANDEX_MARKET_API_KEY}}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"skus\": [\n    {\n      \"sku\": $node['Validate input'].json.offerId,\n      \"items\": [\n        {\n          \"count\": Number($node['Validate input'].json.stock),\n          \"updatedAt\": new Date().toISOString()\n        }\n      ]\n    }\n  ]\n}"
      },
      "id": "UpdateStocks",
      "name": "Update stock",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1220,
        0
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={\n  \"ok\": true,\n  \"offerId\": $node['Validate input'].json.offerId,\n  \"message\": \"Product, price and stock sync was sent to Yandex Market API\"\n}"
      },
      "id": "Respond",
      "name": "Respond",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1460,
        0
      ]
    }
  ],
  "connections": {
    "Webhook - New Product": {
      "main": [
        [
          {
            "node": "Validate input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate input": {
      "main": [
        [
          {
            "node": "Create/update product",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create/update product": {
      "main": [
        [
          {
            "node": "Wait after product update",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait after product update": {
      "main": [
        [
          {
            "node": "Update business price",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update business price": {
      "main": [
        [
          {
            "node": "Update stock",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update stock": {
      "main": [
        [
          {
            "node": "Respond",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}

Code node: батчинг под лимит 512 КБ

Используйте перед HTTP Request, если отправляете много товаров:

const maxBytes = 480 * 1024; // запас до лимита 512 КБ
const offers = $input.all().map(i => i.json);
const batches = [];
let current = [];

function payloadSize(items) {
  return Buffer.byteLength(JSON.stringify({ offerMappings: items }), 'utf8');
}

for (const offer of offers) {
  const next = [...current, offer];
  if (payloadSize(next) > maxBytes || next.length > 100) {
    batches.push(current);
    current = [offer];
  } else {
    current = next;
  }
}

if (current.length) batches.push(current);

return batches.map((batch, index) => ({
  json: {
    batchIndex: index + 1,
    batchSize: batch.length,
    offerMappings: batch
  }
}));

Production-чек-лист перед запуском

  • API-Key создан и хранится в credentials/env.
  • Проверен apiAvailability для магазина.
  • businessId и campaignId сохранены отдельно.
  • Настроен rate limiter: не более 4 параллельных запросов.
  • Есть обработка 420 Enhance Your Calm.
  • Retry только для 420 и 5xx; 400/422 уходят в лог ошибок.
  • Тело запроса не превышает 512 КБ.
  • Категории и характеристики обновляются по расписанию.
  • Есть локальная валидация обязательных характеристик и защита от переиспользования offerId.
  • Есть price guardrail и проверка карантина.
  • Остатки обновляет только одна мастер-система.
  • Заказы синхронизируются через API-уведомления + контрольный polling.
  • Отмена/перенос заказов — только по правилу или вручную.
  • Все ошибки пишутся с offerId, endpoint, body, response, timestamp.

Типовые ошибки интеграции

ОшибкаПричинаКак исправить
420 Enhance Your Calmбольше 4 параллельных запросов или превышен ресурсный лимиточередь, backoff, уменьшить concurrency
UNKNOWN_CATEGORYневерная категориябрать категорию из /v2/categories/tree
EMPTY_MARKET_CATEGORYне передана категорияпередать листовой marketCategoryId
UNKNOWN_PARAMETERхарактеристика не относится к категорииобновить параметры через /v2/category/{categoryId}/parameters
INVALID_UNIT_IDневерная единица измерениябрать unitId из справочника параметра
Цена в карантинерезкое изменение ценыпроверить цену, исправить или подтвердить
Товар не обновилсяданные обновляются не мгновеннождать несколько минут и проверять повторно
Остаток не совпадаетSKU отличается символами или ведущим нулёмхранить SKU строкой, не числом
API отключёнapiAvailability не AVAILABLEвключить API в кабинете или проверить договор

Какой minimum viable backend нужен

Для маленького каталога можно начать с n8n + Google Sheets. Для production лучше иметь базу. Минимальные таблицы:

CREATE TABLE ym_products (
  offer_id TEXT PRIMARY KEY,
  business_id BIGINT NOT NULL,
  campaign_id BIGINT,
  market_category_id BIGINT,
  market_sku BIGINT,
  title TEXT,
  status TEXT,
  last_catalog_sync_at TIMESTAMP,
  last_error TEXT
);

CREATE TABLE ym_prices (
  offer_id TEXT PRIMARY KEY,
  price NUMERIC,
  discount_base NUMERIC,
  purchase_price NUMERIC,
  margin NUMERIC,
  quarantine_status TEXT,
  updated_at TIMESTAMP
);

CREATE TABLE ym_stocks (
  offer_id TEXT,
  campaign_id BIGINT,
  stock INTEGER,
  source TEXT,
  updated_at TIMESTAMP,
  PRIMARY KEY (offer_id, campaign_id)
);

CREATE TABLE ym_api_logs (
  id BIGSERIAL PRIMARY KEY,
  endpoint TEXT,
  offer_id TEXT,
  status_code INTEGER,
  request_body JSONB,
  response_body JSONB,
  created_at TIMESTAMP DEFAULT now()
);

FAQ

Можно ли через API полностью автоматизировать Яндекс Маркет?

Да, большую часть рутины можно автоматизировать: товары, цены, остатки, заказы, FBS-документы, отзывы, вопросы, чаты и отчёты. Но цены, отмены, возвраты и спорные отзывы лучше оставлять с ручным контролем или строгими правилами.

Чем отличается offer-mappings/update от offer-cards/update?

offer-mappings/update добавляет и обновляет товар в каталоге: название, описание, фото, категория, базовые характеристики и часть цен. offer-cards/update редактирует характеристики, специфичные для категории.

Почему ошибка 420, а не 429?

Яндекс Маркет использует HTTP 420 Enhance Your Calm при превышении лимитов. Для интеграции это сигнал поставить запрос в очередь и повторить позже.

Какой размер батча лучше использовать?

Для товаров — 50–100 позиций. Для цен — 100–300. Для остатков — 500–1000. Формальные лимиты могут быть выше, но маленькие батчи проще логировать, ретраить и валидировать.

Можно ли использовать n8n?

Да, для MVP и средних объёмов. Но нужен rate limiter, батчинг, лог ошибок, credentials и контроль повторов. Для больших объёмов лучше вынести очередь и логику лимитов в backend.

Источники

  1. API Яндекс Маркета для продавцов — partner-api/doc
  2. Авторизация API-Key — concepts/authorization
  3. Ограничения запросов — concepts/limits
  4. Дерево категорий — getCategoriesTree
  5. Характеристики категории — getCategoryContentParameters
  6. Добавление товаров — updateOfferMappings
  7. Категорийные характеристики — updateOfferContent
  8. Цены для всех магазинов — updateBusinessPrices
  9. Цены в конкретном магазине — updatePrices
  10. Цены и карантин — assortment-change-prices
  11. Передача остатков — updateStocks
  12. Заказы — getOrders
  13. API-уведомления — push-notifications

Читать также

Интеграция Яндекс Маркета

Нужно связать Яндекс Маркет с 1С, МойСклад, CRM или n8n?

Соберём интеграцию через Partner API: справочники и характеристики, каталог, цены с проверкой карантина, остатки, FBS-заказы и API-уведомления — с rate-limit под лимит 420, батчингом под 512 КБ и журналом ошибок по offerId.

Другие статьи