Gram Forge

Everything about GRM and .gram, one call away.

Token metrics, the full holder list and Gram DNS resolution, served from an index we keep warm. No signup, no key, no CORS restrictions.

https://api.gram.su
GET/v1/token
GET/v1/holders?min_balance=100000
GET/v1/holders/{address}
GET/v1/dns/{name}
GET/v1/dns/{name}/image?size=512
GET/v1/pools

This API is in beta. Everything documented here works and is serving live data — but field names and response shapes can still change while the ecosystem settles. Breaking changes, incidents and maintenance are announced in @GrmAPI_Bot before they happen — subscribe if you are building on this. For anything else, @nosint.

16Endpoints
NoneAuth required
600/minRate limit
0.3msCursor page

Request examples

See the shape before you write a line

Real responses from this deployment. Pick a group, open the link, compare it with what you get back.

GET/v1/token

Supply, holder count, mintability, price and market cap. One call for a dashboard header.

Run it
{
  "ok": true,
  "data": {
    "symbol": "GRM",
    "name": "Grm",
    "decimals": 9,
    "master": {
      "raw": "0:b8ef4f77a17e5785bd31ba4da50abd91852f2b8febee97ad6ee16d941f939198",
      "bounceable": "EQC47093oX5Xhb0xuk2lCr2RhS8rj-vul61u4W2UH5ORmG_O",
      "non_bounceable": "UQC47093oX5Xhb0xuk2lCr2RhS8rj-vul61u4W2UH5ORmDIL"
    },
    "description": "The first-ever PoW jetton on TON Blockchain.",
    "image": "https://gramcoin.org/img/icon.png",
    "total_supply": 2434215638.26197,
    "holders": 92963,
    "mintable": true,
    "price_usd": 0.0007634,
    "market_cap_usd": 1858280.218
  }
}

Reference

Every endpoint

All read-only, all returning the same envelope. Tap any path to run it.

Token

Price, supply and market metrics.

Holders

The full holder index, filterable.

Wallets

One address, everything it has.

Liquidity

Who holds a share of each GRM pool.

Gram DNS

Resolution, browsing and artwork.

Service

Operational.

Paging & filtering

Getting exactly the rows you want

Every list endpoint takes the same shape of parameters. Two of them decide whether your integration stays fast at scale.

Holders — /v1/holders

ParameterValuesNotes
limit1–1000, default 50Rows per page.
cursornext_cursor from the previous response The fast path. Costs the same on page 10 000 as on page 1. Pass it back unmodified.
offset0–10 000 Simple but linear: the database walks every preceding row. Rejected above 10 000 — use the cursor.
min_balanceGRM, e.g. 100000Only holders at or above this balance.
max_balanceGRMCombine with min_balance for a band.
with_domaintrue Only holders who own a .gram name. The name is on every row as domain; when someone owns several, the shortest is shown.
orderdesc (default), asc By balance. asc starts at the dust.

Liquidity providers — /v1/pools/{address}/providers

ParameterValuesNotes
limit1–1000, default 50Providers per page, largest share first.
offset0–100 000The set is small enough that offset is fine here.

On both STON.fi and DeDust a share of a pool is an LP jetton whose master contract is the pool itself, so providers are the holders of that jetton. Reading it that way uses one source we already trust rather than two exchange APIs with different shapes and limits. share is the fraction of total LP supply; lp_balance is the raw amount. The set is replaced wholesale on each sync, because membership changes as people withdraw and upserting would leave them listed forever.

GRM DEX's own GRM/USDT pool isn't listed on any price aggregator, so its numbers are read from the chain directly instead of from a feed. price_usd, price_native and change_24h come from the pool contract's own reserves. volume_24h and txns_24h come from reconstructing real swaps out of the pool's transfer history — matched transfer pairs sharing one counterparty — rather than from reserve differences, which undercounts when trades land close together. Either figure carries "volume_estimated": true only when the walk hits its length cap with history still unread, not as a standing caveat.

Domains — /v1/dns

ParameterValuesNotes
limit1–500, default 50Rows per page.
offset0–100 000The collection is small enough that offset is fine here.
qsubstring, e.g. need Matches anywhere in the name. Trigram-indexed, so it stays fast.
ownerany address form Everything one account holds. Raw, EQ… and UQ… all work.
on_saletrueOnly names currently listed on a marketplace.
sortindex (default), name, price, expiry price puts the cheapest listing first; expiry the soonest to lapse.

Several names at once — /v1/dns-batch

ParameterValuesNotes
namescomma-separated, up to 25 Each resolved exactly as /v1/dns/{name} would — same fields, same cache.
fresh1 Key holders only, same restriction as the single-name endpoint. Forces every name in the batch past the cache at once, so it costs more upstream calls than the single form multiplied by the batch size — use it sparingly.

Returns {"results": [{"requested": "...", "domain": {...}}, ...]}, one entry per name in the order given, so the response lines up with the request without matching on the resolved name. An entry that could not resolve carries "error" instead of "domain" — a typo in one of twenty-five names doesn't fail the other twenty-four. GET, not POST: this API has no write endpoints, and a comma-separated list needs none either.

