Category API - Управление деревом категорий и характеристиками
Category API - Управление деревом категорий и характеристиками
Category API предоставляет возможности для работы с деревом категорий OZON, получения характеристик категорий и управления справочными значениями для создания и настройки товаров.
Обзор API
Количество методов: 6
Основные функции: Дерево категорий, характеристики товаров, справочники значений
Ключевая особенность: Создание товаров доступно только в категориях последнего уровня
Методы
1. Получение дерева категорий и типов товаров
Метод: getCategoryTree()
Эндпоинт: POST /v1/description-category/tree
Возвращает полное дерево категорий и типов товаров. Создание товаров доступно только в категориях последнего уровня (без подкатегорий).
Параметры запроса
interface CategoryGetTreeRequest {
language?: 'DEFAULT' | 'RU' | 'EN'; // Язык ответа (по умолчанию RU)
}
Пример использования
import { OzonSellerApiClient } from '@spacechemical/ozon-seller-api';
const client = new OzonSellerApiClient({
apiKey: 'your-api-key',
clientId: 'your-client-id'
});
// Получить дерево категорий
const categoryTree = await client.category.getCategoryTree({
language: 'RU'
});
// Функция для печати дерева категорий
const printTree = (items: CategoryTreeItem[], level = 0) => {
items.forEach(item => {
const indent = ' '.repeat(level);
const status = item.disabled ? '[НЕДОСТУПНА]' : '[ДОСТУПНА]';
console.log(`${indent}${item.category_name} (ID: ${item.description_category_id}) ${status}`);
if (item.type_name) {
console.log(`${indent} Тип: ${item.type_name} (ID: ${item.type_id})`);
}
if (item.children && item.children.length > 0) {
printTree(item.children, level + 1);
}
});
};
printTree(categoryTree.result || []);
// Найти категории последнего уровня для создания товаров
const getLeafCategories = (items: CategoryTreeItem[]): CategoryTreeItem[] => {
const result: CategoryTreeItem[] = [];
items.forEach(item => {
if ((!item.children || item.children.length === 0) && !item.disabled) {
result.push(item);
} else if (item.children) {
result.push(...getLeafCategories(item.children));
}
});
return result;
};
const availableCategories = getLeafCategories(categoryTree.result || []);
console.log(`Доступно категорий для создания товаров: ${availableCategories.length}`);
Структура ответа
interface CategoryTreeItem {
description_category_id?: number; // ID категории
category_name?: string; // Название категории
disabled?: boolean; // Недоступна для создания товаров
type_id?: number; // ID типа товара
type_name?: string; // Название типа товара
children?: CategoryTreeItem[]; // Подкатегории
}
2. Получение характеристик категории
Метод: getCategoryAttributes()
Эндпоинт: POST /v1/description-category/attribute
Получает список характеристик для указанной категории и типа товара, включая информацию о справочниках значений.
Параметры запроса
interface CategoryGetAttributesRequest {
description_category_id: number; // ID категории
type_id: number; // ID типа товара
language?: 'DEFAULT' | 'RU' | 'EN'; // Язык ответа
}
Пример использования
// Получить характеристики для категории и типа товара
const attributes = await client.category.getCategoryAttributes({
description_category_id: 15621,
type_id: 31,
language: 'RU'
});
attributes.result?.forEach(attr => {
console.log(`Характеристика: ${attr.name} (ID: ${attr.id})`);
console.log(` Тип: ${attr.type}`);
console.log(` Обязательная: ${attr.is_required ? 'Да' : 'Нет'}`);
console.log(` Коллекция: ${attr.is_collection ? 'Да' : 'Нет'}`);
console.log(` Аспектная: ${attr.is_aspect ? 'Да' : 'Нет'}`);
console.log(` Группа: ${attr.group_name} (ID: ${attr.group_id})`);
if (attr.dictionary_id && attr.dictionary_id > 0) {
console.log(` Справочник: ${attr.dictionary_id}`);
console.log(` Зависит от категории: ${attr.category_dependent ? 'Да' : 'Нет'}`);
} else {
console.log(` Справочник: отсутствует`);
}
if (attr.description) {
console.log(` Описание: ${attr.description}`);
}
console.log('');
});
// Найти обязательные характеристики
const requiredAttributes = attributes.result?.filter(attr => attr.is_required) || [];
console.log(`Обязательных характеристик: ${requiredAttributes.length}`);
// Найти характеристики со справочниками
const dictionaryAttributes = attributes.result?.filter(attr =>
attr.dictionary_id && attr.dictionary_id > 0
) || [];
console.log(`Характеристик со справочниками: ${dictionaryAttributes.length}`);
Структура характеристики
interface CategoryAttribute {
id?: number; // ID характеристики
name?: string; // Название характеристики
description?: string; // Описание
type?: string; // Тип характеристики
is_collection?: boolean; // Можно указать несколько значений
is_required?: boolean; // Обязательная для заполнения
is_aspect?: boolean; // Аспектная характеристика
group_id?: number; // ID группы характеристик
group_name?: string; // Название группы
dictionary_id?: number; // ID справочника (0 = нет справочника)
category_dependent?: boolean; // Значения зависят от категории
}
3. Получение значений характеристики
Метод: getCategoryAttributeValues()
Эндпоинт: POST /v1/description-category/attribute/values
Возвращает справочник значений для указанной характеристики с поддержкой пагинации.
Параметры запроса
interface CategoryGetAttributeValuesRequest {
attribute_id: number; // ID характеристики
description_category_id: number; // ID категории
type_id: number; // ID типа товара
language?: 'DEFAULT' | 'RU' | 'EN'; // Язык ответа
limit?: number; // Количество значений (по умолчанию 100)
last_value_id?: number; // ID последнего значения для пагинации
}
Пример использования
// Получить первую страницу значений характеристики
const attributeValues = await client.category.getCategoryAttributeValues({
attribute_id: 85,
description_category_id: 15621,
type_id: 31,
limit: 100,
language: 'RU'
});
attributeValues.result?.forEach(value => {
console.log(`Значение: ${value.value} (ID: ${value.id})`);
if (value.info) {
console.log(` Описание: ${value.info}`);
}
if (value.picture) {
console.log(` Изображение: ${value.picture}`);
}
});
// Функция для получения всех значений с пагинацией
const getAllAttributeValues = async (attributeId: number, categoryId: number, typeId: number) => {
const allValues: CategoryAttributeValue[] = [];
let lastValueId: number | undefined;
do {
const response = await client.category.getCategoryAttributeValues({
attribute_id: attributeId,
description_category_id: categoryId,
type_id: typeId,
limit: 1000,
last_value_id: lastValueId
});
if (response.result) {
allValues.push(...response.result);
lastValueId = response.result[response.result.length - 1]?.id;
}
if (!response.has_next) {
break;
}
} while (true);
return allValues;
};
const allValues = await getAllAttributeValues(85, 15621, 31);
console.log(`Всего значений: ${allValues.length}`);
4. Поиск значений характеристики
Метод: searchCategoryAttributeValues()
Эндпоинт: POST /v1/description-category/attribute/values/search
Возвращает справочные значения характеристики по заданному поисковому запросу.
Параметры запроса
interface CategorySearchAttributeValuesRequest {
attribute_id: number; // ID характеристики
description_category_id: number; // ID категории
type_id: number; // ID типа товара
value: string; // Поисковый запрос
limit?: number; // Количество значений (максимум 1000)
}
Пример использования
// Поиск значений цвета по запросу "крас"
const searchResults = await client.category.searchCategoryAttributeValues({
attribute_id: 85,
description_category_id: 15621,
type_id: 31,
value: 'крас',
limit: 50
});
console.log(`Найдено значений: ${searchResults.result?.length || 0}`);
searchResults.result?.forEach(value => {
console.log(`- ${value.value} (ID: ${value.id})`);
if (value.info) {
console.log(` Описание: ${value.info}`);
}
});
// Функция для поиска значения по точному совпадению
const findExactValue = async (attributeId: number, categoryId: number, typeId: number, searchValue: string) => {
const results = await client.category.searchCategoryAttributeValues({
attribute_id: attributeId,
description_category_id: categoryId,
type_id: typeId,
value: searchValue,
limit: 100
});
return results.result?.find(value =>
value.value?.toLowerCase() === searchValue.toLowerCase()
);
};
const exactMatch = await findExactValue(85, 15621, 31, 'красный');
if (exactMatch) {
console.log(`Найдено точное совпадение: ${exactMatch.value} (ID: ${exactMatch.id})`);
}
Практические сценарии использования
1. Выбор категории для нового товара
class CategorySelector {
constructor(private client: OzonSellerApiClient) {}
// Найти подходящую категорию по ключевым словам
async findCategoriesByKeywords(keywords: string[]): Promise<CategoryMatch[]> {
const tree = await this.client.category.getCategoryTree({ language: 'RU' });
const leafCategories = this.getLeafCategories(tree.result || []);
const matches: CategoryMatch[] = [];
leafCategories.forEach(category => {
const categoryName = category.category_name?.toLowerCase() || '';
const typeName = category.type_name?.toLowerCase() || '';
const score = keywords.reduce((acc, keyword) => {
const lowerKeyword = keyword.toLowerCase();
if (categoryName.includes(lowerKeyword)) acc += 2;
if (typeName.includes(lowerKeyword)) acc += 1;
return acc;
}, 0);
if (score > 0) {
matches.push({
category,
score,
matchedKeywords: keywords.filter(keyword =>
categoryName.includes(keyword.toLowerCase()) ||
typeName.includes(keyword.toLowerCase())
)
});
}
});
return matches.sort((a, b) => b.score - a.score);
}
// Получить информацию о комиссии для категорий
async getCategoryCommissionInfo(categoryIds: number[]) {
// Здесь могла бы быть логика получения информации о комиссиях
// В реальном приложении это может быть отдельный API или справочник
console.log('Внимательно выбирайте категорию - для разных категорий применяется разный размер комиссии');
return categoryIds.map(id => ({
categoryId: id,
commission: 'Информация о комиссии доступна в личном кабинете'
}));
}
private getLeafCategories(items: CategoryTreeItem[]): CategoryTreeItem[] {
const result: CategoryTreeItem[] = [];
items.forEach(item => {
if ((!item.children || item.children.length === 0) && !item.disabled) {
result.push(item);
} else if (item.children) {
result.push(...this.getLeafCategories(item.children));
}
});
return result;
}
}
interface CategoryMatch {
category: CategoryTreeItem;
score: number;
matchedKeywords: string[];
}
const selector = new CategorySelector(client);
// Найти категории для товара "смартфон"
const matches = await selector.findCategoriesByKeywords(['смартфон', 'телефон', 'мобильный']);
console.log(`Найдено ${matches.length} подходящих категорий:`);
matches.slice(0, 5).forEach(match => {
console.log(`${match.category.category_name} - ${match.category.type_name}`);
console.log(` ID категории: ${match.category.description_category_id}, ID типа: ${match.category.type_id}`);
console.log(` Совпадения: ${match.matchedKeywords.join(', ')} (score: ${match.score})`);
console.log('');
});
2. Анализ характеристик для создания товара
class AttributeAnalyzer {
constructor(private client: OzonSellerApiClient) {}
async analyzeCategory(categoryId: number, typeId: number) {
const attributes = await this.client.category.getCategoryAttributes({
description_category_id: categoryId,
type_id: typeId,
language: 'RU'
});
const analysis = {
total: attributes.result?.length || 0,
required: 0,
optional: 0,
withDictionary: 0,
withoutDictionary: 0,
collections: 0,
aspects: 0,
groups: new Set<string>()
};
const attributesByGroup = new Map<string, CategoryAttribute[]>();
attributes.result?.forEach(attr => {
if (attr.is_required) analysis.required++;
else analysis.optional++;
if (attr.dictionary_id && attr.dictionary_id > 0) analysis.withDictionary++;
else analysis.withoutDictionary++;
if (attr.is_collection) analysis.collections++;
if (attr.is_aspect) analysis.aspects++;
if (attr.group_name) {
analysis.groups.add(attr.group_name);
if (!attributesByGroup.has(attr.group_name)) {
attributesByGroup.set(attr.group_name, []);
}
attributesByGroup.get(attr.group_name)!.push(attr);
}
});
console.log(`Анализ характеристик для категории ${categoryId}, тип ${typeId}:`);
console.log(`Всего характеристик: ${analysis.total}`);
console.log(`Обязательных: ${analysis.required}`);
console.log(`Опциональных: ${analysis.optional}`);
console.log(`Со справочниками: ${analysis.withDictionary}`);
console.log(`Без справочников: ${analysis.withoutDictionary}`);
console.log(`Коллекций: ${analysis.collections}`);
console.log(`Аспектных: ${analysis.aspects}`);
console.log(`Групп характеристик: ${analysis.groups.size}`);
console.log('');
// Анализ по группам
console.log('Характеристики по группам:');
attributesByGroup.forEach((attrs, groupName) => {
const requiredInGroup = attrs.filter(a => a.is_required).length;
console.log(`${groupName}: ${attrs.length} характеристик (${requiredInGroup} обязательных)`);
attrs.filter(a => a.is_required).forEach(attr => {
console.log(` - ${attr.name} (ID: ${attr.id}) [ОБЯЗАТЕЛЬНАЯ]`);
});
});
return {
analysis,
attributesByGroup,
requiredAttributes: attributes.result?.filter(a => a.is_required) || []
};
}
async validateProductData(categoryId: number, typeId: number, productData: any) {
const { requiredAttributes } = await this.analyzeCategory(categoryId, typeId);
const errors: string[] = [];
const warnings: string[] = [];
requiredAttributes.forEach(attr => {
const attrId = attr.id?.toString();
if (!productData.attributes || !productData.attributes[attrId!]) {
errors.push(`Отсутствует обязательная характеристика: ${attr.name} (ID: ${attr.id})`);
}
});
return { errors, warnings, isValid: errors.length === 0 };
}
}
const analyzer = new AttributeAnalyzer(client);
// Анализ категории смартфонов
const analysis = await analyzer.analyzeCategory(15621, 31);
// Проверка данных товара
const productData = {
name: 'iPhone 15',
attributes: {
'85': [123], // Цвет
'10096': ['Apple'], // Бренд
// ... другие характеристики
}
};
const validation = await analyzer.validateProductData(15621, 31, productData);
if (!validation.isValid) {
console.log('Ошибки в данных товара:');
validation.errors.forEach(error => console.log(`- ${error}`));
}
3. Система автодополнения для характеристик
class AttributeAutocomplete {
constructor(private client: OzonSellerApiClient) {}
async searchValues(
attributeId: number,
categoryId: number,
typeId: number,
query: string,
limit: number = 10
) {
if (query.length < 2) {
return [];
}
try {
const results = await this.client.category.searchCategoryAttributeValues({
attribute_id: attributeId,
description_category_id: categoryId,
type_id: typeId,
value: query,
limit
});
return (results.result || []).map(value => ({
id: value.id,
value: value.value,
description: value.info,
picture: value.picture
}));
} catch (error) {
console.error('Ошибка поиска значений:', error);
return [];
}
}
async suggestBrands(categoryId: number, typeId: number, query: string) {
// Предполагаем, что ID характеристики "Бренд" = 10096
return this.searchValues(10096, categoryId, typeId, query, 20);
}
async suggestColors(categoryId: number, typeId: number, query: string) {
// Предполагаем, что ID характеристики "Цвет" = 85
return this.searchValues(85, categoryId, typeId, query, 15);
}
async suggestSizes(categoryId: number, typeId: number, query: string) {
// Предполагаем, что ID характеристики "Размер" = 4180
return this.searchValues(4180, categoryId, typeId, query, 30);
}
}
const autocomplete = new AttributeAutocomplete(client);
// Поиск брендов, начинающихся на "App"
const brandSuggestions = await autocomplete.suggestBrands(15621, 31, 'App');
console.log('Предложения брендов:');
brandSuggestions.forEach(brand => {
console.log(`- ${brand.value} (ID: ${brand.id})`);
});
// Поиск цветов, содержащих "син"
const colorSuggestions = await autocomplete.suggestColors(15621, 31, 'син');
console.log('Предложения цветов:');
colorSuggestions.forEach(color => {
console.log(`- ${color.value} (ID: ${color.id})`);
});
4. Кэширование данных категорий
class CategoryCache {
private treeCache: Map<string, { data: any; timestamp: number }> = new Map();
private attributesCache: Map<string, { data: any; timestamp: number }> = new Map();
private valuesCache: Map<string, { data: any; timestamp: number }> = new Map();
private cacheTimeout = 60 * 60 * 1000; // 1 час
constructor(private client: OzonSellerApiClient) {}
async getCategoryTree(language: string = 'RU', useCache: boolean = true) {
const cacheKey = `tree-${language}`;
if (useCache && this.isCacheValid(this.treeCache, cacheKey)) {
return this.treeCache.get(cacheKey)!.data;
}
const data = await this.client.category.getCategoryTree({ language });
this.treeCache.set(cacheKey, { data, timestamp: Date.now() });
return data;
}
async getCategoryAttributes(
categoryId: number,
typeId: number,
language: string = 'RU',
useCache: boolean = true
) {
const cacheKey = `attributes-${categoryId}-${typeId}-${language}`;
if (useCache && this.isCacheValid(this.attributesCache, cacheKey)) {
return this.attributesCache.get(cacheKey)!.data;
}
const data = await this.client.category.getCategoryAttributes({
description_category_id: categoryId,
type_id: typeId,
language
});
this.attributesCache.set(cacheKey, { data, timestamp: Date.now() });
return data;
}
async getAttributeValues(
attributeId: number,
categoryId: number,
typeId: number,
useCache: boolean = true
) {
const cacheKey = `values-${attributeId}-${categoryId}-${typeId}`;
if (useCache && this.isCacheValid(this.valuesCache, cacheKey)) {
return this.valuesCache.get(cacheKey)!.data;
}
// Получаем все значения с пагинацией
const allValues: any[] = [];
let lastValueId: number | undefined;
do {
const response = await this.client.category.getCategoryAttributeValues({
attribute_id: attributeId,
description_category_id: categoryId,
type_id: typeId,
limit: 1000,
last_value_id: lastValueId
});
if (response.result) {
allValues.push(...response.result);
lastValueId = response.result[response.result.length - 1]?.id;
}
if (!response.has_next) break;
} while (true);
const data = { result: allValues, total: allValues.length };
this.valuesCache.set(cacheKey, { data, timestamp: Date.now() });
return data;
}
private isCacheValid(cache: Map<string, { data: any; timestamp: number }>, key: string): boolean {
const cached = cache.get(key);
if (!cached) return false;
return Date.now() - cached.timestamp < this.cacheTimeout;
}
clearCache() {
this.treeCache.clear();
this.attributesCache.clear();
this.valuesCache.clear();
console.log('Кэш очищен');
}
getCacheStats() {
return {
tree: this.treeCache.size,
attributes: this.attributesCache.size,
values: this.valuesCache.size
};
}
}
const cache = new CategoryCache(client);
// Использование с кэшированием
const tree = await cache.getCategoryTree('RU');
const attributes = await cache.getCategoryAttributes(15621, 31, 'RU');
const values = await cache.getAttributeValues(85, 15621, 31);
console.log('Статистика кэша:', cache.getCacheStats());
Обработка ошибок
Типичные ошибки
try {
const attributes = await client.category.getCategoryAttributes({
description_category_id: 15621,
type_id: 31,
language: 'RU'
});
} catch (error) {
if (error.response?.status === 400) {
const errorData = error.response.data;
switch (errorData.code) {
case 'INVALID_CATEGORY_ID':
console.error('Неверный ID категории');
break;
case 'INVALID_TYPE_ID':
console.error('Неверный ID типа товара');
break;
case 'CATEGORY_TYPE_MISMATCH':
console.error('Тип товара не соответствует категории');
break;
case 'CATEGORY_DISABLED':
console.error('Категория недоступна для создания товаров');
break;
default:
console.error('Неизвестная ошибка:', errorData.message);
}
} else if (error.response?.status === 404) {
console.error('Категория или тип товара не найдены');
} else if (error.response?.status === 429) {
console.error('Превышен лимит запросов. Повторите попытку позже.');
} else {
console.error('Произошла ошибка:', error.message);
}
}
Лучшие практики
1. Оптимизация запросов
// Кэшируйте дерево категорий - оно меняется редко
const categoryTree = await client.category.getCategoryTree();
// Используйте пагинацию для больших справочников
async function getAllValues(attributeId: number, categoryId: number, typeId: number) {
const values = [];
let lastId: number | undefined;
while (true) {
const response = await client.category.getCategoryAttributeValues({
attribute_id: attributeId,
description_category_id: categoryId,
type_id: typeId,
limit: 1000,
last_value_id: lastId
});
if (response.result?.length) {
values.push(...response.result);
lastId = response.result[response.result.length - 1].id;
}
if (!response.has_next) break;
}
return values;
}
2. Валидация данных товара
// Всегда проверяйте обязательные характеристики перед созданием товара
const requiredAttributes = attributes.result?.filter(attr => attr.is_required);
// Убедитесь, что используете правильные значения из справочников
const isValidValue = async (attributeId: number, valueId: number) => {
const values = await getAllValues(attributeId, categoryId, typeId);
return values.some(v => v.id === valueId);
};
3. Работа с многоязычностью
// Получайте данные на нужном языке
const treeRU = await client.category.getCategoryTree({ language: 'RU' });
const treeEN = await client.category.getCategoryTree({ language: 'EN' });
Связанные API: Product API (создание товаров), Prices-Stocks API (управление ценами), Barcode API (штрихкоды)