Skip to content

Лучшие практики работы с рекламными кампаниями ​

⚠️ v3.0.0: Это руководство обновлено для SDK v3.0.0. Если вы используете v2.x, см. руководство по миграции.

Полное руководство по эффективному управлению рекламными кампаниями на Wildberries через SDK.

Целевая аудитория ​

Для кого этот гайд:

  • Разработчики e-commerce платформ
  • Маркетологи-аналитики с техническим бэкграундом
  • Интеграторы рекламных инструментов

Требования ​

  • Базовое знание Wildberries SDK
  • Понимание рекламных кампаний на маркетплейсах
  • Опыт работы с асинхронным TypeScript

Время чтения ​

Приблизительно 30 минут


Архитектура рекламных кампаний ​

Типы кампаний ​

Wildberries использует систему типов для различных версий рекламных кампаний:

ТипСтатусОписание
4-8УстаревшиеСтарые версии API, не рекомендуются для новых кампаний
9АктуальныйТекущая версия с полным функционалом

Совет

Всегда используйте тип 9 для новых кампаний. Старые типы поддерживаются только для обратной совместимости.

Статусы кампаний ​

typescript
enum CampaignStatus {
  READY = 4,        // Готова к запуску
  COMPLETED = 7,    // Завершена
  CANCELED = 8,     // Отменена
  ACTIVE = 9,       // Активна и показывается
  PAUSED = 11,      // На паузе
  DELETING = -1     // В процессе удаления
}

Типы ставок ​

Ручная ставка (Auction Bid):

  • Индивидуальные ставки для каждого товара
  • Разные ставки для размещений (поиск/рекомендации)
  • Максимальная гибкость и контроль
  • Требует больше времени на управление

Единая ставка (CPM):

  • Одна ставка для всех товаров кампании
  • Автоматическое распределение бюджета
  • Проще в управлении
  • Подходит для однородного ассортимента

Типы размещения ​

typescript
enum PlacementType {
  SEARCH = 6,           // Поиск (каталог + карточка товара)
  RECOMMENDATIONS = 8,  // Рекомендации (автопродвижение)
  COMBINED = 9          // Комбинированное (поиск + рекомендации)
}

Диаграмма переходов статусов ​

     [Создание]
         |
         v
    [READY (4)]
         |
         +---> [startCampaign] ---> [ACTIVE (9)]
         |                               |
         +---> [pauseCampaign] -------->+---> [PAUSED (11)]
                                         |         |
                                         |         +---> [startCampaign] ---> [ACTIVE (9)]
                                         |
                                         +---> [stopCampaign] ---> [COMPLETED (7)]
                                         |
                                         +---> [Удаление через ЛК] ---> [DELETING (-1)]

Важно

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


Жизненный цикл кампании ​

Чек-лист перед запуском ​

Перед созданием кампании убедитесь:

  • [ ] У вас есть товары с достаточным остатком
  • [ ] Товары имеют качественные фото и описание
  • [ ] Определены целевые ключевые слова
  • [ ] Рассчитан начальный бюджет (минимум 1000₽)
  • [ ] Выбрана стратегия ставок
  • [ ] Настроены лимиты расхода

Создание кампании ​

typescript
import { WildberriesSDK } from 'daytona-wildberries-typescript-sdk';

const sdk = new WildberriesSDK({
  apiKey: process.env.WB_API_KEY!
});

async function createCampaign() {
  try {
    // Шаг 1: Создание кампании (статус READY)
    const campaign = await sdk.promotion.createSeacatSaveAd({
      type: 9,              // Актуальная версия API
      name: 'Летняя коллекция 2024',
      subjectId: 566,       // ID категории
      nms: [123456, 789012], // Артикулы товаров

      // Параметры размещения
      params: [{
        subjectId: 566,
        sets: [6, 8],       // Поиск + рекомендации
        active: true
      }],

      // Настройки кампании
      dailyBudget: 5000,    // Дневной бюджет в копейках (50₽)
      unitedParams: [{
        subjectId: 566,
        sets: [6, 8],
        bid: 200            // CPM ставка 2₽
      }]
    });

    console.log('Кампания создана:', campaign.advertId);
    console.log('Статус:', campaign.status); // 4 (READY)

    return campaign;

  } catch (error) {
    console.error('Ошибка создания кампании:', error);
    throw error;
  }
}

Последовательность запуска ​

typescript
async function launchCampaign(advertId: number) {
  try {
    // Шаг 2: Пополнение бюджета (минимум 1000₽)
    console.log('Пополнение бюджета...');
    await sdk.promotion.createBudgetDeposit(
      { sum: 100000, return: false }, // 1000₽
      { id: advertId }
    );

    // Ждем обновления статуса на PAUSED (11)
    await new Promise(resolve => setTimeout(resolve, 2000));

    // Шаг 3: Запуск кампании
    console.log('Запуск кампании...');
    await sdk.promotion.startCampaign({ id: advertId });

    // Шаг 4: Проверка статуса
    const campaigns = await sdk.promotion.getCampaigns();
    const activeCampaign = campaigns.adverts.find(
      a => a.advertId === advertId
    );

    if (activeCampaign?.status === 9) {
      console.log('✓ Кампания успешно запущена');
      return true;
    } else {
      console.warn('⚠ Статус кампании:', activeCampaign?.status);
      return false;
    }

  } catch (error) {
    console.error('Ошибка запуска:', error);
    throw error;
  }
}

Критично

После пополнения бюджета кампания автоматически переходит в статус PAUSED (11). Необходимо явно вызвать startCampaign() для активации.

Мониторинг активной кампании ​

typescript
async function monitorCampaign(advertId: number) {
  // Получение текущего состояния
  const campaigns = await sdk.promotion.getCampaigns();
  const campaign = campaigns.adverts.find(a => a.advertId === advertId);

  if (!campaign) {
    throw new Error('Кампания не найдена');
  }

  console.log('Статус кампании:', {
    id: campaign.advertId,
    name: campaign.name,
    status: campaign.status,
    type: campaign.type,
    changeTime: campaign.changeTime
  });

  // Проверка бюджета
  const budget = await sdk.promotion.getAdvBudget({ id: advertId });
  console.log('Бюджет:', {
    balance: budget.balance / 100, // В рублях
    dailyBudget: budget.dailyBudget / 100
  });

  // Получение статистики за сегодня
  const today = new Date().toISOString().split('T')[0];
  const stats = await sdk.promotion.getAdvFullstats({
    id: [advertId],
    dates: { from: today, to: today }
  });

  if (stats.length > 0) {
    const todayStats = stats[0];
    console.log('Статистика за сегодня:', {
      views: todayStats.views,
      clicks: todayStats.clicks,
      ctr: ((todayStats.clicks / todayStats.views) * 100).toFixed(2) + '%',
      spent: todayStats.sum / 100, // В рублях
      orders: todayStats.orders,
      cr: ((todayStats.orders / todayStats.clicks) * 100).toFixed(2) + '%'
    });
  }
}

