📦 Products API - Управление каталогом товаров
📦 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
📞 Поддержка
Нашли ошибку или хотите улучшить документацию?
Полезные ресурсы:
🏠 Главная документация | 📚 Все категории ```