Skip to content

GET /apiv2/balances/

Read the balances of any TRON address: right now, at a past moment, over time, or summed up for a period. Six endpoints, all synchronous — the answer comes in the reply, there is no queue and nothing to poll.

Endpoint base URL

https://netts.io/apiv2/balances/{address}

{address} is a TRON address in base58, exactly 34 characters.

Request headers

HeaderRequiredDescription
X-API-KEYyesAPI key from the dashboard
X-Real-IPyesAn address from the key whitelist

Your account balance must be at least 4 TRX. A depleted account is answered with 402 before the request reaches the data.

The six endpoints

EndpointAnswers
GET /apiv2/balances/{address}every token held right now
GET /apiv2/balances/{address}/at?on=YYYY-MM-DDbalances at the end of that date, UTC
GET /apiv2/balances/{address}/at-block?block=Nbalances at an exact block, or at ts=YYYY-MM-DD HH:MM:SS
GET /apiv2/balances/{address}/history?token_id=TRX&days=90how one token moved, day by day
GET /apiv2/balances/{address}/summary?date_from=&date_to=opening, inflow, outflow, fees and closing per token
GET /apiv2/balances/{address}/statement?date_from=&date_to=statement preview with individual operations

Example

bash
curl -s 'https://netts.io/apiv2/balances/TXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' \
  -H 'X-API-KEY: your-api-key' \
  -H 'X-Real-IP: 203.0.113.10'
json
{
  "status": "success",
  "code": 0,
  "msg": "",
  "data": {
    "address": "TXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    "as_of_block": 86012345,
    "live": false,
    "hide_spam": false,
    "total_value_usd": "385.25",
    "balances": [
      {
        "token_id": "TRX",
        "symbol": "TRON",
        "token_type": "TRX",
        "decimals": 6,
        "balance": "1141.899000",
        "price_usd": "0.334968",
        "value_usd": "382.50",
        "is_verified": true,
        "is_spam": false,
        "balance_source": "events",
        "node_balance": "1141.899000"
      }
    ]
  }
}

Things worth knowing before you integrate

Amounts are strings, not numbers. "1141.899000" is a decimal serialised as text so that no precision is lost to a floating-point round trip. Parse it with a decimal type, not a float.

In a reply about the past, balance is the answer and node_balance is not. balance is the amount at the point you asked about. node_balance is what the chain holds right now, in every reply — so in an answer about last month it still shows today's number. Never display it as the historical amount. For a question about now the roles swap; that is the next section.

A date means the end of that day. ?on=2026-09-01 answers for 2026-09-01 23:59:59Z. If you need the start of a day, ask for the end of the previous one, or use /at-block with an explicit ts.

Two balances, and why "now" is the tricky one

A reply carries two different numbers, and on an active address they do not agree:

FieldWhat it isWhen it is exact
balanceThe ledger value, rebuilt from indexed chain events up to the block reported in as_of_blockExact for that block — which is not the newest block
node_balanceWhat a TRON node holds right nowAlways current, never historical

balance is not "the balance on the chain right now". It is the balance as of as_of_block. When you need the chain's present number, read node_balance — it is taken from a TRON node at request time and, for TRX, by the full formula: liquid balance plus staked frozenV2 plus what is delegated out. On an address holding 41.7 million staked TRX it answered 42035672.226020, which is exactly 237799.226020 + 41760434 + 36816 + 623. Reading only the node's plain balance field would have shown 237 thousand and been wrong by two orders of magnitude.

But node_balance is not filled in for every row. TRX and every TRC10 come back in one getaccount call, so they always carry it. TRC20 cannot: a node has no way to list the TRC20 tokens an address holds, so balanceOf is only asked for the major ones. Measured on a wallet with 504 TRC20 rows, 494 of them came back null. That null is policy rather than a failure, and the same null appears if the node is briefly unreachable. So for TRX and TRC10 the chain's present value is always available to you; for a long-tail TRC20 all you have is balance and the block it stands on.

The ledger advances to a block only once every indexing writer has confirmed that block, and its watermark is the minimum across all of them. The slowest writers publish their marks in batches, so the gap widens steadily and then snaps back — a sawtooth, not a constant.

