📦 Products API - Управление каталогом товаров

Самая важная категория OZON Seller API для полного управления каталогом товаров от создания до архивирования.

🎯 Назначение API

Products API предоставляет полный набор инструментов для:

  • Создание и импорт товаров в каталог OZON
  • Управление атрибутами и характеристиками товаров
  • Контроль цен и остатков на складах
  • Работа с медиа (фотографии, видео, 360° обзоры)
  • Управление статусами публикации и модерации
  • Массовые операции для больших каталогов
  • Получение аналитики по товарам и продажам

📋 Список методов (34 endpoint’а)

🏗️ Создание и импорт товаров

| Метод | Endpoint | Версия | Назначение | |——-|———-|———|————| | create | /v2/product/import | v2 | Создание/импорт товаров | | importBySKU | /v1/product/import-by-sku | v1 | Импорт по артикулам продавца |

📊 Получение информации о товарах

| Метод | Endpoint | Версия | Назначение | |——-|———-|———|————| | getList | /v2/product/list | v2 | Список товаров с фильтрами | | getListV3 | /v3/product/list | v3 | Расширенный список товаров | | getInfo | /v2/product/info | v2 | Детальная информация о товарах | | getInfoV3 | /v3/product/info | v3 | Расширенная информация (v3) | | getDescription | /v1/product/info/description | v1 | Описания товаров | | getSubscription | /v1/product/info/subscription | v1 | Подписочные товары |

💰 Управление ценами и остатками

| Метод | Endpoint | Версия | Назначение | |——-|———-|———|————| | updatePrices | /v1/product/import/prices | v1 | Обновление цен | | updateStocks | /v1/product/import/stocks | v1 | Обновление остатков | | getPrices | /v4/product/info/prices | v4 | Получение цен товаров | | getStocks | /v3/product/info/stocks | v3 | Получение остатков |


🚀 Быстрый старт

Инициализация клиента

import { OzonSellerAPI } from 'daytona-ozon-seller-api';

const client = new OzonSellerAPI({
  clientId: 'your-client-id',
  apiKey: 'your-api-key'
});

Базовые операции

1. Получение списка товаров

try {
  const result = await client.products.getList({
    filter: {
      offer_id: ['SKU-001', 'SKU-002'], // Ваши артикулы
      product_id: [], // Или OZON Product ID
      visibility: 'ALL'
    },
    limit: 100,
    last_id: '', // Для пагинации
    sort_dir: 'ASC'
  });

  console.log(`📦 Найдено товаров: ${result.result?.items?.length || 0}`);
  console.log(`🔄 Есть ещё: ${result.result?.has_next ? 'Да' : 'Нет'}`);
  
  result.result?.items?.forEach(item => {
    console.log(`- ${item.offer_id}: ${item.name} (${item.status?.state})`);
  });
} catch (error) {
  console.error('❌ Ошибка получения списка товаров:', error);
}

2. Создание нового товара

try {
  const newProduct = await client.products.create({
    items: [{
      name: 'Смартфон Apple iPhone 15 Pro 256GB',
      offer_id: 'IPHONE-15-PRO-256', // Ваш уникальный артикул
      category_id: 17033631, // ID категории в OZON
      price: '89990',
      old_price: '99990',
      premium_price: '87990',
      vat: '0.2', // НДС 20%
      height: 147,
      width: 71,
      depth: 8,
      weight: 187,
      images: [{
        file_name: 'main.jpg',
        default: true
      }],
      attributes: [{
        complex_id: 0,
        id: 85, // ID атрибута "Бренд"
        values: [{
          dictionary_value_id: 971082156,
          value: 'Apple'
        }]
      }]
    }]
  });

  console.log('✅ Товар создан успешно');
  console.log(`📝 Task ID: ${newProduct.result?.task_id}`);
} catch (error) {
  console.error('❌ Ошибка создания товара:', error);
}

3. Обновление цен