Пауза, остановка и удаление ​

typescript
async function manageCampaignLifecycle(advertId: number) {
  // Пауза (можно возобновить)
  await sdk.promotion.pauseCampaign({ id: advertId });
  console.log('Кампания на паузе (статус 11)');

  // Возобновление с паузы
  await sdk.promotion.startCampaign({ id: advertId });
  console.log('Кампания возобновлена (статус 9)');

  // Остановка (завершение кампании)
  await sdk.promotion.stopCampaign({ id: advertId });
  console.log('Кампания остановлена (статус 7)');

  // Удаление (только для статуса 4 - READY)
  // Если кампания активна, сначала нужно остановить
  const campaigns = await sdk.promotion.getCampaigns();
  const campaign = campaigns.adverts.find(a => a.advertId === advertId);

  if (campaign?.status !== 4) {
    console.log('Сначала останавливаем кампанию...');
    await sdk.promotion.stopCampaign({ id: advertId });
    await new Promise(resolve => setTimeout(resolve, 2000));
  }

  // Удаление кампаний через SDK недоступно в v3.0.0
  // Используйте личный кабинет Wildberries для удаления кампаний
  console.log('Для удаления кампании используйте личный кабинет WB');
}

Правила переходов статусов ​

Текущий статусДоступные действия
READY (4)startCampaign() → ACTIVE<br>Удаление через личный кабинет
ACTIVE (9)pauseCampaign() → PAUSED<br>stopCampaign() → COMPLETED<br>Добавление фраз (только в этом статусе)
PAUSED (11)startCampaign() → ACTIVE<br>stopCampaign() → COMPLETED<br>Пополнение бюджета
COMPLETED (7)Только просмотр статистики

Важные ограничения

  • Добавление ключевых фраз возможно только в статусе ACTIVE (9)
  • Пополнение бюджета переводит кампанию в статус PAUSED (11)
  • Удаление возможно только через личный кабинет Wildberries

Управление бюджетом ​

Проверка баланса рекламного счета ​

typescript
async function checkBalance() {
  const balance = await sdk.promotion.getAdvBalance();

  console.log('Баланс рекламного кабинета:', {
    balance: balance.balance / 100, // В рублях
    net: balance.net / 100,
    bonus: balance.bonus / 100
  });

  // Рекомендация по пополнению
  if (balance.balance < 100000) { // Меньше 1000₽
    console.warn('⚠ Рекомендуется пополнить баланс');
  }

  return balance;
}

Пополнение бюджета кампании ​

typescript
async function depositToCampaign(
  advertId: number,
  amount: number // В рублях
) {
  try {
    // Конвертация в копейки
    const amountInKopecks = amount * 100;

    // Проверка минимальной суммы
    if (amountInKopecks < 100000) {
      throw new Error('Минимальная сумма пополнения: 1000₽');
    }

    // Пополнение (return: false = безвозвратное)
    await sdk.promotion.createBudgetDeposit(
      {
        sum: amountInKopecks,
        return: false // true = деньги вернутся на счет при остановке
      },
      { id: advertId }
    );

    console.log(`✓ Пополнено ${amount}₽`);
    console.log('⚠ Кампания переведена в статус PAUSED (11)');
    console.log('💡 Вызовите startCampaign() для возобновления');

    // Проверка нового баланса кампании
    const budget = await sdk.promotion.getAdvBudget({ id: advertId });
    console.log('Новый баланс кампании:', budget.balance / 100, '₽');

  } catch (error) {
    console.error('Ошибка пополнения:', error);
    throw error;
  }
}

Получение бюджета кампании ​

typescript
async function getCampaignBudget(advertId: number) {
  const budget = await sdk.promotion.getAdvBudget({ id: advertId });

  const info = {
    balance: budget.balance / 100,           // Текущий баланс
    dailyBudget: budget.dailyBudget / 100,   // Дневной лимит

    // Расчет прогноза
    daysLeft: Math.floor(
      budget.balance / budget.dailyBudget
    ),

    // Предупреждения
    needsRefill: budget.balance < budget.dailyBudget * 3
  };

  if (info.needsRefill) {
    console.warn(`⚠ Баланса хватит на ${info.daysLeft} дней`);
    console.warn('Рекомендуется пополнить для непрерывной работы');
  }

  return info;
}

История операций с бюджетом ​

typescript
async function getBudgetHistory(
  from: string, // 'YYYY-MM-DD'
  to: string
) {
  // История изменений бюджета кампаний
  const updates = await sdk.promotion.getAdvUpd({ from, to });

  console.log('История изменений:');
  updates.forEach(update => {
    console.log({
      advertId: update.advertId,
      date: update.date,
      type: update.type,
      sum: update.sum / 100, // В рублях
      updateTime: update.updateTime
    });
  });

  // История платежей
  const payments = await sdk.promotion.getAdvPayments({ from, to });

  console.log('\nИстория платежей:');
  payments.forEach(payment => {
    console.log({
      advertId: payment.advertId,
      date: payment.date,
      sum: payment.sum / 100,
      type: payment.type // Тип операции
    });
  });

  return { updates, payments };
}

Стратегия управления бюджетом ​

