API-ключі

API-ключ - це облікові дані вашого акаунта для Leadgram API. Керуються вони в налаштуваннях: тут ви створюєте ключ, даєте йому ім'я і відкликаєте, коли потрібно. Розберемо, що ключ уміє і чого не вміє, як його створити, як видати йому права, як надіслати ним подію і як його ротувати чи відкликати.

Навіщо потрібні API-ключі

API-ключ дозволяє зовнішньому інструменту чи скрипту репортити конверсії в Leadgram від вашого імені, не передаючи йому логін чи сесійну куку. Це механізм для server-to-server інтеграцій: партнерська мережа, CRM рекламодавця чи власний скрипт надсилають подію в момент депозиту, а не людина клікає в дашборді. Для використання в браузері ключ не призначений.

Створення ключа

  1. Відкрийте Налаштування → API-ключі.
  2. Дайте ключу назву, за якою впізнаєте його пізніше, наприклад «Keitaro» чи «скрипт звітів» - це єдине поле у формі.
  3. Натисніть Створити ключ. Секрет показується рівно один раз, одразу після створення, у блоці для копіювання - скопіюйте його зараз же. Ні таблиця ключів, ні жоден запит більше не покажуть повне значення - зберігаються лише назва і дата створення.
Панель API-ключів з діалогом створення ключаПанель API-ключів з діалогом створення ключа

Що ключ може і чого не може

Ключ належить вашому обліковому запису, але робоча область у нього не з'являється сама. Поки ключу жодного разу не зберігали права, він не прив'язаний до жодної області: організацію для нього виводить найраніше живе членство власника, а не та область, що зараз відкрита у вас у дашборді. З однією областю різниці немає, з двома і більше - події поїдуть у найстаршу. Тому в акаунті, де областей більше однієї, видайте ключу права з явно вибраною областю (кнопка Права в рядку ключа) ще до того, як віддасте секрет інтеграції.

