Усі статті
7 хв читання

tonapi.io і rate-ліміти: як прибрати помилку 429

tonapi.io віддає HTTP 429 при перевищенні rate-ліміту. Розбираємо, як прибрати помилку 429, чому «228» — це мем, і як отримати свій ключ через MCP TONNode.

tonapiпомилка 429rate limitTON APIMCPTONNode

tonapi 429: пів на дев'яту вечора, прод горить, а в логах — стіна червоного

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

HTTP 429 Too Many Requests

Знайомо? Ви звертаєтеся до tonapi.io по баланси та історію транзакцій, усе працювало на десяти користувачах, а на тисячі анонімний доступ уперся в стелю. Це класика: поки трафік невеликий, публічний API здається безкоштовним і нескінченним. Щойно навантаження зростає — він перетворюється на вузьке місце, і ви ловите tonapi 429 цілими пачками. Це не баг вашого коду і не «нода лягла» — це rate limit публічного API, і лікується він передбачувано.

Розберімо, чому виникає 429, чому горезвісна «228» тут ні до чого, які фікси реально допомагають і як піти зі спільного анонімного пулу на персональний ліміт для читання TON — через MCP-сервер TONNode.

Чому tonapi.io повертає 429 Too Many Requests

tonapi.io — публічний HTTP-API до даних TON. Як і будь-який публічний сервіс, він має tonapi rate limit — обмеження частоти запитів, щоб один клієнт не з'їв усю пропускну здатність.

Коли ви ходите без ключа, ви ділите спільний анонімний пул з усіма іншими анонімами планети. На практиці це ліміт близько ~1 запиту на секунду. Для одного скрипта це терпимо. Але щойно у вас з'являється паралельний воркер, який для кожного гаманця смикає баланс, потім джетон-баланс, потім історію, — ви миттєво вилітаєте за секундний ліміт. Особливо боляче на холодному старті, коли треба проіндексувати одразу багато адрес.

Сервер відповідає так:

HTTP/1.1 429 Too Many Requests
Retry-After: 1

Важливо розуміти: 429 — це стандартний код HTTP зі специфікації, а не пропрієтарний код tonapi. Його віддає будь-хто при перевищенні частоти — GitHub, Stripe, Cloudflare, будь-який rate-limited сервіс. Той самий механізм у toncenter і в більшості RPC-провайдерів. А отже, і лікується він стандартними прийомами, про які нижче.

«Помилка 228» — це не код API, а мем ком'юніті (реальний код — 429)

Якщо ви гуглили проблему на російськомовних форумах, вам напевно траплялася «помилка 228». Розставимо крапки над i, бо це реально збиває новачків з пантелику.

«228» — не офіційний код помилки ані tonapi, ані toncenter. Це мем-число, яке давно живе в TON-ком'юніті і раз у раз випливає в чатах та жартах. Жодного HTTP-статусу 228 у відповідь на перевищення ліміту вам не прилетить — такого статус-коду в HTTP просто не існує.

Реальний код, який ви побачите в логах і заголовках відповіді, — саме 429. Коли хтось у чаті пише «впіймав 228 від тонапі», насправді він упіймав 429 (або звичайний таймаут), а «228» вживає як фігуру мови. Шукайте у своїх логах 429, а не «228» — так ви знайдете справжню причину. Перевіряється одним рядком:

curl -s -o /dev/null -w "%{http_code}\n" https://tonapi.io/v2/blockchain/masterchain-head
# під навантаженням без ключа побачите: 429

Швидкі фікси: ретраї, бекоф, кеш і власний ключ

Оскільки 429 — стандартна історія, для неї є стандартний набір ліків. Йдемо від простого до головного.

1. Експоненційний бекоф із повагою до Retry-After

Не гатіть по ендпоінту щільним циклом після першої ж відмови — так ви лише погіршите ситуацію. При 429 сервер часто віддає заголовок Retry-After — скільки секунд почекати. Поважайте його, а якщо його немає — нарощуйте паузу експоненційно.