typescript
async function manageBudgetStrategy(advertId: number) {
  const budget = await sdk.promotion.getAdvBudget({ id: advertId });
  const dailyBudget = budget.dailyBudget;
  const currentBalance = budget.balance;

  // Стратегия 1: Автопополнение при низком балансе
  const minBalance = dailyBudget * 7; // Неделя работы

  if (currentBalance < minBalance) {
    console.log('Баланс низкий, требуется пополнение');

    // Ставим на паузу перед пополнением
    await sdk.promotion.pauseCampaign({ id: advertId });

    // Пополняем на 2 недели работы
    const refillAmount = dailyBudget * 14;
    await sdk.promotion.createBudgetDeposit(
      { sum: refillAmount, return: false },
      { id: advertId }
    );

    // Возобновляем кампанию
    await sdk.promotion.startCampaign({ id: advertId });

    console.log('✓ Автопополнение выполнено');
  }

  // Стратегия 2: Динамическое изменение дневного бюджета
  const today = new Date().toISOString().split('T')[0];
  const stats = await sdk.promotion.getAdvFullstats({
    id: [advertId],
    dates: { from: today, to: today }
  });

  if (stats.length > 0) {
    const todayStats = stats[0];
    const roi = todayStats.orders > 0
      ? (todayStats.sum_price / todayStats.sum)
      : 0;

    if (roi > 2.0) {
      // Хорошая доходность - увеличиваем бюджет
      console.log('ROI высокий, рекомендуется увеличить бюджет');
    } else if (roi < 1.0) {
      // Убыточная кампания - уменьшаем бюджет
      console.log('ROI низкий, рекомендуется уменьшить бюджет');
    }
  }
}

Лучшая практика

Всегда ставьте кампанию на паузу (pauseCampaign) перед пополнением бюджета, даже если она уже активна. Это предотвращает потенциальные ошибки синхронизации.


Оптимизация ставок ​

Минимальные ставки по товарам ​

typescript
async function getMinimumBids(
  advertId: number,
  nmIds: number[],      // Артикулы товаров
  paymentType: number,  // 1 = CPM, 2 = CPC
  placementTypes: number[] // [6, 8] = поиск + рекомендации
) {
  const minBids = await sdk.promotion.createBidsMin({
    advert_id: advertId,
    nm_ids: nmIds,
    payment_type: paymentType,
    placement_types: placementTypes
  });

  console.log('Минимальные ставки:');
  minBids.forEach(bid => {
    console.log({
      nmId: bid.nm_id,
      search: bid.bids.find(b => b.type === 6)?.bid / 100 + '₽',
      recommendations: bid.bids.find(b => b.type === 8)?.bid / 100 + '₽'
    });
  });

  return minBids;
}

Обновление единой ставки (CPM) ​

typescript
async function updateUnifiedBid(advertId: number, newBid: number) {
  try {
    // newBid в рублях, конвертируем в копейки
    const bidInKopecks = Math.round(newBid * 100);

    await sdk.promotion.updateAdvBid({
      bids: [{
        id: advertId,
        bid: bidInKopecks
      }]
    });

    console.log(`✓ Единая ставка обновлена: ${newBid}₽`);

    // Проверка применения
    await new Promise(resolve => setTimeout(resolve, 1000));
    const campaigns = await sdk.promotion.getCampaigns();
    const updated = campaigns.adverts.find(a => a.advertId === advertId);

    console.log('Текущая ставка кампании:', updated?.bid / 100, '₽');

  } catch (error) {
    console.error('Ошибка обновления ставки:', error);
    throw error;
  }
}

Обновление ручных ставок (Auction Bid) ​

typescript
async function updateAuctionBids(
  advertId: number,
  bidsData: Array<{
    nmId: number;
    searchBid: number;  // В рублях
    recBid: number;     // В рублях
  }>
) {
  // Формирование структуры для API
  const bids = bidsData.map(item => ({
    id: advertId,
    nm: item.nmId,
    cpm: [
      {
        type: 6, // Поиск
        bid: Math.round(item.searchBid * 100)
      },
      {
        type: 8, // Рекомендации
        bid: Math.round(item.recBid * 100)
      }
    ]
  }));

  await sdk.promotion.updateAuctionBid({ bids });

  console.log('✓ Ручные ставки обновлены для товаров:', bidsData.length);

  // Вывод обновленных ставок
  bidsData.forEach(item => {
    console.log(`  Товар ${item.nmId}:`);
    console.log(`    Поиск: ${item.searchBid}₽`);
    console.log(`    Рекомендации: ${item.recBid}₽`);
  });
}

CPM vs CPC: Выбор стратегии ​

typescript
async function analyzePaymentStrategy(advertId: number) {
  const sevenDaysAgo = new Date();
  sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
  const from = sevenDaysAgo.toISOString().split('T')[0];
  const to = new Date().toISOString().split('T')[0];

  const stats = await sdk.promotion.getAdvFullstats({
    id: [advertId],
    dates: { from, to }
  });

  if (stats.length === 0) {
    console.log('Недостаточно данных для анализа');
    return;
  }

  // Агрегирование за неделю
  const totals = stats.reduce((acc, day) => ({
    views: acc.views + day.views,
    clicks: acc.clicks + day.clicks,
    spent: acc.spent + day.sum
  }), { views: 0, clicks: 0, spent: 0 });

  const avgCTR = (totals.clicks / totals.views) * 100;
  const avgCPC = totals.spent / totals.clicks / 100; // В рублях

  console.log('Анализ за 7 дней:');
  console.log(`  CTR: ${avgCTR.toFixed(2)}%`);
  console.log(`  CPC: ${avgCPC.toFixed(2)}₽`);
  console.log(`  Показы: ${totals.views}`);
  console.log(`  Клики: ${totals.clicks}`);

  // Рекомендация
  if (avgCTR > 2.0) {
    console.log('\n💡 Рекомендация: CPM');
    console.log('   Высокий CTR - выгоднее платить за показы');
  } else if (avgCTR < 0.5) {
    console.log('\n💡 Рекомендация: CPC');
    console.log('   Низкий CTR - безопаснее платить за клики');
  } else {
    console.log('\n💡 Рекомендация: A/B тестирование');
    console.log('   Создайте две кампании для сравнения');
  }

  return { avgCTR, avgCPC, totals };
}

Динамическая оптимизация ставок ​

