Skip to main content
This page is also available in English.

Огляд

Цей реліз додає дві основні групи ендпоінтів:
  1. Google Ads Management CRUD — створення, оновлення та видалення груп оголошень, оголошень (RSA), ключових слів, розкладу показів та таргетингу за локацією
  2. Редагування активних кампаній — читання та оновлення налаштувань вже опублікованих кампаній Google Ads
Також є кілька змін в існуючих GET ендпоінтах, на які варто звернути увагу.

Зміни в існуючих ендпоінтах

Параметр login_customer_id більше не потрібний

Параметр login_customer_id більше не використовується жодним ендпоінтом Google Ads. Бекенд тепер автоматично визначає його з бази даних на основі customer_id. Якщо ви зараз передаєте login_customer_id — нічого не зламається, він просто ігнорується. Проте рекомендуємо видалити його з API-викликів для чистоти коду, оскільки він більше не має жодного ефекту.

Новий фільтр campaign_id на /ads

Ендпоінт GET /ads тепер приймає необов’язковий параметр campaign_id для фільтрації оголошень за кампанією. Це на додаток до існуючого фільтра ad_group_id.
GET /api/v1/google-ads/{customer_id}/ads?campaign_id=123456

Послаблення фільтрів за статусом

Ендпоінти таблиць даних, які раніше повертали лише ENABLED ресурси, тепер також повертають PAUSED ресурси. Це стосується кампаній, груп оголошень, оголошень та ключових слів. Фронтенд має бути готовий відображати обидва статуси.

Серверний пошук (?search=)

Усі GET ендпоінти таблиць даних тепер підтримують параметр search. Див. окремий гайд Пошук по таблицях для повної специфікації API (поля пошуку по кожній таблиці, правила параметрів тощо). Цей розділ фокусується на реалізації пошуку на фронтенді, базуючись на тому, як це вже працює в admin.cattix.com.

Два рівні пошуку

Адмін-панель реалізує пошук на двох рівнях:
  1. Пошуковий рядок у кожній таблиці — текстове поле над таблицею, яке фільтрує конкретну таблицю через ?search=<term>
  2. Глобальний пошуковий діалог (Cmd+K) — відправляє той самий ?search= запит до всіх 7 таблиць паралельно, показуючи результати згруповані за категоріями
Можна реалізувати один або обидва варіанти.

Рекомендований паттерн реалізації

1. Debounce вводу (300мс)

Не робіть API-виклик на кожне натискання клавіші. В адмінці використовується debounce 300мс:
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null)

const onSearchChange = (value: string) => {
  setQuery(value)
  if (debounceRef.current) clearTimeout(debounceRef.current)

  if (!value.trim()) {
    // Очистити результати одразу коли поле порожнє
    resetResults()
    return
  }

  debounceRef.current = setTimeout(() => {
    fetchWithSearch(value)
  }, 300)
}

2. Захист від застарілих результатів (race condition)

Якщо користувач набирає “sho”, а потім швидко змінює на “brand”, відповідь на “sho” може прийти після “brand”. Використовуйте search-ID ref для відкидання застарілих відповідей:
const searchIdRef = useRef(0)

const fetchWithSearch = (query: string) => {
  const currentId = ++searchIdRef.current

  api.getCampaigns({ customer_id, search: query }).then((res) => {
    if (searchIdRef.current !== currentId) return // застаріло — ігноруємо
    setResults(res.data)
  })
}

3. Для глобального пошуку — паралельні запити

В адмінці всі 7 запитів категорій відправляються незалежно (не через Promise.all), тому результати з’являються поступово:
const CATEGORIES = [
  "campaigns", "ad-groups", "ads", "keywords",
  "search-terms", "ad-schedule", "locations",
] as const

for (const category of CATEGORIES) {
  api[category]({ customer_id, search: query, limit: 5 })
    .then((res) => {
      if (searchIdRef.current !== currentId) return
      setState((prev) => ({
        ...prev,
        [category]: { loading: false, results: extract(category, res) },
      }))
    })
    .catch((err) => {
      if (searchIdRef.current !== currentId) return
      setState((prev) => ({
        ...prev,
        [category]: { loading: false, error: err.message, results: [] },
      }))
    })
}
Це дає приємний ефект поступового завантаження з індикатором прогресу (“Пошук… 3/7 завантажено”).

4. Стани UI для обробки

СтанЩо показувати
Клієнт не вибранийВимкнене поле вводу, підказка вибрати акаунт
Порожній запитПлейсхолдер (“Пошук кампаній, оголошень, ключових слів…”)
ЗавантаженняСкелетон-рядки або спінер на кожну категорію
Часткові результатиПоказати готові категорії, скелетон для очікуваних
Немає результатів”Немає результатів для ‘запит‘“
ПомилкаПовідомлення про помилку для кожної категорії