Повноцінним доступом до дашборда ключ не стає ні за яких прав:

  • Можна з коробки: надсилати події на POST /api/events. Це єдине, що відкрито ключу без окремого права, і тільки доти, доки права цьому ключу жодного разу не зберігали.
  • Можна видати окремо: читання звітів і стрічки кліків; приймання конверсій (слаг events); створення кампаній, оферів, лендингів, воронок і Click Flows, а перемикання статусу - тільки в оферів і лендингів (архівування і повернення з архіву) та в Click Flows (зняття з трафіку й архівування): окремого права на статус у кампаній і воронок немає зовсім; окремим рівнем - правку, публікацію, витрати, правила постбеків і видалення. Кожне право видається конкретному ключу, прив'язане до однієї робочої області і типово вимкнене: у щойно випущеного ключа не позначено нічого.
  • Не видається нічим: боти та їхні токени, канали, рекламні кабінети, розсилки підписникам, білінг і поповнення, учасники команди, видалення організації, зміна пароля й пошти, відкликання сесій, кастомні домени, тестове надсилання правила постбеку і тестова подія в рекламний кабінет, повторне надсилання вже зробленої доставки, а ще самі права ключа - ключ, здатний розширити себе, це захоплення акаунта, а не зручність. Запит із ключем до /api/auth/* отримує 403 завжди, хоч би що власнику ключа було доступно в браузері.

Права відкривають ключу дві поверхні понад приймання конверсій. Перша - читання по HTTP: GET /api/v1/clicks і GET /api/v1/report. Друга - MCP-сервер на POST https://leadgram.org/api/mcp, 25 інструментів, якими розмовний клієнт (Claude, Cursor чи інший хост MCP) створює кампанії, читає звіти й репортить конверсії вашими ж правами. Автентифікація там теж тільки x-api-key, куки дашборда не приймаються, а каталог інструментів однаковий для всіх: відмова за невиданим правом приходить на виклику інструмента і називає, якого саме слага бракує. Параметри, коди відповіді й ліміти обох поверхонь - у Довіднику API.

Виняток для ключів без прав на MCP не поширюється. Ключ, якому права не видавали жодного разу, шле конверсії по HTTP, але той самий ключ у MCP отримає 403 з code: api_key_write_not_granted, поки йому не видали events.

Розподілу прав за учасниками команди в поточному інтерфейсі немає: якщо доступ до інтеграцій потрібен кільком людям чи системам, створіть ключ на кожну окремо - так ви відрізните їх у таблиці і зможете відкликати один, не зламавши інші.

Видача прав

  1. Відкрийте Налаштування → API-ключі і натисніть Права в рядку потрібного ключа.
  2. У діалозі Права ключа виберіть Робочу область: ключ працює тільки в ній, і без неї зберігати права нікуди.
  3. Позначте потрібні слаги в блоках Читання, Запис і Видалення та незворотне.
  4. Щоб додати право з блоку Видалення та незворотне, введіть DELETE у полі Підтвердження: цей рівень замкнено окремо. Щоб зняти вже видане, підтвердження не потрібне.
  5. За потреби перемкніть Кампанії на Обмежити кампаніями і виберіть ті, у межах яких ключ працюватиме.
  6. Натисніть Зберегти права. Блок «Зараз видано» показує результат, а кнопка Зняти всі права знімає видане цілком - разом із прийманням конверсій: після неї ключ не несе нічого, включно з events, і в стан щойно випущеного ключа, який конверсії ще приймає, не повертається.

Збереження замінює попередні права цілком: це повний перелік того, що ключ уміє, а не додавання до вже виданого. Хочете додати читання і не втратити запис - позначте і те, і те в одному збереженні.

Ось що саме можна позначити. Слаги машинні і не перекладаються: ці самі рядки стоять у редакторі, у кодах відмов і в Довіднику API.

  • Читання: report - зведена воронка GET /api/v1/report; clicks - посторінкова стрічка кліків GET /api/v1/clicks.
  • Запис: campaigns, offers, landings, funnels, click-flows - створення; offers:status, landings:status, click-flows:status - перемикання статусу, тобто архівування і повернення з архіву; events - приймання конверсій.
  • Видалення та незворотне: campaigns:update, offers:update, landings:update, funnels:update, click-flows:update - заміна сутності цілком; campaigns:delete, offers:delete, landings:delete, funnels:delete, click-flows:delete - видалення; click-flows:publish - вивід графа в живий трафік; postbacks, postbacks:update, postbacks:delete - створення, правка й видалення правил постбеків; ad-spend - запис рекламної витрати.

По HTTP працює кожне з цих прав, а MCP-інструмент є у всіх, крім одного: у click-flows:update його немає зовсім - парного читання графа за ключем не існує, і розмовному клієнту лишилося б тільки затирати чужий граф наосліп.

Другий рівень читається як «незворотне за наслідками», а не як HTTP-метод DELETE. У ньому лежать заміна оферу й лендинга цілком (не передані поля очищаються, а прочитати поточні значення ключем не можна), публікація графа в живий трафік, створення правила постбеку - воно стріляє на кожній майбутній конверсії на довільну зовнішню адресу - і запис рекламної витрати, у якої порожня сума видаляє рядок. Ярлика «оборотне» в першого рівня при цьому немає: його найгірша гілка - events, а приймання конверсії списує гроші області і надсилає подію в підключений рекламний кабінет, звідки її вже не забрати.

Обмеження за кампаніями доїжджає не до кожного права. Звузити можна читання, приймання конверсій, усе про кампанії та постбеки, запис витрати, видалення воронки і Click Flows, крім click-flows:update. Офери, лендинги, створення й перейменування воронок і заміна графа цілком спільні на всю робочу область за будовою моделі, тож набір, де одночасно позначено таке право і обмеження за кампаніями, не зберігається зовсім: редактор скаже про це окремим рядком, а прямий запит до API отримає 400 з grant_scope_unsupported_resource. Читання під цю перевірку не потрапляє, і звуження до нього застосовується по-справжньому: report і clicks віддадуть тільки вибрані кампанії.

Перше збереження вимикає виняток для ключів без прав, і це найдорожча пастка на цій сторінці. Ключ, якому права не видавали жодного разу, приймає конверсії без усякого гранта. Щойно ви зберегли права - будь-які - працює рівно те, що позначено, а приймання конверсій вимагає позначеного events. Зайшли в діалог по читання звітів, зберегли самий тільки report - і робоча інтеграція, яка щохвилини слала депозити, почала отримувати 403. Редактор попереджає про це в діалозі; позначайте events явно.

Надсилання події: POST /api/events

Ключ передається в заголовку x-api-key:

curl -X POST https://leadgram.org/api/events \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "clickId": "tz4k9m2p1xq7v0b3n8s6d5fg",
    "type": "deposit",
    "payout": 42.50,
    "nonce": "crm-conversion-8813",
    "payload": { "telegram_user_id": 123456789 }
  }'

Поля тіла:

  • clickId - обов'язкове. Id кліку, який видав Leadgram: це те значення, що приходить до вас із макроса {click_id} в URL оферу, редиректу кампанії чи лендингу. Клік чужої організації не підійде.
  • type - обов'язкове, одне з: click, land, bot_start, channel_join, join_request, first_dm, miniapp_launch, lead, registration, deposit, ftd, purchase, subscription_renewed, subscription_cancelled, custom_1-custom_8. Приймати тип і виробляти його - різні речі: у click, land і first_dm усередині Leadgram продюсера немає, вони існують лише тому, що ви їх надіслали; а в bot_start, channel_join, join_request, miniapp_launch, purchase й обох subscription_* продюсер усередині Leadgram уже є - надішлете їх вручну, поруч з автоматичною з'явиться друга подія. Єдиний виняток - bot_start, і він з умовами: ваш виклик зійдеться з уже записаним стартом, лише якщо несе telegram user id у payload (без нього - 422, bot_start_missing_telegram_user_id) і в запиті є чим назвати бота, якого запустила людина - payload.bot_username чи payload.bot_id, а якщо їх немає, то бот названого clickId. У кліку зі звичайної редирект-кампанії бота немає, як і в кліку, чийого бота видалили: такий виклик без bot_username/bot_id пару назвати не може і запишеться окремою подією. Другого списання це вам не коштує - Leadgram списує один раз за людину на робочий простір, а Telegram-акаунт упізнає цю людину завжди, тож дубль безкоштовний, - але в Meta піде другий Lead з іншим event_id, звести який жодна дедуплікація вже не зможе, і оптимізатор вашої кампанії вивчить, що одна людина сконвертувалася двічі. Подробиці в Подіях і конверсіях.
  • payout - необов'язкове. Виплата за цією конверсією: число від 0 до 1 000 000 (рядок на кшталт "42.50" теж приймається, від'ємні значення відхиляються). Якщо поле не передати, конверсія успадкує «Виплату» оферу, прив'язаного до кліку.
  • nonce - необов'язкове, від 1 до 128 символів. Ключ ідемпотентності: id події виводиться з трійки clickId + type + nonce, тож повтор того самого запиту не створить другий рядок і не задвоїть ні списання, ні постбеки, ні конверсії в рекламних кабінетах. Зворотний бік: дві справді різні конверсії одного кліку (другий депозит) мають надіслати різний nonce. Замість поля можна передати заголовок Idempotency-Key - він підхоплюється як запасне джерело того самого значення.
  • payload - необов'язковий об'єкт довільної форми, але три ключі всередині нього платформа розбирає окремо. За telegram_user_id рахується білінг і працює дедуплікація постбеків за користувачем. bot_username і bot_id стосуються лише bot_start і називають бота, якого людина справді запустила: @ім'я (зі знаком @ чи без) або внутрішній id бота в Leadgram. Обидва необов'язкові, але їх варто надсилати, щойно у вас більше одного бота: без них пара спирається на бота з кліку, а це здогадка, і вона хибна, коли клік прийшов із кампанії з іншим ботом. Ім'я, яке не належить вашим ботам, отримує 403, а не тихий відкат до здогадки.

Успішна відповідь - 201 зі створеним рядком події. 400 означає, що тіло не пройшло валідацію, 401 - ключ надіслано, але не прийнято: невідомий, відкликаний, вимкнений чи прострочений. Запит узагалі без заголовка x-api-key до цієї перевірки не доходить: його відбиває раніше 403 із тілом CSRF: Origin header required for mutating requests, і це ознака загубленого заголовка, а не проблеми з кліком. А 403 на самому маршруті приходить у шести різних випадках, і тіло відмови розрізняє їх машинно: клік не знайдено або він належить іншій робочій області - у цієї відмови в тілі одне поле error, ні code, ні hint там немає, і тією самою відмовою без code відповідає bot_start, у якого payload.bot_username чи payload.bot_id називає бота чужої організації; клік лежить поза звуженням ключа за кампаніями або взагалі не прив'язаний до кампанії (code: campaign_scope_forbidden); ключу не видано потрібне право (code: api_key_write_not_granted або api_key_delete_not_granted); власник ключа більше не owner і не admin у цій робочій області (code: role_forbidden), і роль перечитується на кожному виклику - крім ключів, випущених до 2 вересня 2026 року: до них перевірка ролі на POST /api/events по HTTP не застосовується зовсім, тож такий ключ приймає конверсії й після розжалування власника, а лікується це лише випуском нового ключа; права ключа не вдалося розібрати, або в них є рівні запису й вони не називають робочої області (code: api_key_grant_unverifiable); права називають робочу область, а живого членства власника ключа в ній уже немає (code: api_key_org_not_member). Поле code є в усіх, крім першого, а hint із готовою вказівкою, що робити, - в усіх, крім першого та відмови за звуженням кампаній; логуйте code, а не самий лише статус.

Ендпоінт тримає 1800 запитів на хвилину на організацію; понад це приходить 429 із заголовком Retry-After. Тіло понад 1 МіБ відхиляється з 413 ще до розбору: ця стеля стоїть на будь-якому пишучому запиті за ключем. Окремо варто обробити 503 із заголовком Retry-After: 60 (поля code в тілі немає): це не ваш ліміт, а тимчасова недоступність, і для запитів за ключем відмова на аварії завжди закрита - подія НЕ записана, запит треба повторити через названий інтервал. 504 читається так само, але приходить лише в запитів із заголовком Idempotency-Key: обробник не вклався в 55-секундний бюджет, а без цього заголовка обгортка з таймером не вмикається взагалі. 409 буває двох видів: без поля code - запит із тим самим Idempotency-Key ще виконується, повторіть трохи згодом; із code: org_deleted - робочу область видалено. Повтор із тим самим nonce безпечний і саме він є рекомендованою стратегією ретраю: другого рядка від нього не з'явиться.

Ротація і відкликання

Механізму «перегенерувати на місці» немає - секрет ключа фіксований на весь час його життя. Щоб ротувати ключ: спочатку створіть новий, підставте його в інтеграцію, переконайтеся, що події проходять, і лише потім відкличте старий ключ у таблиці, щоб нічого не лишилося працювати на ньому. Відкликання відбувається миттєво - натисніть Відкликати у рядку ключа, і він одразу перестає працювати, без періоду на переїзд, тому спочатку перемикайтесь, потім відкликайте, а не навпаки.

Типові граблі

  • Секрет показується один раз, при створенні. Закрили діалог, не скопіювавши, - він втрачений назавжди: відкликайте ключ і створюйте новий.
  • Ключ пише конверсії у вашу організацію, тож ключ, що витік, - це чужі події у вашій аналітиці й у ваших списаннях. Що він уміє понад це, залежить від виданих прав. З postbacks стороння людина заводить правило, яке шле кожну вашу майбутню конверсію на свою адресу, і без обмеження за кампаніями таке правило діє на всі кампанії області, включно з майбутніми; з offers:update вона переписує адресу оферу і забирає живий трафік. Гроші та звіти в будь-якому разі ваші: не вставляйте ключ у чат підтримки, публічний репозиторій чи спільний скрипт, а якщо витік - ротуйте і заразом перевірте Постбеки на чужі правила.
  • Забутий nonce на ретраях. Без нього повторне надсилання тієї самої події схлопнеться в один рядок, і це добре, але друга справжня конверсія по тому самому кліку без нового nonce втратиться так само.
  • Чужий чи застарілий clickId дає 403, а не тиху успішну відповідь: подія нікуди не запишеться, і мовчання у звітах означатиме саме це. Але той самий 403 буває і з інших причин: клік належить не тій робочій області, у яку розв'язався ключ (у ключа без збережених прав це найраніша область власника); ключ звужено за кампаніями, а клік лежить поза звуженням (code: campaign_scope_forbidden); ключу не видано право events (api_key_write_not_granted); власник ключа втратив роль owner чи admin (role_forbidden). Розрізняє їх поле code у тілі відмови, і його відсутність - теж відповідь: code немає рівно тоді, коли клік не знайдено.
  • Перше збереження прав без позначеного events вимикає приймання конверсій у ключа, який досі працював без прав. Інтеграція починає падати з 403 одразу, тож зайшли в діалог прав - перевірте events, навіть якщо прийшли туди по звіти.
  • У відкликання немає скасування і немає затримки: будь-яка інтеграція, яка досі використовує старий ключ, одразу починає падати.
  • Називайте ключі за тим, куди вони підключені (наприклад «Keitaro prod»), щоб точно знати, який відкликати, коли щось зламалося, а не гадати за голою датою створення.