typescript
async function optimizeBids(advertId: number) {
  // Получение текущих ставок и статистики
  const today = new Date().toISOString().split('T')[0];
  const stats = await sdk.promotion.getAdvFullstats({
    id: [advertId],
    dates: { from: today, to: today }
  });

  if (stats.length === 0) {
    console.log('Нет данных за сегодня');
    return;
  }

  const todayStats = stats[0];
  const ctr = (todayStats.clicks / todayStats.views) * 100;
  const cpc = todayStats.sum / todayStats.clicks / 100;
  const roi = todayStats.orders > 0
    ? (todayStats.sum_price / todayStats.sum)
    : 0;

  console.log('Текущие показатели:');
  console.log(`  CTR: ${ctr.toFixed(2)}%`);
  console.log(`  CPC: ${cpc.toFixed(2)}₽`);
  console.log(`  ROI: ${roi.toFixed(2)}`);

  // Стратегия оптимизации
  const campaigns = await sdk.promotion.getCampaigns();
  const campaign = campaigns.adverts.find(a => a.advertId === advertId);
  const currentBid = campaign?.bid || 0;

  let newBid = currentBid;

  if (roi > 3.0 && ctr > 2.0) {
    // Отличные показатели - увеличиваем ставку на 20%
    newBid = Math.round(currentBid * 1.2);
    console.log(`\n✓ Увеличиваем ставку на 20%: ${newBid / 100}₽`);

  } else if (roi < 1.0 || ctr < 0.5) {
    // Плохие показатели - уменьшаем ставку на 15%
    newBid = Math.round(currentBid * 0.85);
    console.log(`\n↓ Уменьшаем ставку на 15%: ${newBid / 100}₽`);

  } else {
    console.log('\n→ Ставка оптимальна, изменения не требуются');
    return;
  }

  // Применение новой ставки
  await sdk.promotion.updateAdvBid({
    bids: [{ id: advertId, bid: newBid }]
  });

  console.log('✓ Ставка обновлена автоматически');
}

Стратегия ставок

Для новых кампаний:

  1. Начните с минимальной ставки + 10%
  2. Мониторьте CTR первые 3 дня
  3. Увеличивайте постепенно (по 10-15%) если ROI > 2.0
  4. Уменьшайте при ROI < 1.0

Для стабильных кампаний:

  • Оптимизируйте раз в неделю
  • Избегайте резких изменений (>25%)
  • Анализируйте тренды за 7-14 дней

Управление ключевыми словами ​

typescript
async function addSearchExcludedKeywords(
  advertId: number,
  keywords: string[]
) {
  try {
    // Важно: кампания должна быть в статусе ACTIVE (9)
    const campaigns = await sdk.promotion.getCampaigns();
    const campaign = campaigns.adverts.find(a => a.advertId === advertId);

    if (campaign?.status !== 9) {
      throw new Error(
        'Кампания должна быть активна (статус 9) для добавления фраз'
      );
    }

    await sdk.promotion.createSearchSetExcluded({
      id: advertId,
      excluded: keywords
    });

    console.log(`✓ Добавлено ${keywords.length} минус-фраз:`, keywords);

  } catch (error) {
    console.error('Ошибка добавления минус-фраз:', error);
    throw error;
  }
}

Добавление минус-фраз (Auto) ​

typescript
async function addAutoExcludedKeywords(
  advertId: number,
  keywords: string[]
) {
  await sdk.promotion.createAutoSetExcluded({
    id: advertId,
    excluded: keywords
  });

  console.log(`✓ Минус-фразы для автопродвижения:`, keywords);
}

Добавление ключевых фраз ​

typescript
async function addSearchKeywords(
  advertId: number,
  keywords: Array<{
    keyword: string;
    excluded?: boolean; // false = добавить, true = исключить
  }>
) {
  const pluse = keywords
    .filter(k => !k.excluded)
    .map(k => k.keyword);

  await sdk.promotion.createSearchSetPlu({
    id: advertId,
    pluse // Именно 'pluse' (опечатка в API WB)
  });

  console.log(`✓ Добавлено ${pluse.length} ключевых фраз`);
}

Получение списка ключевых фраз ​

typescript
async function getSearchKeywords(advertId: number) {
  const keywords = await sdk.promotion.getSearchSetPlus({
    id: advertId
  });

  console.log('Ключевые фразы:');
  keywords.forEach(kw => {
    console.log(`  "${kw.keyword}" - ${kw.count} товаров`);
  });

  return keywords;
}

Управление товарами в автопродвижении ​

typescript
async function manageAutoPromotionItems(advertId: number) {
  // Получение доступных для добавления товаров
  const available = await sdk.promotion.getAutoGetnmtoadd({
    id: advertId
  });

  console.log('Доступно для добавления:', available.nms.length, 'товаров');

  if (available.nms.length > 0) {
    // Добавление товаров
    await sdk.promotion.createAutoUpdatenm({
      id: advertId,
      nms: available.nms.slice(0, 10) // Добавляем первые 10
    });

    console.log('✓ Добавлено 10 товаров в автопродвижение');
  }
}

Стратегия работы с ключевыми словами ​

typescript
async function keywordStrategy(advertId: number) {
  // Шаг 1: Анализ статистики по ключевым словам
  const weekAgo = new Date();
  weekAgo.setDate(weekAgo.getDate() - 7);
  const from = weekAgo.toISOString().split('T')[0];
  const to = new Date().toISOString().split('T')[0];

  const keywordStats = await sdk.promotion.getStatsKeywords({
    id: [advertId],
    dates: { from, to }
  });

  // Группировка по фразам
  const phrasePerformance = new Map<string, {
    clicks: number;
    views: number;
    orders: number;
    spent: number;
  }>();

  keywordStats.forEach(stat => {
    stat.keywords.forEach(kw => {
      const existing = phrasePerformance.get(kw.keyword) || {
        clicks: 0, views: 0, orders: 0, spent: 0
      };

      phrasePerformance.set(kw.keyword, {
        clicks: existing.clicks + kw.clicks,
        views: existing.views + kw.views,
        orders: existing.orders + kw.orders,
        spent: existing.spent + kw.sum
      });
    });
  });

  // Анализ эффективности
  const ineffective: string[] = [];
  const effective: string[] = [];

  phrasePerformance.forEach((stats, keyword) => {
    const ctr = (stats.clicks / stats.views) * 100;
    const roi = stats.orders > 0 ? (stats.spent / stats.orders) : 0;

    if (ctr < 0.3 && stats.views > 1000) {
      // Низкий CTR при большом количестве показов
      ineffective.push(keyword);
    } else if (roi > 2.0 && stats.orders > 5) {
      // Высокий ROI
      effective.push(keyword);
    }
  });

  console.log('\nАнализ ключевых слов:');
  console.log(`  Эффективных: ${effective.length}`);
  console.log(`  Неэффективных: ${ineffective.length}`);

  // Добавление неэффективных в минус-фразы
  if (ineffective.length > 0) {
    console.log('\nДобавление в минус-фразы:', ineffective);
    await sdk.promotion.createSearchSetExcluded({
      id: advertId,
      excluded: ineffective
    });
  }

  return { effective, ineffective, phrasePerformance };
}

Критично

Добавление ключевых фраз возможно ТОЛЬКО когда кампания в статусе ACTIVE (9). Попытка добавить фразы в другом статусе приведет к ошибке.


Статистика кампаний ​