try {
  const priceUpdate = await client.products.updatePrices({
    prices: [{
      offer_id: 'SKU-001',
      price: '15990',
      old_price: '19990',
      premium_price: '14990',
      currency_code: 'RUB'
    }, {
      offer_id: 'SKU-002', 
      price: '7500',
      currency_code: 'RUB'
    }]
  });

  console.log('💰 Цены обновлены');
  console.log(`📊 Результат: ${priceUpdate.result?.length} товаров`);
} catch (error) {
  console.error('❌ Ошибка обновления цен:', error);
}

4. Обновление остатков

try {
  const stockUpdate = await client.products.updateStocks({
    stocks: [{
      offer_id: 'SKU-001',
      stock: 50, // Количество на складе
      warehouse_id: 123456789
    }, {
      offer_id: 'SKU-002',
      stock: 0, // Товар закончился
      warehouse_id: 123456789
    }]
  });

  console.log('📦 Остатки обновлены');
  console.log(`📊 Результат: ${stockUpdate.result?.length} товаров`);
} catch (error) {
  console.error('❌ Ошибка обновления остатков:', error);
}

🎯 Детальные сценарии использования

📊 Сценарий 1: Полная синхронизация каталога

Задача: Синхронизировать внешний каталог с OZON (цены, остатки, описания)

async function syncCatalogWithOzon() {
  console.log('🔄 Начинаем синхронизацию каталога...');
  
  try {
    // 1. Получаем список всех товаров в OZON
    const existingProducts = new Map();
    let lastId = '';
    
    do {
      const batch = await client.products.getList({
        filter: { visibility: 'ALL' },
        limit: 1000,
        last_id: lastId
      });
      
      batch.result?.items?.forEach(item => {
        if (item.offer_id) {
          existingProducts.set(item.offer_id, item);
        }
      });
      
      lastId = batch.result?.last_id || '';
      console.log(`📥 Загружено: ${existingProducts.size} товаров`);
      
    } while (lastId);
    
    // 2. Загружаем данные из внешней системы
    const externalProducts = await getProductsFromExternalSystem();
    console.log(`🗃️ Внешний каталог: ${externalProducts.length} товаров`);
    
    // 3. Определяем изменения
    const toUpdate = [];
    const toCreate = [];
    
    for (const extProduct of externalProducts) {
      const existing = existingProducts.get(extProduct.sku);
      
      if (existing) {
        // Товар существует, проверяем изменения
        if (needsUpdate(existing, extProduct)) {
          toUpdate.push(extProduct);
        }
      } else {
        // Новый товар
        toCreate.push(extProduct);
      }
    }
    
    console.log(`➕ Создать: ${toCreate.length} товаров`);
    console.log(`🔄 Обновить: ${toUpdate.length} товаров`);
    
    // 4. Создаём новые товары
    if (toCreate.length > 0) {
      const createResult = await client.products.create({
        items: toCreate.map(product => mapToOzonProduct(product))
      });
      console.log(`✅ Создание: Task ID ${createResult.result?.task_id}`);
    }
    
    // 5. Обновляем цены
    if (toUpdate.length > 0) {
      const prices = toUpdate.map(product => ({
        offer_id: product.sku,
        price: product.price.toString(),
        old_price: product.oldPrice?.toString(),
        currency_code: 'RUB'
      }));
      
      await client.products.updatePrices({ prices });
      console.log(`💰 Цены обновлены: ${prices.length} товаров`);
    }
    
    // 6. Обновляем остатки
    const stocks = externalProducts.map(product => ({
      offer_id: product.sku,
      stock: product.quantity,
      warehouse_id: DEFAULT_WAREHOUSE_ID
    }));
    
    await client.products.updateStocks({ stocks });
    console.log(`📦 Остатки обновлены: ${stocks.length} товаров`);
    
    console.log('✅ Синхронизация завершена успешно');
    
  } catch (error) {
    console.error('❌ Ошибка синхронизации:', error);
    throw error;
  }
}

// Вспомогательные функции
function needsUpdate(existing: any, external: any): boolean {
  return (
    existing.price !== external.price.toString() ||
    existing.name !== external.name ||
    existing.primary_image !== external.image
  );
}

