Guide
REST API
How the Tenrai HTTP API works. Base URLs, JSON conventions, pagination, batch reads, submitting transactions, limits and signed answers.
The node and the Tenrai API serve one HTTP API. The route reference lists every route.
| Service | Base URL |
|---|---|
| Tenrai API | https://api.tenrai-technologies.org |
| Your node | http://127.0.0.1:28701 |
No key and no account. Opening the base URL in a browser shows the routes the service offers.
Conventions
- Routes start with
/v1/. Documents carry"api_version": 1. - Requests and answers are JSON,
application/json. - Amounts are atoms in decimal strings:
"100000000"is 1 TRAI. - Identifiers are 96 lower-case hexadecimal characters.
- Heights are cut heights.
finalized_heightin an answer says which cut the data belongs to. - Times are Unix milliseconds in fields that end in
_millis.
curl https://api.tenrai-technologies.org/v1/address/trai1…/balance{
"api_version": 1,
"address": "trai1…",
"balance": "6248685732224",
"utxos": 184566,
"indexed_height": 20147
}Pages
Lists return a cursor for the next page: next_cursor goes back as cursor, and the cut list's next_before as before. The last page has none.
| Route | Parameters | Page size |
|---|---|---|
/v1/cuts | before, limit | 20, at most 100 |
/v1/address/{address}/utxos | cursor, limit | at most 512 |
/v1/address/{address}/history | cursor, limit | 100, at most 256 |
/v1/cuts/{height}/transactions | cursor, limit | at most 64 |
/v1/cuts/{height}/changes | cursor, limit | 1,024, at most 4,096 |
/v1/transactions/recent, /v1/blocks/recent | limit | 50, at most 500 |
curl "https://api.tenrai-technologies.org/v1/address/trai1…/history?limit=2"{
"api_version": 1,
"address": "trai1…",
"indexed_height": 20147,
"events": [
{
"finalized_height": 20147,
"kind": "spent",
"outpoint": { "transaction_id": "d52f7537…", "output_index": 0 },
"amount": "53852167",
"source": "regular",
"transaction_id": "146a7aae…"
}
],
"next_cursor": "0000000000004eb3…"
}Batch reads
Reading many addresses or transactions one by one wastes your rate limit. The POST routes take a list instead:
| Route | Takes |
|---|---|
POST /v1/address/balances, /v1/address/utxos, /v1/address/changes | Up to 64 addresses |
POST /v1/mempool/pending, /v1/staking/positions, /v1/keys/registered | Up to 64 addresses |
POST /v1/transactions/status | Up to 1,024 transaction IDs |
POST /v1/proofs/utxos | Up to 8 outpoints |
Submit a transaction
curl -X POST https://api.tenrai-technologies.org/v1/transactions \
-H "content-type: application/json" \
-d '{"api_version":1,"wire_hex":"…"}'wire_hex is the signed transaction in its wire encoding. The SDK, the WASM module and the wallet daemon build it for you. Build it against the rules in GET /v1/transactions/manifest, which names the chain, the limits and the minimum fee.
Then follow it with GET /v1/transactions/{id}/status:
{
"api_version": 1,
"transaction_id": "44a27bbe…",
"lifecycle": "receiving",
"observed_work": { "block_id": "d4967f97…" },
"registered_cut": null,
"finalized_cut": null,
"failure": null
}lifecycle | Meaning |
|---|---|
processing | In the mempool |
receiving | Mined: a work block carries it |
registered | A cut registered that work block |
finalized | Executed by a finalized cut. Final |
failed | See failure: conflict, invalid, expired, dropped or no_effect |
Only finalized and a failed with conflict or expired in a finalized cut are final. The earlier stages are what this server has seen so far.
Rate limits
The Tenrai API limits the request rate of each client address; IPv6 clients share a limit per /64. A request over the limit gets 429 with retry_after_millis. Clients that keep breaking the rules are blocked for a while.
- Use the batch routes and the sockets instead of polling single routes.
- Repeat a read after
429or503onceretry_after_millishas passed. - Repeat a submission only after
429or503, never after a lost answer. Ask for the transaction's status instead.
The SDK does all three by default and stays below the limits.
A request body may be 128 KiB at most.
Signed answers
Send a random 32-byte nonce and the answer comes back signed with ML-DSA-65:
curl -i https://api.tenrai-technologies.org/v1/chain/head \
-H "x-tenrai-request-nonce: fef427e742d83a5961324f3ccd93520736116ddb3de0b1a0ab35803a023aff0c"x-tenrai-envelope-version: 1
x-tenrai-node-identity: 0620b263989401f20d0ada348046f0b8808aedb97d8270422b4169ecc153ef09
x-tenrai-request-nonce: fef427e742d83a5961324f3ccd93520736116ddb3de0b1a0ab35803a023aff0c
x-tenrai-tip-height: 20149
x-tenrai-signature: 691588c9…The signature covers the method, the target, the request body, the status, the response body, the nonce, the chain ID and the finalized height. GET /v1/node/identity publishes the public key; it must hash to the identity you pinned. Without a nonce nothing is signed. Socket frames are never signed.
The SDK verifies all of this with signedResponses({ identity, chainId }) from @tenrai/sdk/verify.
From a web page
A browser page can call the API only from an origin the operator admitted. Programs that are not browsers are not affected. For a web app of your own, run a node with --browser-origin https://your.app, or call the API from your server.