5. Навігація з глобального пошуку до таблиці

Коли користувач обирає результат у діалозі, перенаправте на сторінку з таблицею з передзаповненим пошуком:
const handleSelect = (category: string) => {
  router.push(`/google-ads-data?tab=${category}&search=${encodeURIComponent(query)}`)
  closeDialog()
}
На сторінці даних зчитайте search з URL-параметрів і автоматично зробіть запит при завантаженні.

Референсна реалізація

Повна робоча реалізація знаходиться в admin.cattix.com:
ФайлПризначення
src/hooks/use-global-search.tsХук пошуку з debounce, паралельними запитами, захистом від race condition
src/components/global-search-dialog.tsxUI діалогу Cmd+K з cmdk
src/lib/api/google-ads.tsAPI-клієнт, що передає параметр search
src/app/admin/google-ads-data/page.tsxСторінка даних, що зчитує ?search= з URL

Усі ендпоінти управління знаходяться під префіксом /api/v1/google-ads/ і потребують параметра customer_id. Базовий шаблон URL:
POST/PATCH/PUT/DELETE /api/v1/google-ads/{ресурс}?customer_id={customer_id}
Усі ендпоінти потребують заголовок Authorization: Bearer <token>.

Групи оголошень

Створити групу оголошень (Swagger)

POST /api/v1/google-ads/ad-groups?customer_id={customer_id}
ПолеТипОбов’язковеОпис
campaign_idintegerТакID кампанії
namestringТакНазва (1-256 символів)
ad_group_typestringНіSEARCH_STANDARD (за замовч.), DISPLAY_STANDARD, SHOPPING_PRODUCT_ADS
statusstringНіENABLED (за замовч.) або PAUSED
cpc_bid_microsintegerНіCPC ставка в мікро (1 000 000 = $1.00)
Відповідь: AdGroupMutationResult
{
  "success": true,
  "ad_group_id": 123456789,
  "resource_name": "customers/1234567890/adGroups/123456789",
  "error_code": null,
  "error_message": null
}

Оновити статус групи оголошень (Swagger)

PATCH /api/v1/google-ads/ad-groups/{ad_group_id}/status?customer_id={customer_id}
Тіло: { "status": "ENABLED" | "PAUSED" }

Видалити групу оголошень (Swagger)

DELETE /api/v1/google-ads/ad-groups/{ad_group_id}?customer_id={customer_id}

Оголошення (RSA)

Створити RSA (Swagger)

POST /api/v1/google-ads/ads?customer_id={customer_id}
ПолеТипОбов’язковеОпис
ad_group_idintegerТакID групи оголошень
final_urlstringТакURL посадкової сторінки
headlinesRSAAssetInput[]Так3-15 заголовків (text + опціональний pinned_field)
descriptionsRSAAssetInput[]Так2-4 описи
display_path_1stringНіМакс. 15 символів
display_path_2stringНіМакс. 15 символів
final_mobile_urlstringНіМобільний URL
tracking_url_templatestringНіШаблон трекінгу

Оновити RSA (Swagger)

PATCH /api/v1/google-ads/ads/{ad_id}?customer_id={customer_id}
Усі поля необов’язкові. Коли headlines або descriptions надані, вони замінюють усі існуючі.

Видалити оголошення (Swagger)

DELETE /api/v1/google-ads/ads/{ad_group_id}/{ad_id}?customer_id={customer_id}

Ключові слова

Створити ключові слова (Swagger)

POST /api/v1/google-ads/keywords?customer_id={customer_id}
ПолеТипОбов’язковеОпис
ad_group_idintegerТакID групи оголошень
keywordsKeywordInput[]ТакМасив: text (обов.) + match_type (EXACT, PHRASE, BROAD, за замовч. PHRASE)

Оновити ключове слово (Swagger)

PATCH /api/v1/google-ads/keywords/{ad_group_id}/{criterion_id}?customer_id={customer_id}
ПолеТипОпис
statusstringENABLED або PAUSED
cpc_bid_microsintegerНова CPC ставка в мікро

Видалити ключові слова (пакетно) (Swagger)

POST /api/v1/google-ads/keywords/remove?customer_id={customer_id}

Розклад показів

Додати записи розкладу (Swagger)