function mapToOzonProduct(product: any) {
  return {
    name: product.name,
    offer_id: product.sku,
    category_id: product.categoryId,
    price: product.price.toString(),
    old_price: product.oldPrice?.toString(),
    images: product.images?.map((img: string, index: number) => ({
      file_name: `image_${index}.jpg`,
      default: index === 0
    })) || [],
    attributes: mapAttributes(product.attributes)
  };
}

🔍 Сценарий 2: Мониторинг статусов модерации

Задача: Отслеживать статусы товаров на модерации и получать уведомления

async function monitorModerationStatus() {
  console.log('🔍 Мониторинг статусов модерации...');
  
  try {
    // Получаем товары со статусом модерации
    const moderationItems = await client.products.getList({
      filter: {
        visibility: 'ALL'
      },
      limit: 1000
    });
    
    const statusCounts = new Map();
    const problemItems = [];
    
    moderationItems.result?.items?.forEach(item => {
      const state = item.status?.state;
      const moderationState = item.status?.moderation_status;
      
      // Подсчитываем статусы
      statusCounts.set(state, (statusCounts.get(state) || 0) + 1);
      
      // Ищем проблемные товары
      if (state === 'FAILED_MODERATION' || 
          state === 'FAILED_VALIDATION' ||
          moderationState === 'DECLINED') {
        problemItems.push({
          offer_id: item.offer_id,
          name: item.name,
          state: state,
          moderation_status: moderationState,
          errors: item.status?.item_errors || []
        });
      }
    });
    
    // Выводим статистику
    console.log('\n📊 Статистика по статусам:');
    for (const [status, count] of statusCounts) {
      console.log(`  ${getStatusEmoji(status)} ${status}: ${count} товаров`);
    }
    
    // Обрабатываем проблемные товары
    if (problemItems.length > 0) {
      console.log(`\n⚠️ Найдено ${problemItems.length} проблемных товаров:`);
      
      for (const item of problemItems) {
        console.log(`\n❌ ${item.offer_id}: ${item.name}`);
        console.log(`   Статус: ${item.state}`);
        
        if (item.errors?.length > 0) {
          console.log('   Ошибки:');
          item.errors.forEach((error: any) => {
            console.log(`   - ${error.code}: ${error.message}`);
          });
        }
      }
      
      // Отправляем уведомления
      await sendModerationAlerts(problemItems);
    }
    
  } catch (error) {
    console.error('❌ Ошибка мониторинга:', error);
  }
}

function getStatusEmoji(status: string): string {
  const statusEmojis: { [key: string]: string } = {
    'PROCESSED': '',
    'PROCESSING': '🔄',
    'FAILED_MODERATION': '',
    'FAILED_VALIDATION': '⚠️',
    'ARCHIVED': '📦'
  };
  return statusEmojis[status] || '';
}

📈 Сценарий 3: Массовое управление ценами с учётом конкурентов