Running out of rent — /v1/dns-expiring

ParameterValuesNotes
days1–365, default 30Rent due inside this many days from now.
symbolslength in characters Exact, not a maximum — symbols=1 means k.gram, l.gram, never ab.gram.
limit1–500, default 100Rows per page.
offset0–100 000

A read from the index the bulk sync already keeps current, not a resolution — nothing here reaches the chain, so it costs the same whether you ask about one day or a year. Sorted soonest-first. A name with no owner has no rent clock and never appears here regardless of the filters.

What a name cost

FieldMeaning
mint_price_grmWhat the name went for when it was first claimed, in GRM.
mint_bidsBids in that first round. One means it was bought outright; more means it was auctioned.
minted_byThe wallet that paid. For a fixed-price mint this is followed back past the collection.
mint_fee_gramNetwork coin spent on the transaction. A fee, not a price — note the different suffix.
last_price_grmPresent only when the name lapsed and was won again. What the current owner paid.
auctionsEvery round, oldest first, with its price, bid count, winner and date.
settledWhether the winner was confirmed as an owner. False means unverified, not disproved.

A name can be won more than once: at the original mint, and again every time it expires and goes back up. tele.gram was won for 160 811 GRM in 2024, lapsed, and was won again for 583 332 in 2025 — so a single "price" would answer the wrong question whichever number it carried. Old mints may have no price at all: resolution needs transfer history, and the indexer does not keep it forever. Missing means null.

Walking the whole holder list

Request a page, read next_cursor, pass it as cursor on the next request, stop when it comes back null. Never increment offset in a loop — that is the slow path by construction.

Filters combine

?min_balance=1000&with_domain=true&limit=100 is one query, one index scan. total counts rows matching the filters, not the whole table.

Ranks and cursors don't mix

rank is populated when paging by offset. With a cursor it is omitted, because computing it would need the same full scan the cursor exists to avoid. Use /v1/holders/{address} for one exact rank.

Cheapest domains on sale

/v1/dns?on_sale=true&sort=price&limit=20. Prices are nanograms in price_nano; divide by 10⁹ for GRM.

Conventions

Worth knowing first

Six things that will save you an afternoon.

One envelope, always

Success is {"ok":true,"data":…}, failure is {"ok":false,"error":"code","message":"…"}. Status codes always agree with ok.

Three address forms, everywhere

Every address is returned as raw, bounceable and non_bounceable. Any of the three is accepted as input, so you never convert anything.

Lists are thinner than lookups

Mint dates, expiry and prices come from resolving a domain, which reaches the chain. A list returns what the index already holds, so some rows carry more than others. /v1/dns/{name} resolves one domain fully — lists do not, because a wallet can hold dozens of names and each resolution is about ten upstream calls.

Null means unknown

A figure we could not determine is null, never 0. A zero here is a real zero. Fields that cannot apply — auction details on an owned name — are absent rather than null, so a null always means "unknown" and never "not relevant".

Times come twice

Every timestamp is ISO-8601 in UTC with a Unix integer beside it (expires_at_unix). expires_in_days truncates — use expires_in_seconds when hours matter.

GRM is not GRAM

GRAM is the network's native coin, formerly Toncoin. GRM is this jetton, contract EQC47093…mG_O. Fields ending _gram are the coin; _usd is dollars.

Freshness, and what it costs

Holders, the catalogue, pools and distribution are read from our own index, refreshed every ten minutes. A single domain and a wallet's names are reconciled against the chain once a minute, so what you see is at most a minute behind a transfer. Mint prices and auction history never change once known.

Ask for the same name as often as you like

Concurrent lookups for one domain collapse into a single trip to the chain — the cost of this API scales with how many different things you ask about, not with how often. Polling one name every second costs no more than polling it once a minute.

Read your budget from the headers

Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Policy, so you can pace without discovering the ceiling by hitting it. Going too fast is answered with 429 and a Retry-After — never a 503, which would claim the service failed when it did not.

The stage travels with the response

X-API-Stage: beta on everything, and /v1/health reports the running build, the stage and where changes are announced. A client that never opens this page still sees it.

Know before it breaks

@GrmAPI_Bot posts breaking changes, incidents and maintenance windows — and nothing else. If a field is going to move, you hear it there first. Quiet weeks stay quiet.

Questions and changes

A field that looks wrong, a case this doesn't cover, an integration you're planning, or a heads-up before something changes — all of it goes to @nosint. While this is in beta that is the only channel, and it is a fast one.

Caching

Respect Cache-Control and you will never hit the rate limit.

DataFresh forWhy
/v1/price10 sRead live from the pools
/v1/pools15 sSame source, heavier payload
/v1/token20 sSupply moves slowly
/v1/holders30 sServed from our index
/v1/dns/{name}3 minFour chain calls per resolution
images1 yearDeterministic — a name always renders the same

The holder and domain indexes refresh in the background; every holder response carries synced_at so you can see exactly how current it is.