Полная статистика кампании ​

typescript
async function getFullCampaignStats(
  advertIds: number[],
  from: string,
  to: string
) {
  // Лимит: 3 запроса в минуту, интервал 20 секунд
  const stats = await sdk.promotion.getAdvFullstats({
    id: advertIds,
    dates: { from, to }
  });

  console.log(`Статистика за ${from} - ${to}:`);

  stats.forEach(dayStat => {
    console.log(`\n${dayStat.date}:`);
    console.log(`  Показы: ${dayStat.views.toLocaleString()}`);
    console.log(`  Клики: ${dayStat.clicks.toLocaleString()}`);
    console.log(`  CTR: ${((dayStat.clicks / dayStat.views) * 100).toFixed(2)}%`);
    console.log(`  Расход: ${(dayStat.sum / 100).toFixed(2)}₽`);
    console.log(`  Заказы: ${dayStat.orders}`);
    console.log(`  CR: ${((dayStat.orders / dayStat.clicks) * 100).toFixed(2)}%`);
    console.log(`  Выручка: ${(dayStat.sum_price / 100).toFixed(2)}₽`);
    console.log(`  ROI: ${(dayStat.sum_price / dayStat.sum).toFixed(2)}`);
  });

  return stats;
}

Статистика по ключевым словам ​

typescript
async function getKeywordStatistics(
  advertIds: number[],
  from: string,
  to: string
) {
  // Лимит: 240 запросов в минуту
  const keywordStats = await sdk.promotion.getStatsKeywords({
    id: advertIds,
    dates: { from, to }
  });

  console.log('Статистика по ключевым словам:');

  keywordStats.forEach(campaignStat => {
    console.log(`\nКампания ${campaignStat.advertId} (${campaignStat.date}):`);

    // Сортировка по кликам
    const sorted = campaignStat.keywords.sort((a, b) => b.clicks - a.clicks);

    sorted.slice(0, 10).forEach(kw => {
      const ctr = ((kw.clicks / kw.views) * 100).toFixed(2);
      const cpc = (kw.sum / kw.clicks / 100).toFixed(2);

      console.log(`  "${kw.keyword}"`);
      console.log(`    Показы: ${kw.views}, Клики: ${kw.clicks}`);
      console.log(`    CTR: ${ctr}%, CPC: ${cpc}₽`);
      console.log(`    Заказы: ${kw.orders}, Расход: ${(kw.sum / 100).toFixed(2)}₽`);
    });
  });

  return keywordStats;
}

Статистика поисковых фраз ​

typescript
async function getSearchPhraseStats(
  advertIds: number[],
  from: string,
  to: string
) {
  const stats = await sdk.promotion.getStatWords({
    id: advertIds,
    dates: { from, to }
  });

  console.log('Поисковые фразы:');

  stats.forEach(campaignStat => {
    console.log(`\nКампания ${campaignStat.advertId}:`);

    campaignStat.words.forEach(word => {
      console.log(`  Фраза: "${word.keyword}"`);
      console.log(`    Показы: ${word.views}`);
      console.log(`    Клики: ${word.clicks}`);
      console.log(`    Частота: ${word.freq}`);
    });
  });

  return stats;
}

Статистика автопродвижения ​

typescript
async function getAutoPromotionStats(
  advertIds: number[],
  from: string,
  to: string
) {
  const stats = await sdk.promotion.getAutoStatWords({
    id: advertIds,
    dates: { from, to }
  });

  console.log('Автопродвижение - статистика:');

  stats.forEach(campaignStat => {
    console.log(`\nКампания ${campaignStat.advertId}:`);

    campaignStat.words.forEach(word => {
      console.log(`  "${word.keyword}"`);
      console.log(`    Показы: ${word.views}, Клики: ${word.clicks}`);
    });
  });

  return stats;
}

Детализация по SKU ​

typescript
async function getSKUBreakdown(
  advertId: number,
  from: string,
  to: string
) {
  const stats = await sdk.promotion.getAdvFullstats({
    id: [advertId],
    dates: { from, to }
  });

  // Группировка по товарам
  const skuPerformance = new Map<number, {
    views: number;
    clicks: number;
    orders: number;
    spent: number;
    revenue: number;
  }>();

  stats.forEach(dayStat => {
    dayStat.nm?.forEach(nm => {
      const existing = skuPerformance.get(nm.nmId) || {
        views: 0, clicks: 0, orders: 0, spent: 0, revenue: 0
      };

      skuPerformance.set(nm.nmId, {
        views: existing.views + nm.views,
        clicks: existing.clicks + nm.clicks,
        orders: existing.orders + nm.orders,
        spent: existing.spent + nm.sum,
        revenue: existing.revenue + nm.sum_price
      });
    });
  });

  console.log('Эффективность по товарам:');

  // Сортировка по ROI
  const sorted = Array.from(skuPerformance.entries())
    .sort((a, b) => {
      const roiA = a[1].revenue / a[1].spent;
      const roiB = b[1].revenue / b[1].spent;
      return roiB - roiA;
    });

  sorted.forEach(([nmId, perf]) => {
    const roi = perf.revenue / perf.spent;
    const ctr = ((perf.clicks / perf.views) * 100).toFixed(2);

    console.log(`\nТовар ${nmId}:`);
    console.log(`  CTR: ${ctr}%`);
    console.log(`  Заказов: ${perf.orders}`);
    console.log(`  Расход: ${(perf.spent / 100).toFixed(2)}₽`);
    console.log(`  Выручка: ${(perf.revenue / 100).toFixed(2)}₽`);
    console.log(`  ROI: ${roi.toFixed(2)}`);
  });

  return skuPerformance;
}

Коды платформ ​

typescript
enum Platform {
  WEBSITE = 1,      // Основной сайт WB
  ANDROID = 32,     // Android приложение
  IOS = 64          // iOS приложение
}

async function getStatsByPlatform(
  advertId: number,
  from: string,
  to: string
) {
  const stats = await sdk.promotion.getAdvFullstats({
    id: [advertId],
    dates: { from, to }
  });

  const platformStats = {
    website: { views: 0, clicks: 0, orders: 0 },
    android: { views: 0, clicks: 0, orders: 0 },
    ios: { views: 0, clicks: 0, orders: 0 }
  };

  stats.forEach(dayStat => {
    dayStat.apps?.forEach(app => {
      switch (app.appType) {
        case 1:
          platformStats.website.views += app.views;
          platformStats.website.clicks += app.clicks;
          platformStats.website.orders += app.orders;
          break;
        case 32:
          platformStats.android.views += app.views;
          platformStats.android.clicks += app.clicks;
          platformStats.android.orders += app.orders;
          break;
        case 64:
          platformStats.ios.views += app.views;
          platformStats.ios.clicks += app.clicks;
          platformStats.ios.orders += app.orders;
          break;
      }
    });
  });

  console.log('Статистика по платформам:');
  console.log('Сайт:', platformStats.website);
  console.log('Android:', platformStats.android);
  console.log('iOS:', platformStats.ios);

  return platformStats;
}

