Time travel on our public Algorand API, powered by dt-algorand

Time travel on our public Algorand API, powered by dt-algorand

Urtho
Urtho
Updated

Our public Algorand API answers questions about the past. Add ?round= to a read and you get the ledger as it stood at the end of that round. It works on MainNet and TestNet, with no key. It is free for now: from 1 January 2027, MainNet history needs a paid plan (Boost, Unlimited or Enterprise), and TestNet stays free.

The answers come from nodes running dt-algorand, D13’s community fork of go-algorand. Its README describes “a node that keeps the value of every account, asset, app and box at every round since genesis, and serves the REST API and transaction simulation at any past round”.

What you can do with it

Each section below has commands you can paste. You need curl and jq, and xxd for the sixteen rounds. There is no key, and nothing here signs or sends a transaction. Every output is real, from 2026-11-06. Paste the blocks in order into one shell: later blocks reuse $API and $CALL.

Read the chain at any past round

Ask for an asset, account, app or box as it stood at a given round. USDC was created at round 8,874,561, on 2020-09-04 at 20:49 UTC. One round earlier, the asset does not exist.

Terminal window
API=https://mainnet-api.4160.nodely.dev
curl -s -w 'HTTP %{http_code}\n' "$API/v2/assets/31566704?round=8874560"
# {"message":"asset does not exist"}
# HTTP 404
curl -s -w 'HTTP %{http_code}\n' "$API/v2/assets/31566704?round=8874561"
# {"index":31566704,"params":{"creator":"2UEQTE5QDNXPI7M3TU44G6SYKLFWLPQO7EBZM7K7MHMQQMFI4QJPLHQFHM","decimals":6,"default-frozen":false,"freeze":"3ERES6JFBIJ7ZPNVQJNH2LETCBQWUPGTO4ROA6VFUR25WFSYKGX3WBO5GE","manager":"37XL3M57AXBUJARWMT5R7M35OERXMH3Q22JMMEFLBYNDXXADGFN625HAL4","name":"USDC","name-b64":"VVNEQw==","reserve":"2UEQTE5QDNXPI7M3TU44G6SYKLFWLPQO7EBZM7K7MHMQQMFI4QJPLHQFHM","total":18446744073709551615,"unit-name":"USDC","unit-name-b64":"VVNEQw==","url":"https://www.centre.io/usdc","url-b64":"aHR0cHM6Ly93d3cuY2VudHJlLmlvL3VzZGM="}}
# HTTP 200
curl -s "$API/v2/assets/31566704/versions"
# {"round":66706337,"versions":[{"deleted":false,"round":8874561,"timestamp":1599252577}]}

In /versions, the outer round is the latest round when you ask. A stock node ignores round and answers as of the latest round, so dt-algorand marks its answers:

Terminal window
curl -s -D - -o /dev/null "$API/v2/assets/31566704?round=8874561" | grep -i x-algod-tt-round
# x-algod-tt-round: 8874561

Dry-run a transaction in the past

POST /v2/transactions/simulate takes a round. Nothing is signed, sent or committed. For example, you can pay 1,000 ALGO into the Tinyman v2 ALGO/USDC pool at round 40,000,000 and call its swap, from a stand-in account. The answer is 134.710140 USDC out, run on 2026-11-06.

A smaller example you can paste: whether the fee sink could opt in to USDC at the same two rounds, with no signature.

Terminal window
for R in 8874560 8874561; do
curl -s "$API/v2/transactions/simulate" -H 'Content-Type: application/json' -d '{
"round": '$R',
"allow-empty-signatures": true,
"txn-groups": [{"txns": [{"txn": {
"type": "axfer", "xaid": 31566704, "fee": 1000, "fv": 8874000, "lv": 8874999,
"snd": "Y76M3MSY6DKBRHBL7C3NNDXGS5IIMQVQVUAB6MP4XEMMGVF2QWNPL226CA",
"arcv": "Y76M3MSY6DKBRHBL7C3NNDXGS5IIMQVQVUAB6MP4XEMMGVF2QWNPL226CA",
"gen": "mainnet-v1.0",
"gh": "wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8="
}}]}]
}' | jq -c '{round: .["last-round"], failure: .["txn-groups"][0]["failure-message"]}'
done
# {"round":8874560,"failure":"transaction 45TZNH7OSHMFCSASPJ5VATZLO5RBAKVKO6QUXQHF6UCTJHYXKCUQ: asset 31566704 does not exist or has been deleted"}
# {"round":8874561,"failure":null}

Run one query across many rounds

