All posts
10 min read

How to Fix the toncenter 429 "Too Many Requests" Error

Why toncenter returns 429 Too Many Requests, why AI agents hit it hardest, a quick backoff fix — and the real cure, your own key with guaranteed throughput.

toncenter429 errorTON rate limitTON MCPTONNodeTON AI agents

Your token-trading agent on TON dies at the worst possible moment: the user asks "show me my balance and recent trades," the agent dutifully calls toncenter — and instead of a JSON payload it gets back a curt HTTP 429 Too Many Requests. In a single tick the agent tried to gather the wallet balance, account state, transaction history, and hit a couple of get-methods — and public toncenter slammed the door on the second request. The user sees "something went wrong," and you see a wall of red logs all carrying the same status code. This is not a bug in your code. It's toncenter's public rate limit, and every agent runs into it the moment it does anything more ambitious than one request per second.

Let's break down where toncenter 429 comes from, why AI agents catch Too Many Requests on TON more than anyone else, how to quickly cut the error rate with backoff — and how to remove the ceiling entirely by switching to your own key with guaranteed throughput.

What 429 "Too Many Requests" means on toncenter

Code 429 is the standard HTTP response for "too many requests." The server didn't crash and didn't reject your data: it's simply telling you that you've exceeded the allowed request rate and you're being throttled (rate limiting). On toncenter (v2) the response body looks roughly like this:

{ "ok": false, "error": "Rate limit exceeded", "code": 429 }

The key detail: "ok": false, and "code": 429 is just the HTTP status echoed back in the body — not an internal contract error code. Also note that the result field with account data is simply absent — the rate-limit message lives in the error field. So if your code expects to pull data out of result, it will get undefined and most likely blow up somewhere further down the stack with a cryptic parsing error — while the real cause sits in error. The rule is simple: on ok: false, check code and read error first, instead of digging through a result that isn't there.

A quick word on folklore: the TON community loves to joke about "error 228." That is not an API code — it's a meme, and no server will ever return 228 to you. The real rate-limit code is 429, and that's the one to look up in the docs. 429 is a transient error: the exact same call will succeed if you make it more slowly or with a valid key.

Why toncenter gives you ~1 request/sec without a key

Public toncenter without an API key limits you to roughly one request per second. That's not a bug and not greed — it's protection for a shared free resource used by thousands of people at once. A couple of subtleties that trip up even experienced developers:

  • The shared pool is about anonymous traffic. Without a key you're splitting a single public limit of about 1 rps with every anonymous request on the planet. At peak hours the rate actually available to you can be even lower.
  • A paid key raises the ceiling — but only if it actually makes it into the request. The classic trap: the key exists, but the X-API-Key header got lost during an HTTP-client refactor, and you're back on the public limit, spending hours wondering why the paid plan "doesn't work."

One request per second is fine for manual debugging in the browser or a script that checks a balance once a minute. For any automation — and especially for an agent — it's fatally low.

Why AI agents hit 429 the hardest: the burst pattern

Here's the crux of it. A regular application sends requests more or less evenly. An AI agent works differently — in bursts. It operates in ticks: at each reasoning step it needs to gather context, so it issues dozens of calls back to back, almost simultaneously.

Picture one step: "the user wants to know whether a payment arrived." To answer, the agent fires off, within a fraction of a second:

  1. get_masterchain_info — get the current head of the blockchain;
  2. get_balance — the wallet balance;
  3. get_account_state — account status and flags;
  4. get_transactions — recent transactions;
  5. run_get_method — read a contract get-method;
  6. get_jetton_balance — and the USDT balance while we're at it.

Six calls in one tick. The limit is one per second. The first goes through, the rest come back 429, and the agent either stalls or starts hallucinating on incomplete data. And this isn't a "badly built agent" — this is the normal architecture of autonomous reasoning: gather context first, then think. Worse, after catching the errors the agent often tries to "fix" them with retries — and burns through whatever quota was left. The public limit simply wasn't designed for this load profile.

It's a similar story with the public liteservers from TON's global config — under load they respond not ready or drop with an ADNL timeout. There's a separate write-up on that: why a liteserver says "not ready" and what to do about it. And tonapi.io suffers from the same 429 disease — breakdown here.

The quick fix: retries with exponential backoff and Retry-After

The first thing to do right now is stop slamming into the wall at full speed. If a 429 does land anyway, don't immediately hammer the server again: that only extends the ban. This is a palliative — it lowers the frequency of 429s but does not raise the ceiling. Done properly, it has four parts.

1. Exponential backoff with jitter. Each retry waits longer than the last, plus a random offset, so parallel workers don't synchronize and all pile into the same second.

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;

    // Prefer the server's Retry-After, otherwise exponential with jitter
    const retryAfter = res.headers.get("Retry-After");
    const baseMs = retryAfter
      ? Number(retryAfter) * 1000
      : Math.min(1000 * 2 ** attempt, 16000);
    const jitter = Math.random() * 300;
    await new Promise((r) => setTimeout(r, baseMs + jitter));
  }
  throw new Error("toncenter: still 429 after retries");
}

2. Respect the Retry-After header if it shows up. toncenter usually doesn't send it with a 429, so your own backoff formula should carry most of the weight. But if the server does tell you how long to wait — wait exactly that long instead of guessing (that's what the if (retryAfter) branch above does).

3. Cap your concurrency. Put a queue or a semaphore in front of toncenter that releases no more than ~1 request per second. The agent's burst then gets stretched over time: that same list of six calls runs sequentially, in ~6 seconds, but without a single 429.

