Toncenter
Public TON API gateway. JSON-RPC over a liteserver pool: getTransactions, runGetMethod, sendBoc, getAccount. Free tier with 1 RPS, paid tiers for production load.
Aliases: toncenter api, ton center
Toncenter (toncenter.com) is a public TON API gateway: REST/JSON-RPC over a pool of liteservers. Maintained by TON Foundation as the reference implementation of a public TON API.
Core methods
getTransactions(address, limit, lt, hash)— account transaction history.runGetMethod(address, method_name, stack)— read-only call to a contract’s get-method.sendBoc(boc)— broadcast a signed external message.getAccount(address)— balance, code, data, status.
Tiers
- Free — 1 RPS, no API key. For prototypes and occasional calls.
- Pro — paid, higher rate limit, separate SLA.
Alternatives
- TONAPI (
tonapi.io) — indexed API with friendly JSON schemas and operation-level search. Often easier for DApp development. - Your own liteserver pool — for latency-critical infrastructure or for privacy.
Authentication and rate limits
Free tier works without a key: https://toncenter.com/api/v2/getAccount?address=... responds immediately. Limit — 1 request per second per IP; exceeding it returns 429 Too Many Requests.
For production load you need an API key (free signup via @tonapibot for the classic tier):
const r = await fetch('https://toncenter.com/api/v2/getAccount?address=...', {
headers: { 'X-API-Key': process.env.TONCENTER_KEY },
});
With a key — 10 RPS on free-pro, higher on paid tiers. The X-API-Key header is more reliable than the api_key query parameter — some proxies strip query strings.
Paginating transactions
The correct cursor for getTransactions is a (lt, hash) pair, not an offset. Example walking an account’s full history:
let cursor = null;
while (true) {
const params = new URLSearchParams({ address: addr, limit: '50' });
if (cursor) { params.set('lt', cursor.lt); params.set('hash', cursor.hash); }
const { result } = await (await fetch(`${API}/getTransactions?${params}`)).json();
if (!result.length) break;
for (const tx of result) handle(tx);
const last = result[result.length - 1];
cursor = { lt: last.transaction_id.lt, hash: last.transaction_id.hash };
}
Using utime as a cursor causes gaps: many transactions can share the same second, and utime >= will drop some.
Common errors
503 Service Unavailable— liteserver pool is overloaded. Retry with exponential backoff (start = 500 ms, cap = 8 s) usually fixes it.runGetMethodreturns stack withnull— the contract has no such method_id, or threw inside. Inspect the contract code viagetAccount→data→ ABI.sendBocreturned 200 but no transaction appears — the external message may have been rejected by the validator (low fee, bad signature). Check the account’sprev_ltafter 15-20 sec: if it didn’t change — your message was rejected.