POST /v2/query runs one read-only program at many rounds in a single request. Sixteen is today’s limit for one request, as GET /v2/capabilities reports. This program works out what 1,000 ALGO would fetch at the same pool, every 2,500,000 rounds from 28.5 M to 66 M. Each figure is computed from the pool’s state at that round: no trade took place, and it is not a forecast.

The output is in microunits, with lines cut here for length.

Terminal window
CALL='{"accounts":["2PIFZW53RHCSFSYMCFUBW4XOCXOMB7XOYQSQ6KGT3KVGJTL4HM6COZRNMM"],"foreign-apps":[1002541853]}'
SWAPOUT=DDYcAYGdpobeA4AQYXNzZXRfMl9yZXNlcnZlc2NINQA2HAGBnaaG3gOAEGFzc2V0XzFfcmVzZXJ2ZXNjSDUBNhwBgZ2mht4DgA90b3RhbF9mZWVfc2hhcmVjSDUCgYCU69wDNAILgY9OCIGQTgo1A4GAlOvcAzQDCTUENAQ0AR00ADQECJcWsIEBQw==
ROUNDS=28500000,31000000,33500000,36000000,38500000,41000000,43500000,46000000,48500000,51000000,53500000,56000000,58500000,61000000,63500000,66000000
curl -s "$API/v2/query" -H 'Content-Type: application/json' \
-d '{"program":"'$SWAPOUT'","calls":['$CALL'],"rounds":['$ROUNDS']}' \
| jq -r '.rounds[] | "\(.round) \(.calls[0].logs[0])"' \
| while read R V; do echo "$R $(( 0x$(echo $V | base64 -d | xxd -p) ))"; done
# 28500000 197796800
# 31000000 106770062
# ...
# 63500000 81627916
# 66000000 107496929

We timed seven runs of each on 2026-11-06, from our workstation: the read at round 8,874,561 took 0.15 to 0.22 s, the sixteen-round query 0.15 to 0.20 s.

Run new code against old state

POST /v2/query can also run an old round under later rules, “as if an upgrade to it had taken effect right after that round”. At round 27,000,000 a 66-byte AVM 12 program is refused under "protocol": "round", because that round only supports version 8. Under "protocol": "latest" it runs and logs the pool’s two reserves. The answer also lists what changed: 24 consensus parameters, EnablePQSchemeFalcon1024 among them.

The two logs below are big-endian integers in microunits: 5,069,191.410026 ALGO and 1,279,872.455062 USDC.

Terminal window
RESERVES=DDYcAYGdpobeA4AQYXNzZXRfMl9yZXNlcnZlc2NIFrA2HAGBnaaG3gOAEGFzc2V0XzFfcmVzZXJ2ZXNjSBawgQFD
for P in round latest; do
curl -s "$API/v2/query" -H 'Content-Type: application/json' \
-d '{"program":"'$RESERVES'","calls":['$CALL'],"rounds":[27000000],"protocol":"'$P'"}' \
| jq -c '.rounds[0].calls[0]'
done
# {"cost":0,"error":"check failed on ApprovalProgram: program version 12 greater than protocol supported version 8","logs":[]}
# {"cost":16,"logs":["AAAEnENacWo=","AAABKf5X0ZY="]}

What to know before you rely on it

dt-algorand is a community fork, not an official Algorand release. In the README’s words: “It changes no consensus rule, but it is not an official Algorand release: the node answers questions a standard node cannot, and some historical answers are approximations”.

  • Approximations. One of the limitations in the time travel README: “Simulating at an old round detects duplicate txids and leases only against transactions still in the txtail (about the last 1,000 rounds), and knocks offline only the absent or expired accounts the simulated transactions touch.”
  • No future. A round not written yet is refused. The read answers 400 and simulate 500. The README says: “Block headers after R can’t be read, as on chain, where block R+1 isn’t there yet”.
  • Removed options. dt-algorand v5.0.5-dt5, live on our API on 2026-11-06 at 08:11 UTC (09:11 CET), removed simulate’s rounds, rewrite-validity-window and protocol. They now answer 400 and point to POST /v2/query. Simulate at a single round still works.
  • Not for validators. The repo’s main README says “the fork is not audited for running validators”, so on mainnet, testnet and betanet the node refuses participation keys unless its config says otherwise.
  • Not the Analytics DB. Our pricing page lists “Time travel to any block height” under Analytics DB. That is a separate product.

Who built it

The fork is D13’s, and the credit is theirs. You may know D13 from the explorers Algo.Surf and Flow. On mainnet the history takes about 70 GB, and a node that keeps it can prune about 3.5 TB of old block bodies and certificates. The code is open source and anyone can run it. We run it for you on our public API.