async function updatePricesWithCompetitorAnalysis() {
  console.log('💰 Анализ цен конкурентов и обновление...');
  
  try {
    // Получаем текущие цены наших товаров
    const ourProducts = await client.products.getList({
      filter: { visibility: 'VISIBLE' },
      limit: 1000
    });
    
    const priceUpdates = [];
    
    for (const item of ourProducts.result?.items || []) {
      if (!item.offer_id) continue;
      
      // Получаем детальную информацию о товаре
      const productInfo = await client.products.getInfo({
        offer_id: item.offer_id
      });
      
      const currentPrice = parseFloat(productInfo.result?.price || '0');
      const competitorPrice = await getCompetitorPrice(item.offer_id);
      
      if (competitorPrice > 0) {
        // Стратегия ценообразования: на 5% ниже конкурента
        const newPrice = Math.round(competitorPrice * 0.95);
        const priceChange = ((newPrice - currentPrice) / currentPrice) * 100;
        
        // Обновляем только если изменение значительное (>2%)
        if (Math.abs(priceChange) > 2) {
          priceUpdates.push({
            offer_id: item.offer_id,
            price: newPrice.toString(),
            old_price: currentPrice.toString(),
            currency_code: 'RUB'
          });
          
          console.log(`📊 ${item.offer_id}: ${currentPrice}₽ → ${newPrice}₽ (${priceChange > 0 ? '+' : ''}${priceChange.toFixed(1)}%)`);
        }
      }
    }
    
    // Батчевое обновление цен
    if (priceUpdates.length > 0) {
      const batchSize = 100;
      
      for (let i = 0; i < priceUpdates.length; i += batchSize) {
        const batch = priceUpdates.slice(i, i + batchSize);
        
        await client.products.updatePrices({
          prices: batch
        });
        
        console.log(`✅ Обновлено цен: ${i + batch.length}/${priceUpdates.length}`);
        
        // Пауза между батчами для избежания лимитов
        await new Promise(resolve => setTimeout(resolve, 1000));
      }
    }
    
    console.log(`💰 Ценообразование завершено: ${priceUpdates.length} товаров обновлено`);
    
  } catch (error) {
    console.error('❌ Ошибка обновления цен:', error);
  }
}

📝 TypeScript типы и интерфейсы

Основные Request интерфейсы

// Создание товаров
interface ProductImportRequest {
  items: ProductImportItem[];
}

interface ProductImportItem {
  name: string;
  offer_id: string;
  category_id: number;
  price?: string;
  old_price?: string;
  premium_price?: string;
  vat?: string;
  height?: number;
  width?: number;
  depth?: number;
  weight?: number;
  images?: ProductImage[];
  attributes?: ProductAttribute[];
  pdf_list?: ProductPdf[];
  complex_attributes?: ProductComplexAttribute[];
}

// Получение списка товаров
interface ProductListRequest {
  filter?: ProductListFilter;
  limit?: number;
  last_id?: string;
  sort_dir?: 'ASC' | 'DESC';
}

interface ProductListFilter {
  offer_id?: string[];
  product_id?: number[];
  visibility?: 'ALL' | 'VISIBLE' | 'INVISIBLE' | 'EMPTY_STOCK' | 
              'NOT_MODERATED' | 'MODERATED' | 'DISABLED' | 'STATE_FAILED_MODERATION' |
              'READY_TO_SUPPLY' | 'VALIDATION_STATE_PENDING' | 'VALIDATION_STATE_FAIL' |
              'VALIDATION_STATE_SUCCESS' | 'TO_SUPPLY' | 'IN_SALE' | 'REMOVED_FROM_SALE' |
              'BANNED' | 'OVERPRICED' | 'CRITICALLY_OVERPRICED' | 'EMPTY_BARCODE' |
              'BARCODE_EXISTS' | 'QUARANTINE' | 'ARCHIVED' | 'OVERPRICED_WITH_STOCK' |
              'PARTIAL_APPROVED' | 'IMAGE_ABSENT' | 'MODERATION_BLOCK';
}

// Обновление цен
interface ProductPricesImportRequest {
  prices: ProductPrice[];
}

interface ProductPrice {
  offer_id?: string;
  product_id?: number;
  price: string;
  old_price?: string;
  premium_price?: string;
  currency_code?: string;
}

// Обновление остатков
interface ProductStocksImportRequest {
  stocks: ProductStock[];
}

interface ProductStock {
  offer_id?: string;
  product_id?: number;
  stock: number;
  warehouse_id: number;
}

### Response интерфейсы