Sampled over a 20-minute window on 6 September 2026:

Blocks behind the headTime behind
Best22~1 min
Median44~2 min
90th percentile86~4 min
Worst observed121~6 min

Plan for the ledger being a few minutes behind the chain, not a few seconds.

What follows from that:

  • A quiet address is exact even for "now". Once nothing has moved for longer than the current lag, the ledger has caught up and balance equals node_balance.
  • On an address that just transacted, balance can be wrong in either direction — too low while an incoming transfer is still unindexed, too high while an outgoing one is.
  • live=true narrows the gap without closing it. It applies the tail of transfer events between as_of_block and the chain head on the fly, for 30–80 ms. It does not move as_of_block, it does not account for fees, and it deliberately skips TRC10 tokens, which are tracked by a separate index. A measured example: an address whose ledger reported 157.317444 TRX answered 766.194807 with live=true, while the node held 1698.995472. Helpful, but node_balance stays the only field that is the chain's current value.
  • Historical answers are exact, full stop. /at, /at-block, /history, /summary and /statement describe points the ledger passed long ago. There is no lag to account for.

For accounting, reconciliation and statements, use the historical endpoints and trust them. For a live wallet screen, show node_balance where it is present — which covers TRX and every TRC10 — and fall back to balance with as_of_block beside it where it is null, so the reader knows which block the number stands on.

/history returns only the days that had activity. Asking for days=7 on an address that moved on three of them returns three points, not seven. Each point carries that day's closing balance and the delta against the previous point.

/summary reconciles itself. For every token opening_balance + period_in − period_out − period_fees = closing_balance, and the engine returns that arithmetic already written out in control_formula, for example 76493.940406 + 141490.950160 - 216763.731366 - 79.260200 = 1141.899000. Fees are broken out into period_fees_energy and period_fees_bandwidth. Note that /summary may report a small negative balance for a token that the current-balance endpoint leaves out entirely.

/statement is capped by ops_limit. It accepts 10 to 5000 operations; anything outside that range is a 422. operations_total is the real count for the period and operations_truncated says whether the list was cut short. token_id defaults to TRX when omitted. For a complete statement beyond 5000 operations, order a file instead — see Statement files.

Token order is deliberate. TRX and the major stablecoins come first, then verified tokens that have a price, then everything else. Do not re-sort by amount: airdropped spam often carries enormous nominal balances and would float to the top.

Spam is marked, not removed. is_spam flags tokens classified as deceptive. Pass hide_spam=true to drop them from the reply; TRX and USDT are never hidden.

Rate limits

Each endpoint accepts 10 requests per second, shared across all clients of that endpoint. The limit is per endpoint, so /history and /summary do not compete with each other.

Exceeding it gives 429 with Retry-After: 1, alongside RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset. Retry after the stated delay.

A second, much wider limit of 100 requests per second per source IP applies across the whole API. The two are told apart by the message: the endpoint limit says Endpoint rate limit exceeded (10 req/s shared), the account-wide one says API rate limit exceeded.

Errors

HTTPMeaning
400the address is 34 characters but fails its base58 checksum
401key missing or invalid, or the source IP is not whitelisted
402account balance below the 4 TRX minimum
403the API key is blocked; contact support
422a parameter is missing or out of range — a wrong address length, or an ops_limit outside 10–5000
429rate limit exceeded
503the balance engine did not answer; the request was not counted, retry

Error bodies come in three shapes, depending on which layer rejected the request. Match on the HTTP status, not on the body.

json
// 401, 402, 403 — the gateway, before the request reaches the service
{"detail": {"code": 1004, "msg": "Insufficient funds. Minimum balance is 4 TRX. Please top up your account."}}

// 400, 422 — the service, after parameters are parsed
{"status": "error", "code": -4, "msg": "address must be base58 (T...)", "data": {"code": -4, "msg": "address must be base58 (T...)"}}

// 429 — the rate limiter
{"message": "Endpoint rate limit exceeded (10 req/s shared), retry shortly", "request_id": "e5dbf25437d2eb215c764617d50b8d3b"}