Rate Limiting

Метод getAdvFullstats() имеет строгий лимит: 3 запроса в минуту с интервалом 20 секунд. Используйте для регулярного мониторинга getStatsKeywords() (240 зап/мин).


Маркетплейс-акции Wildberries ​

Отличие от рекламных кампаний ​

Акции WB — это маркетинговые мероприятия, организованные самим маркетплейсом (например, "Черная Пятница", "8 Марта"). Это НЕ рекламные кампании.

Ключевые отличия:

  • Акции организует Wildberries, не продавец
  • Участие требует соответствия критериям WB
  • Скидки устанавливаются по правилам акции
  • Доступ через отдельный API (/promo/*)

Календарь акций ​

typescript
async function getUpcomingPromotions() {
  const calendar = await sdk.promotion.getCalendarPromotions();

  console.log('Предстоящие акции:');

  calendar.data.promotions.forEach(promo => {
    console.log(`\n${promo.name}`);
    console.log(`  ID: ${promo.id}`);
    console.log(`  Даты: ${promo.startDateTime} - ${promo.endDateTime}`);
    console.log(`  Описание: ${promo.description}`);
  });

  return calendar.data.promotions;
}

Детали акции ​

typescript
async function getPromotionDetails(promotionId: number) {
  const details = await sdk.promotion.getPromotionsDetails({
    promotionID: promotionId
  });

  console.log('Детали акции:');
  console.log('  Название:', details.data.name);
  console.log('  Тип:', details.data.type);
  console.log('  Начало:', details.data.startDateTime);
  console.log('  Конец:', details.data.endDateTime);
  console.log('  Описание:', details.data.description);

  return details.data;
}

Номенклатура акции ​

typescript
async function getPromotionProducts(promotionId: number) {
  const products = await sdk.promotion.getPromotionsNomenclatures({
    promotionID: promotionId
  });

  console.log(`Товары в акции (всего: ${products.data.total}):`);

  products.data.nomenclatures.forEach(item => {
    console.log(`  Артикул: ${item.nmID}`);
    console.log(`    Размер: ${item.vendorCode}`);
    console.log(`    Скидка: ${item.discount}%`);
    console.log(`    Промокод: ${item.promoCode || 'Нет'}`);
  });

  return products.data.nomenclatures;
}

Загрузка товаров в акцию ​

typescript
async function uploadProductsToPromotion(
  promotionId: number,
  products: Array<{
    nm: number;       // Артикул
    stock: number;    // Остаток для акции
    price: number;    // Цена со скидкой (в копейках)
  }>
) {
  try {
    const result = await sdk.promotion.createPromotionsUpload({
      data: {
        promotionID: promotionId,
        nomenclatures: products.map(p => ({
          nm: p.nm,
          stock: p.stock,
          price: p.price
        }))
      }
    });

    console.log('✓ Товары загружены в акцию');
    console.log('Результат:', result);

    return result;

  } catch (error) {
    console.error('Ошибка загрузки:', error);
    throw error;
  }
}

Стратегия участия в акциях ​

typescript
async function participateInPromotion(promotionId: number) {
  // Шаг 1: Получение деталей акции
  const details = await sdk.promotion.getPromotionsDetails({
    promotionID: promotionId
  });

  console.log('Акция:', details.data.name);
  console.log('Период:', details.data.startDateTime, '-', details.data.endDateTime);

  // Шаг 2: Проверка номенклатуры
  const existingProducts = await sdk.promotion.getPromotionsNomenclatures({
    promotionID: promotionId
  });

  console.log('Уже участвует товаров:', existingProducts.data.total);

  // Шаг 3: Анализ складских остатков
  // (используйте API складов для проверки наличия)

  // Шаг 4: Расчет скидки
  const requiredDiscount = 20; // Пример: акция требует минимум 20%

  // Шаг 5: Формирование списка товаров
  const productsToAdd = [
    {
      nm: 123456,
      stock: 100,
      price: Math.round(1000 * 100 * (1 - requiredDiscount / 100)) // Цена - 20%
    },
    // ... другие товары
  ];

  // Шаг 6: Загрузка
  await uploadProductsToPromotion(promotionId, productsToAdd);

  console.log('✓ Участие в акции оформлено');
}

Различие API

Методы работы с акциями находятся в /promo/* эндпоинтах, а рекламные кампании — в /adv/*. Не путайте эти два разных функционала.


Стратегия Rate Limiting ​

Таблица лимитов ​

МетодЛимитИнтервалВсплеск
getAdvFullstats3 зап/мин20 сек3
getStatsKeywords240 зап/мин-240
createSeacatSaveAd1 зап/мин60 сек1
createBudgetDeposit10 зап/мин6 сек10
updateAdvBid60 зап/мин1 сек60
createSearchSetExcluded30 зап/мин2 сек30
getCampaigns60 зап/мин1 сек60

Обработка RateLimitError ​

typescript
import { RateLimitError } from 'daytona-wildberries-typescript-sdk';

async function handleRateLimiting<T>(
  operation: () => Promise<T>,
  maxRetries: number = 3
): Promise<T> {
  let attempt = 0;

  while (attempt < maxRetries) {
    try {
      return await operation();

    } catch (error) {
      if (error instanceof RateLimitError) {
        attempt++;
        const waitTime = error.retryAfter || 20000; // По умолчанию 20 сек

        console.warn(
          `Rate limit достигнут. Ожидание ${waitTime / 1000}с ` +
          `(попытка ${attempt}/${maxRetries})`
        );

        if (attempt >= maxRetries) {
          throw new Error('Превышено количество попыток');
        }

        await new Promise(resolve => setTimeout(resolve, waitTime));

      } else {
        throw error;
      }
    }
  }

  throw new Error('Операция не выполнена');
}

// Использование
const stats = await handleRateLimiting(() =>
  sdk.promotion.getAdvFullstats({
    id: [advertId],
    dates: { from, to }
  })
);

Батчинг запросов ​

typescript
async function batchCampaignStats(
  advertIds: number[],
  from: string,
  to: string
) {
  // Группируем по 10 кампаний (лимит API)
  const batches: number[][] = [];
  for (let i = 0; i < advertIds.length; i += 10) {
    batches.push(advertIds.slice(i, i + 10));
  }

  const allStats: any[] = [];

  for (const batch of batches) {
    // Ждем 20 секунд между батчами (лимит getAdvFullstats)
    if (allStats.length > 0) {
      console.log('Ожидание 20 секунд...');
      await new Promise(resolve => setTimeout(resolve, 20000));
    }

    const stats = await sdk.promotion.getAdvFullstats({
      id: batch,
      dates: { from, to }
    });

    allStats.push(...stats);
    console.log(`Обработано ${allStats.length}/${advertIds.length} кампаний`);
  }

  return allStats;
}

Приоритизация запросов ​

typescript
class RequestQueue {
  private queue: Array<{
    priority: number;
    operation: () => Promise<any>;
    resolve: (value: any) => void;
    reject: (error: any) => void;
  }> = [];

  private processing = false;

  async add<T>(
    operation: () => Promise<T>,
    priority: number = 0
  ): Promise<T> {
    return new Promise((resolve, reject) => {
      this.queue.push({ priority, operation, resolve, reject });
      this.queue.sort((a, b) => b.priority - a.priority);

      if (!this.processing) {
        this.process();
      }
    });
  }

  private async process() {
    if (this.queue.length === 0) {
      this.processing = false;
      return;
    }

    this.processing = true;
    const item = this.queue.shift()!;

    try {
      const result = await item.operation();
      item.resolve(result);
    } catch (error) {
      item.reject(error);
    }

    // Задержка между запросами
    await new Promise(resolve => setTimeout(resolve, 1000));

    this.process();
  }
}

// Использование
const queue = new RequestQueue();

// Высокий приоритет - проверка баланса
const balance = await queue.add(
  () => sdk.promotion.getAdvBalance(),
  10
);

// Низкий приоритет - статистика
const stats = await queue.add(
  () => sdk.promotion.getAdvFullstats({ id: [123], dates: { from, to } }),
  1
);

Обработка ошибок ​

Типичные ошибки и решения ​

typescript
async function robustCampaignOperation(advertId: number) {
  try {
    await sdk.promotion.startCampaign({ id: advertId });

  } catch (error: any) {
    // Ошибка 1: Неправильный статус
    if (error.message?.includes('status')) {
      console.error('Ошибка: Кампания не в нужном статусе');
      console.log('Решение: Проверьте текущий статус через getCampaigns()');

      const campaigns = await sdk.promotion.getCampaigns();
      const campaign = campaigns.adverts.find(a => a.advertId === advertId);
      console.log('Текущий статус:', campaign?.status);

      // Автоматическое исправление
      if (campaign?.status === 11) {
        console.log('Кампания на паузе, возобновляем...');
        await sdk.promotion.startCampaign({ id: advertId });
      }
    }

    // Ошибка 2: Недостаточно средств
    if (error.message?.includes('balance') || error.message?.includes('budget')) {
      console.error('Ошибка: Недостаточно средств');
      console.log('Решение: Пополните бюджет кампании');

      const budget = await sdk.promotion.getAdvBudget({ id: advertId });
      console.log('Текущий баланс:', budget.balance / 100, '₽');
      console.log('Требуется минимум:', budget.dailyBudget / 100, '₽');
    }

    // Ошибка 3: Rate limit
    if (error instanceof RateLimitError) {
      console.error('Ошибка: Превышен лимит запросов');
      console.log(`Решение: Подождите ${error.retryAfter / 1000} секунд`);

      await new Promise(resolve => setTimeout(resolve, error.retryAfter));
      return robustCampaignOperation(advertId); // Повтор
    }

    // Ошибка 4: Кампания не найдена
    if (error.status === 404) {
      console.error('Ошибка: Кампания не найдена');
      console.log('Решение: Проверьте ID кампании');
    }

    // Ошибка 5: Неверный API ключ
    if (error.status === 401 || error.status === 403) {
      console.error('Ошибка: Проблемы с авторизацией');
      console.log('Решение: Проверьте API ключ и его права');
    }

    throw error;
  }
}

Паттерн обработки ​

typescript
async function safePromotionOperation<T>(
  operation: () => Promise<T>,
  context: string
): Promise<T | null> {
  const maxRetries = 3;
  let lastError: any;

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await operation();

    } catch (error: any) {
      lastError = error;
      console.error(`[${context}] Попытка ${attempt}/${maxRetries} не удалась`);

      // Не повторяем для ошибок валидации
      if (error.status === 400 || error.status === 422) {
        console.error('Ошибка валидации, повтор невозможен');
        break;
      }

      // Не повторяем для ошибок авторизации
      if (error.status === 401 || error.status === 403) {
        console.error('Ошибка авторизации, повтор невозможен');
        break;
      }

      // Ждем перед повтором
      if (attempt < maxRetries) {
        const delay = Math.pow(2, attempt) * 1000; // Экспоненциальная задержка
        console.log(`Ожидание ${delay / 1000}с перед повтором...`);
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }

  console.error(`[${context}] Все попытки исчерпаны:`, lastError);
  return null;
}

// Использование
const result = await safePromotionOperation(
  () => sdk.promotion.startCampaign({ id: 123 }),
  'Запуск кампании'
);

if (!result) {
  console.error('Не удалось запустить кампанию');
}

Полный рабочий пример ​

Сквозной пример создания и управления рекламной кампанией:

typescript
import { WildberriesSDK, RateLimitError } from 'daytona-wildberries-typescript-sdk';

const sdk = new WildberriesSDK({
  apiKey: process.env.WB_API_KEY!
});

async function fullCampaignLifecycle() {
  console.log('=== Полный жизненный цикл рекламной кампании ===\n');

  // ШАГ 1: Проверка баланса рекламного счета
  console.log('1. Проверка баланса...');
  const balance = await sdk.promotion.getAdvBalance();
  console.log(`   Баланс: ${balance.balance / 100}₽`);

  if (balance.balance < 500000) {
    console.warn('   ⚠ Низкий баланс. Рекомендуется пополнить счет.');
  }

  // ШАГ 2: Создание кампании
  console.log('\n2. Создание кампании...');
  const campaign = await sdk.promotion.createSeacatSaveAd({
    type: 9,
    name: 'Тестовая кампания - Электроника',
    subjectId: 566,
    nms: [123456, 789012],

    params: [{
      subjectId: 566,
      sets: [6, 8], // Поиск + рекомендации
      active: true
    }],

    dailyBudget: 10000, // 100₽ в день
    unitedParams: [{
      subjectId: 566,
      sets: [6, 8],
      bid: 300 // CPM ставка 3₽
    }]
  });

  const advertId = campaign.advertId;
  console.log(`   ✓ Кампания создана: ID ${advertId}`);
  console.log(`   Статус: ${campaign.status} (READY)`);

  // ШАГ 3: Получение минимальных ставок
  console.log('\n3. Проверка минимальных ставок...');
  const minBids = await sdk.promotion.createBidsMin({
    advert_id: advertId,
    nm_ids: [123456, 789012],
    payment_type: 1, // CPM
    placement_types: [6, 8]
  });

  minBids.forEach(bid => {
    const searchBid = bid.bids.find(b => b.type === 6);
    const recBid = bid.bids.find(b => b.type === 8);
    console.log(`   Товар ${bid.nm_id}:`);
    console.log(`     Поиск: от ${searchBid?.bid / 100}₽`);
    console.log(`     Рекомендации: от ${recBid?.bid / 100}₽`);
  });

  // ШАГ 4: Пополнение бюджета
  console.log('\n4. Пополнение бюджета кампании...');
  await sdk.promotion.createBudgetDeposit(
    { sum: 100000, return: false }, // 1000₽
    { id: advertId }
  );
  console.log('   ✓ Пополнено 1000₽');
  console.log('   Статус автоматически изменен на PAUSED (11)');

  await new Promise(resolve => setTimeout(resolve, 2000));

  // ШАГ 5: Проверка бюджета
  const budget = await sdk.promotion.getAdvBudget({ id: advertId });
  console.log('   Баланс кампании:', budget.balance / 100, '₽');

  // ШАГ 6: Запуск кампании
  console.log('\n5. Запуск кампании...');
  await sdk.promotion.startCampaign({ id: advertId });
  console.log('   ✓ Кампания запущена (статус ACTIVE - 9)');

  await new Promise(resolve => setTimeout(resolve, 2000));

  // ШАГ 7: Добавление ключевых фраз
  console.log('\n6. Добавление ключевых фраз...');
  await sdk.promotion.createSearchSetPlu({
    id: advertId,
    pluse: ['смартфон', 'телефон', 'мобильный']
  });
  console.log('   ✓ Добавлено 3 ключевых фразы');

  // ШАГ 8: Добавление минус-фраз
  console.log('\n7. Добавление минус-фраз...');
  await sdk.promotion.createSearchSetExcluded({
    id: advertId,
    excluded: ['дешевый', 'китайский', 'подделка']
  });
  console.log('   ✓ Добавлено 3 минус-фразы');

  // ШАГ 9: Ожидание накопления статистики
  console.log('\n8. Ожидание накопления статистики...');
  console.log('   (В реальном сценарии подождите несколько часов)');

  // Имитация
  await new Promise(resolve => setTimeout(resolve, 3000));

  // ШАГ 10: Мониторинг статистики
  console.log('\n9. Получение статистики...');
  const today = new Date().toISOString().split('T')[0];

  try {
    const stats = await sdk.promotion.getAdvFullstats({
      id: [advertId],
      dates: { from: today, to: today }
    });

    if (stats.length > 0) {
      const todayStats = stats[0];
      console.log('   Статистика за сегодня:');
      console.log(`     Показы: ${todayStats.views}`);
      console.log(`     Клики: ${todayStats.clicks}`);
      console.log(`     CTR: ${((todayStats.clicks / todayStats.views) * 100).toFixed(2)}%`);
      console.log(`     Расход: ${(todayStats.sum / 100).toFixed(2)}₽`);
      console.log(`     Заказы: ${todayStats.orders}`);

      if (todayStats.orders > 0) {
        const roi = todayStats.sum_price / todayStats.sum;
        console.log(`     ROI: ${roi.toFixed(2)}`);
      }
    } else {
      console.log('   Нет данных (кампания только запущена)');
    }
  } catch (error) {
    if (error instanceof RateLimitError) {
      console.log('   Rate limit - используем getStatsKeywords');

      const keywordStats = await sdk.promotion.getStatsKeywords({
        id: [advertId],
        dates: { from: today, to: today }
      });

      console.log(`   Статистика по ${keywordStats.length} периодам`);
    }
  }

  // ШАГ 11: Оптимизация ставок
  console.log('\n10. Оптимизация ставок...');

  const campaigns = await sdk.promotion.getCampaigns();
  const currentCampaign = campaigns.adverts.find(a => a.advertId === advertId);
  const currentBid = currentCampaign?.bid || 300;

  // Увеличиваем ставку на 10%
  const newBid = Math.round(currentBid * 1.1);

  await sdk.promotion.updateAdvBid({
    bids: [{ id: advertId, bid: newBid }]
  });

  console.log(`   ✓ Ставка обновлена: ${currentBid / 100}₽ → ${newBid / 100}₽`);

  // ШАГ 12: Пауза кампании
  console.log('\n11. Приостановка кампании...');
  await sdk.promotion.pauseCampaign({ id: advertId });
  console.log('   ✓ Кампания на паузе (статус PAUSED - 11)');

  await new Promise(resolve => setTimeout(resolve, 2000));

  // ШАГ 13: Возобновление
  console.log('\n12. Возобновление кампании...');
  await sdk.promotion.startCampaign({ id: advertId });
  console.log('   ✓ Кампания возобновлена (статус ACTIVE - 9)');

  await new Promise(resolve => setTimeout(resolve, 2000));

  // ШАГ 14: Остановка
  console.log('\n13. Остановка кампании...');
  await sdk.promotion.stopCampaign({ id: advertId });
  console.log('   ✓ Кампания остановлена (статус COMPLETED - 7)');

  console.log('\n=== Жизненный цикл завершен ===');
  console.log(`ID кампании: ${advertId}`);
  console.log('Все операции выполнены успешно');
}

// Запуск примера
fullCampaignLifecycle()
  .then(() => console.log('\n✓ Пример выполнен'))
  .catch(error => console.error('\n✗ Ошибка:', error));

Связанные ресурсы ​

Дополнительные гайды:

Официальная документация WB:


Последнее обновление: 2024-01-19 Версия SDK: 2.6.0

Made with ❤️ for the Wildberries developer community