```typescript
// Создание товаров
interface ProductImportResponse {
  result?: {
    task_id?: number;
  };
}

// Список товаров
interface ProductListResponse {
  result?: {
    items?: ProductListItem[];
    total?: number;
    last_id?: string;
    has_next?: boolean;
  };
}

interface ProductListItem {
  product_id?: number;
  offer_id?: string;
  is_fbo_visible?: boolean;
  is_fbs_visible?: boolean;
  archived?: boolean;
  is_discounted?: boolean;
  name?: string;
  primary_image?: string;
  status?: ProductStatus;
  errors?: ProductError[];
  vat?: string;
  visible?: boolean;
  buybox_price?: string;
  created_at?: string;
  updated_at?: string;
}

interface ProductStatus {
  state?: 'NEW' | 'PENDING' | 'FAILED_MODERATION' | 'FAILED_VALIDATION' | 
          'PROCESSED' | 'PROCESSING' | 'REMOVED_FROM_PUBLICATION' | 'ARCHIVED';
  state_failed?: string;
  moderate_status?: string;
  decline_reasons?: string[];
  validation_state?: 'NEW' | 'PENDING' | 'FAIL' | 'SUCCESS';
  state_name?: string;
  state_description?: string;
  is_failed?: boolean;
  is_created?: boolean;
  state_tooltip?: string;
  item_errors?: ProductError[];
  state_updated_at?: string;
}

// Детальная информация о товарах
interface ProductInfoResponse {
  result?: {
    items?: ProductInfoItem[];
  };
}

interface ProductInfoItem {
  id?: number;
  name?: string;
  offer_id?: string;
  barcode?: string;
  category_id?: number;
  created_at?: string;
  images?: ProductImage[];
  marketing_price?: string;
  min_price?: string;
  old_price?: string;
  premium_price?: string;
  price?: string;
  recommended_price?: string;
  sources?: ProductSource[];
  state?: string;
  stocks?: ProductStockInfo;
  errors?: ProductError[];
  vat?: string;
  visible?: boolean;
  visibility_details?: ProductVisibilityDetails;
  price_index?: string;
  images360?: ProductImage360[];
  color_image?: string;
  primary_image?: string;
  status?: ProductStatus;
}

// Цены товаров
interface ProductPricesResponse {
  result?: {
    items?: ProductPriceItem[];
  };
}

interface ProductPriceItem {
  product_id?: number;
  offer_id?: string;
  price?: ProductPriceDetails;
  price_indexes?: ProductPriceIndexes;
  commissions?: ProductCommissions;
  volume_weight?: number;
  currency_code?: string;
  marketing_seller_price?: string;
  min_ozon_price?: string;
  min_price?: string;
}

🚨 Особенности API и ограничения

Лимиты запросов

  • Rate Limits: До 1000 запросов в минуту
  • Batch Size: Максимум 100 товаров в одном запросе создания
  • Pagination: Максимум 1000 товаров в одном запросе списка

Асинхронность операций

// ✅ Правильно - проверка статуса задачи
const createResult = await client.products.create({ items: [...] });
const taskId = createResult.result?.task_id;

if (taskId) {
  // Ждём завершения задачи
  let taskStatus;
  do {
    await new Promise(resolve => setTimeout(resolve, 5000)); // 5 секунд
    taskStatus = await client.products.getImportInfo({ task_id: taskId });
    
    console.log(`🔄 Статус задачи: ${taskStatus.result?.state}`);
  } while (taskStatus.result?.state === 'pending');
  
  console.log(`✅ Задача завершена: ${taskStatus.result?.state}`);
}

Обработка ошибок модерации

// ✅ Правильная обработка ошибок товаров
const products = await client.products.getList({ filter: { visibility: 'ALL' } });

products.result?.items?.forEach(item => {
  if (item.status?.is_failed) {
    console.log(`❌ Товар ${item.offer_id} отклонён:`);
    
    item.status?.item_errors?.forEach(error => {
      console.log(`  - ${error.code}: ${error.message}`);
      
      // Обрабатываем специфичные ошибки
      switch (error.code) {
        case 'PRODUCT_SAME_NAME_AND_CATEGORY':
          console.log('  ⚠️ Решение: Измените название или категорию товара');
          break;
        case 'PRODUCT_INVALID_PRICE':
          console.log('  ⚠️ Решение: Проверьте корректность цены');
          break;
        case 'PRODUCT_ATTRIBUTE_VALUE_MISSING':
          console.log('  ⚠️ Решение: Добавьте обязательные атрибуты');
          break;
      }
    });
  }
});

Особенности работы с атрибутами

// Правильная структура атрибутов
const productAttributes = [
  {
    complex_id: 0, // Базовые атрибуты
    id: 85, // ID атрибута "Бренд"
    values: [{
      dictionary_value_id: 971082156, // ID из справочника OZON
      value: 'Apple'
    }]
  },
  {
    complex_id: 0,
    id: 9048, // Цвет товара
    values: [{
      value: 'Чёрный' // Свободный ввод
    }]
  }
];

💡 Лучшие практики

1. Управление производительностью

// ✅ Батчевая обработка
async function batchUpdatePrices(prices: ProductPrice[]) {
  const batchSize = 100;
  const results = [];
  
  for (let i = 0; i < prices.length; i += batchSize) {
    const batch = prices.slice(i, i + batchSize);
    
    try {
      const result = await client.products.updatePrices({ prices: batch });
      results.push(result);
      
      console.log(`✅ Обработано: ${i + batch.length}/${prices.length}`);
      
      // Пауза между батчами
      if (i + batchSize < prices.length) {
        await new Promise(resolve => setTimeout(resolve, 1000));
      }
    } catch (error) {
      console.error(`❌ Ошибка в батче ${i}-${i + batch.length}:`, error);
    }
  }
  
  return results;
}

2. Надёжность и восстановление

// ✅ Retry логика с экспоненциальной задержкой
async function reliableApiCall<T>(
  operation: () => Promise<T>,
  maxRetries = 3
): Promise<T> {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error: any) {
      if (attempt === maxRetries) throw error;
      
      const isRetryable = error.status >= 500 || error.code === 'NETWORK_ERROR';
      if (!isRetryable) throw error;
      
      const delay = Math.pow(2, attempt) * 1000; // 2s, 4s, 8s
      console.log(`⚠️ Попытка ${attempt} не удалась, повтор через ${delay}мс`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  
  throw new Error('Максимальное количество попыток исчерпано');
}

// Использование
const products = await reliableApiCall(() => 
  client.products.getList({ filter: { visibility: 'VISIBLE' } })
);

3. Кеширование и оптимизация

class ProductCache {
  private cache = new Map<string, { data: any; timestamp: number }>();
  private readonly TTL = 5 * 60 * 1000; // 5 минут
  
  async getProductInfo(offerId: string) {
    const cached = this.cache.get(offerId);
    
    if (cached && (Date.now() - cached.timestamp) < this.TTL) {
      return cached.data;
    }
    
    const data = await client.products.getInfo({ offer_id: offerId });
    this.cache.set(offerId, { data, timestamp: Date.now() });
    
    return data;
  }
  
  clear() {
    this.cache.clear();
  }
}

4. Мониторинг и логирование

class ProductManager {
  private metrics = {
    created: 0,
    updated: 0,
    failed: 0,
    errors: [] as Array<{ offerId: string; error: string }>
  };
  
  async createProduct(item: ProductImportItem) {
    try {
      const result = await client.products.create({ items: [item] });
      this.metrics.created++;
      
      console.log(`✅ Товар создан: ${item.offer_id}`);
      return result;
    } catch (error: any) {
      this.metrics.failed++;
      this.metrics.errors.push({
        offerId: item.offer_id,
        error: error.message
      });
      
      console.error(`❌ Не удалось создать товар ${item.offer_id}:`, error);
      throw error;
    }
  }
  
  getMetrics() {
    return { ...this.metrics };
  }
  
  resetMetrics() {
    this.metrics = { created: 0, updated: 0, failed: 0, errors: [] };
  }
}

🔗 Связанные API

  • Category API — получение категорий для товаров
  • Finance API — финансовая отчётность по товарам
  • Analytics API — аналитика продаж
  • FBO API — управление товарами FBO
  • FBS API — управление товарами FBS

📞 Поддержка

Нашли ошибку или хотите улучшить документацию?

Полезные ресурсы:


🏠 Главная документация | 📚 Все категории ```