POST /api/v1/google-ads/ad-schedule?customer_id={customer_id}
ПолеТипОбов’язковеОпис
campaign_idintegerТакID кампанії
entriesAdScheduleEntryInput[]Такday_of_week, start_hour (0-23), start_minute, end_hour (0-24), end_minute

Замінити весь розклад (Swagger)

PUT /api/v1/google-ads/ad-schedule?customer_id={customer_id}
Те саме тіло. Замінює всі існуючі записи розкладу.

Видалити записи розкладу (Swagger)

POST /api/v1/google-ads/ad-schedule/delete?customer_id={customer_id}
Тіло: { "campaign_id": ..., "resource_names": [...] }

Таргетинг за локацією

Додати локації (Swagger)

POST /api/v1/google-ads/locations/targeting?customer_id={customer_id}
ПолеТипОбов’язковеОпис
campaign_idintegerТакID кампанії
locationsLocationTargetingEntry[]Такgeo_target_constant_id + target_type (INCLUDE/EXCLUDE)

Замінити всі локації (Swagger)

PUT /api/v1/google-ads/locations/targeting?customer_id={customer_id}

Видалити таргетинг локацій (Swagger)

POST /api/v1/google-ads/locations/targeting/delete?customer_id={customer_id}

Редагування активних кампаній

Ці ендпоінти дозволяють читати та оновлювати налаштування вже опублікованих кампаній, під префіксом /api/v1/campaigns/live/.

Отримати налаштування кампанії (Swagger)

GET /api/v1/campaigns/live/{customer_id}/{campaign_id}
Повертає повні налаштування кампанії для UI редагування:
{
  "campaign_id": 123456789,
  "campaign_name": "Brand Campaign",
  "status": "ENABLED",
  "daily_budget_micros": 50000000,
  "budget_resource_name": "customers/123/campaignBudgets/456",
  "bidding_strategy": { ... },
  "network_settings": { ... },
  "start_date": "2025-01-15",
  "end_date": null,
  "language_ids": [1000],
  "location_targets": [ ... ],
  "ad_schedule": { ... },
  "conversion_goals": [ ... ]
}

Оновити налаштування кампанії (Swagger)

PATCH /api/v1/campaigns/live/{customer_id}/{campaign_id}
Усі поля необов’язкові — оновлюються лише надані поля.
Поля таргетингу (language_ids, location_targets, ad_schedule, conversion_goals) використовують семантику повної заміни. Коли будь-яке з цих полів надано, існуючі значення повністю замінюються. Якщо поле пропущено, воно залишається без змін.
ПолеТипОпис
campaign_namestringНова назва (1-256 символів)
statusstringENABLED або PAUSED
daily_budget_microsintegerДенний бюджет в мікро
bidding_strategyobjectСтратегія ставок
network_settingsobjectНалаштування мереж
language_idsinteger[]Мови (повна заміна)
location_targetsobject[]Локації (повна заміна)
ad_scheduleobjectРозклад (повна заміна)
start_datestringДата початку (YYYY-MM-DD)
end_datestringДата завершення (YYYY-MM-DD)
conversion_goalsobject[]Цілі конверсій (повна заміна)
Відповідь:
{
  "success": true,
  "campaign_updated": true,
  "budget_updated": true,
  "targeting_updated": false,
  "conversion_goals_updated": false,
  "warnings": [],
  "errors": []
}

Видалити кампанію (Swagger)

DELETE /api/v1/campaigns/live/{customer_id}/{campaign_id}
Повертає 204 No Content. Це незворотна дія.

Контрольний список міграції

1

Реалізуйте серверний пошук

Це, ймовірно, найбільше завдання для FE. Додайте пошуковий рядок у кожну таблицю та, опціонально, глобальний діалог пошуку Cmd+K. Див. гайд з реалізації вище для паттернів (debounce, захист від race condition, поступове завантаження) та референсний код admin.cattix.com.
2

Обробляйте PAUSED ресурси в таблицях

Таблиці тепер повертають і ENABLED, і PAUSED ресурси. Додайте візуальне розрізнення для призупинених елементів.
3

Побудуйте CRUD UI для груп оголошень, оголошень, ключових слів

Використовуйте нові ендпоінти управління для реалізації дій створення/редагування/видалення в таблицях.
4

Побудуйте сторінку редагування кампанії

Використовуйте GET /campaigns/live/{cid}/{campaign_id} для заповнення форми та PATCH для збереження змін.
5

Додайте фільтр campaign_id до таблиці оголошень

Ендпоінт /ads тепер підтримує ?campaign_id= для фільтрації.
6

Приберіть login_customer_id з API-викликів (необов'язково)

Він більше не використовується і буде проігнорований, але його видалення зробить код чистішим.