async function fetchWithBackoff(url, opts = {}, maxRetries = 5) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const res = await fetch(url, opts);
    if (res.status !== 429) return res;

    // Поважаємо Retry-After, інакше нарощуємо паузу експоненційно
    const retryAfter = Number(res.headers.get('retry-after'));
    const waitMs = Number.isFinite(retryAfter) && retryAfter > 0
      ? retryAfter * 1000
      : Math.min(1000 * 2 ** attempt, 30_000); // 1s, 2s, 4s… стеля 30s

    await new Promise(r => setTimeout(r, waitMs));
  }
  throw new Error('429 не минув: вичерпано кількість ретраїв');
}

2. Обмежте кількість одночасних запитів

Часто 429 прилітає не через загальний обсяг, а через те, що ви запускаєте 50 запитів «віялом» через Promise.all. Поставте семафор із лімітом паралелізму (1–4) — і сплески згладяться, не пробиваючи секундний ліміт.

3. Кешуйте незмінні дані

Метадані джетона (ім'я, символ, decimals), результат конвертації адреси, старі транзакції — це дані, які не змінюються. Покладіть їх у локальний кеш із розумним TTL і не звертайтеся до API повторно. Сам лише кеш decimals джетонів прибирає помітну частку звернень.

4. Головний фікс — власний ключ замість аноніма

Перші три пункти — знеболювальне. Справжнє лікування — піти з анонімного пулу на власний ключ. Персональний ключ піднімає ваш ліміт на порядки порівняно з ~1 req/s для аноніма; ви перестаєте конкурувати з усім інтернетом, і 429 просто зникає зі штатної роботи. Окремий випадок для сусіднього провайдера розібрано у фіксі 429 у toncenter — логіка ідентична.

Що робити, коли лімітів tonapi однаково не вистачає

Припустімо, ви все зробили правильно: бекоф, кеш, черга. Але застосунок росте, і навіть із ключем ви впираєтеся або в тариф, або в саму модель «HTTP-прошарок поверх ноди». Тут зазвичай розглядають два шляхи.

Шлях «власні лайтсервери». Виникає спокуса «просто взяти публічний лайтсервер із глобального конфігу TON і ходити безпосередньо через ADNL». Чесне застереження: це не срібна куля. Публічні лайтсервери з глобального конфігу теж спільні й лімітовані — під навантаженням вони регулярно відповідають not ready або відвалюються через ADNL-таймаут, а глибокої історії транзакцій не зберігають. Окремий типовий біль — саме not ready, розбір у як лагодити «liteserver not ready». Тобто просто «перейти на публічний конфіг» проблему лімітів не розв'язує, а часом і погіршує.

Шлях «змінити провайдера/інтерфейс». Проблема не в конкретному tonapi.io, а в тому, що ви сидите на спільному ресурсі. Огляд альтернатив HTTP-API зібрано в альтернативах tonapi у 2026. А якщо ви будуєте ШІ-агента, є сенс не обгортати REST руками, а підключити TON як набір інструментів через MCP — тоді читання блокчейну стає викликом інструмента, а не сирим HTTP-запитом, який треба ретраїти.

TONNode MCP: персональний ліміт замість спільного анонімного пулу

TONNode (сайт tonnode.io) — це hosted MCP-сервер для TON. MCP (Model Context Protocol) — стандарт, за яким ШІ-агенти (Claude, Cursor, ChatGPT/Codex і будь-який MCP-клієнт) викликають зовнішні інструменти. Замість того щоб вчити агента смикати https://tonapi.io/v2/... і обробляти 429, ви даєте йому набір іменованих інструментів для читання TON.

Ключовий момент: пакет @tonnode/mcp працює за нативним ADNL-протоколом TON, без HTTP-прошарків. Він open source (MIT), лежить на npm і GitHub (tonnode/mcp). Це не черговий REST-враппер поверх tonapi, а пряма розмова з мережею.

Типові запити, заради яких ви ходили в tonapi, покриваються інструментами читання один в один:

  • get_balance — баланс GRAM на адресі.
  • get_jetton_balance — баланс USDT чи будь-якого джетона; джетон-гаманець обчислюється он-чейн, вам не треба самому виводити його адресу. Як це замінює ланцюжок із кількох запитів — у нотатці баланс USDT на TON одним викликом.
  • get_account_state — статус акаунта, прапорці, остання транзакція.
  • get_transactions — історія транзакцій.
  • run_get_method — будь-який read-only get-метод контракту.
  • get_masterchain_info — голова мастерчейну (актуальний блок).
  • get_jetton_info — метадані джетона: ім'я, символ, емісія і decimals (у USDT — 6, у більшості джетонів — 9; без них ви неправильно перерахуєте raw-одиниці).
  • parse_address — офлайн-конвертація та перевірка адрес EQ/UQ/raw, узагалі без звернення до мережі.

Невелика нотатка про неймінг: GRAM — це перейменований у червні 2026 року Toncoin. Мережа, як і раніше, називається TON, змінилося лише ім'я монети. Баланси get_balance — у GRAM.

Приклад промпта, який агент виконає через інструменти, а не через ручний HTTP:

Перевір баланс GRAM і баланс USDT на адресі
UQBvW8Z5huBkMJYdnfAEM5JqTNkuWX3diqYENkWsIL0XF_wm
і покажи останні 5 транзакцій цього гаманця.

Агент сам викличе get_balance, потім get_jetton_balance (обчисливши джетон-гаманець он-чейн), потім get_transactions — без жодного написаного вами HTTP-запиту, без Retry-After і без ручного бекофу у вашому коді.

Як підключити за хвилину: локально для розробки або hosted-ключ під прод

Є два способи, і обидва чесні. Важливо їх не плутати: локальний npx без ключа зручний для розробки, але працює на публічному конфігу TON; персональний ліміт, який знімає біль 429 під навантаженням, дає саме hosted-ключ.

Варіант A — локально, безкоштовно, без ключа (для розробки)

Повний набір інструментів читання піднімається однією командою через npx — реєстрація і картка не потрібні. Додайте в конфіг MCP-клієнта:

{
  "mcpServers": {
    "ton": {
      "command": "npx",
      "args": ["-y", "@tonnode/mcp"]
    }
  }
}

Ідеальний старт для локальної розробки та перевірок: повний набір читання, нуль конфігурації, open source під капотом. Але чесне застереження: цей режим працює на публічному конфігу TON, тобто підпадає під ті самі спільні обмеження, що й будь-які публічні лайтсервери (not ready, ADNL-таймаути під навантаженням). Для прод-трафіку це не заміна персональному ліміту — його дає варіант B.

Варіант B — hosted-ендпоінт із власним ключем (для проду)

Коли потрібна гарантована пропускна здатність і персональний ліміт під навантаженням проду, підключаєте hosted-ендпоінт зі своїм Bearer-ключем:

{
  "mcpServers": {
    "ton": {
      "type": "http",
      "url": "https://mcp.tonnode.io/mcp",
      "headers": { "Authorization": "Bearer tn_live_…" }
    }
  }
}

Тарифи

На всіх тарифах доступні всі 16 інструментів TONNode — платите ви лише за пропускну здатність:

  • Hobby — безкоштовно назавжди, 60 запитів/хв. Ключ видається одразу після входу, без картки.
  • Pro — $29/міс, 300 запитів/хв.
  • Scale — $199/міс, 1200 запитів/хв.

Різниця з анонімним tonapi очевидна: там ви ділите ~1 req/s з усім інтернетом, тут у вас персональна стеля — уже на безкоштовному hosted-ключі Hobby це 60 запитів на хвилину лише для вас, без танців із бекофом і без випадкових 429. Це вже не той самий безкоштовний шлях, що й локальний npx без ключа: hosted-ключ Hobby має власний ліміт, а не спільний публічний пул.

Підсумок

429 від tonapi.io — це не баг і не міфічна «228», а чесний сигнал: ви сидите на спільному анонімному пулі й уперлися в його стелю. Ретраї з бекофом, повага до Retry-After, обмеження кількості одночасних запитів і кеш знімають гострий біль. Але по-справжньому проблема зникає лише тоді, коли у вас з'являється власна пропускна здатність — а якщо ви будуєте на ШІ-агентах, це ще й зручніше робити через MCP, де читання TON іде нативним протоколом, а не через HTTP-прошарок.

Візьміть безкоштовний hosted-ключ Hobby (60 req/min, без картки) і перестаньте ловити 429tonnode.io/dashboard?plan=hobby

Потрібен запас під прод-навантаження — порівняйте тарифи Pro і Scale: tonnode.io/pricing.

Дайте вашому агенту доступ до TON

16 MCP-інструментів: читання, некастодіальні свопи, кросчейн і гаманці. Безкоштовний тариф — 60 зап/хв, картка не потрібна.