4. Cache repeated reads. get_masterchain_info can be fetched once per reasoning step and reused. Jetton metadata (decimals, symbol) doesn't change — read it once. A balance you fetched 300 ms ago almost certainly hasn't moved.

This works, and it makes your agent a better citizen, but let's be honest: backoff doesn't raise the ceiling. You're still locked into 1 rps — you're just politely standing in line now instead of crashing. The user waits seconds where they could be waiting milliseconds. For an agent that needs to feel responsive, this treats the symptom, not the disease. Other public-API workarounds are collected in the 2026 roundup of toncenter alternatives.

The real fix: your own key and TON over MCP

The root of the problem is that you're sharing a narrow public channel with thousands of anonymous requests. The only way to truly get rid of 429 is to get your own guaranteed throughput instead of a shared quota. Then the agent's six-call burst goes through in full instead of hitting a wall on call five, and the agent can gather context at full speed. Retries with backoff stay on as insurance against network hiccups, but they stop being the primary survival mechanism.

And there's a path that gets rid of the HTTP-status wrangling entirely. If the TON data is going to an AI agent, it makes more sense to hand it over as ready-made MCP tools than as a raw REST response you have to parse and guard against 429s.

MCP (Model Context Protocol) is the standard AI agents (Claude, Cursor, ChatGPT/Codex, any MCP client) use to call external tools. Instead of teaching your agent to craft the right HTTP request to toncenter, catch 429s, read Retry-After, and parse JSON, you hand it typed tools — and the server does all the network grunt work.

TONNode is a hosted MCP server for TON with exactly 16 tools. Those six reads — the ones an agent typically uses to blow straight through toncenter's limit — are ready-made calls here:

  • get_masterchain_info — the masterchain head;
  • get_balance — GRAM balance;
  • get_account_state — status, flags, latest transaction;
  • get_transactions — transaction history;
  • run_get_method — any read-only contract get-method;
  • get_jetton_balance — a jetton or USDT balance (the jetton-wallet address is computed on-chain).

(By the way, GRAM is Toncoin, renamed in June 2026. The network is still called TON — only the coin's name changed.)

Under the hood, the @tonnode/mcp package speaks TON's native protocol — ADNL, no HTTP layers in between. So you're not just swapping one REST endpoint for another: the agent talks to the network directly, and you stop untangling rate-limit HTTP semantics by hand. The package is open source (MIT), published on npm and GitHub (tonnode/mcp).

The practical difference: the agent no longer needs to know what a 429, Retry-After, or an ADNL timeout even is. It says "give me the balance and recent transactions for this address" — and gets a structured response. That same six-call burst lands on a server that has throughput reserved under your key, not on a shared public limit.

How to plug it in and start for free

There are two paths, and you can start without signing up at all.

Free and local

The full set of read tools over the public config — just add this to your MCP client's settings:

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

No keys needed — npx pulls the package down for you. That's enough to try it out and confirm your agent reads TON without a single 429. Full walkthrough in the free MCP for TON guide.

Hosted endpoint with your own key

When you need guaranteed throughput under real load, switch to the hosted endpoint with your own key:

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

From there you just talk to your agent in plain language — it picks the right tool on its own:

"Check address EQC…: grab the balance with get_balance, the account status with get_account_state, and the last 10 transactions with get_transactions. Then use get_jetton_balance to check the USDT balance."

The agent will call the right tools in the right order — instead of blindly pounding against a rate limit.

Pricing

Every plan includes all 16 tools — you only pay for throughput:

Plan Price Throughput
Hobby free forever 60 requests/min
Pro $29/mo 300 requests/min
Scale $199/mo 1200 requests/min

Even the free Hobby plan at 60 requests/min is a completely different world from public toncenter's ~1 rps: roughly the same ~60 requests per minute, but now guaranteed to be yours, not shared with a crowd of anonymous callers. An agent burst of a dozen calls goes through in full, without a single 429. The Hobby key is issued right after sign-in, no card required.

Paid plans can be settled in GRAM or USDT on TON via TonConnect, or in BTC/ETH/SOL and others through an xRocket invoice in Telegram.

A couple of honest caveats so expectations stay grounded: TONNode currently covers reading data and building transactions, but does not offer pay-per-request, a REST API v2, webhooks, SSE streams, or a written SLA with uptime percentages as shipped products. The archive node with deep history is still syncing — it's a roadmap item, not a promise for today. Everything described above works right now.

A practical migration plan

  1. Grab a free Hobby key (60 requests/min, no card): tonnode.io/dashboard?plan=hobby.
  2. Drop the hosted config above into your MCP client.
  3. Replace manual toncenter calls with the get_balance, get_account_state, get_transactions, run_get_method, get_jetton_balance tools.
  4. The moment your agent hits the 60 requests/min ceiling under load, move to Pro at $29/mo (300 requests/min): tonnode.io/pricing.

Wrapping up

  • A 429 "Too Many Requests" from toncenter means you've hit the ~1 rps public limit — not that your code is broken. (And it's definitely not "228" — that code doesn't exist.)
  • AI agents hit it more than anyone because of the burst pattern: dozens of calls in a single reasoning tick.
  • Retries with exponential backoff, Retry-After, a semaphore, and caching reduce the 429 rate but don't move the ceiling — and you pay for it in speed.
  • The real way out is your own key with guaranteed throughput. And if the data is going to an agent, TONNode serves the same TON reads as MCP tools — and the question "how do I handle 429" simply disappears from your code.

Start free: get a Hobby key — 60 requests/min, no card. If your agent carries real load with dense bursts, go straight to Pro at $29/mo, 300 requests/min.

Give your agent TON access

16 MCP tools: reads, non-custodial swaps, cross-chain and wallets. Free tier — 60 req/min, no card required.