Documentation
RPC API reference
The HTTP interface a node serves to wallets: four endpoints, JSON for status and errors, a compact binary encoding for blocks and payments.
Test network: the coins have no value
The base URL of the public test node is https://node.tint.top. The interface never sees a key or an amount: a wallet sends finished payments and reads whole blocks, which it decrypts itself.
Endpoints
| Method and path | Answers with |
|---|---|
GET /v1/status | Where the chain stands, as JSON. |
GET /v1/blocks/{height} | One finalized block, in the wire encoding. |
GET /v1/blocks?from=N | The blocks with payments from height N on, as a block batch. |
POST /v1/transactions | Takes a payment in the wire encoding; answers with its identifier as JSON. |
GET /v1/status
curl -s https://node.tint.top/v1/status{
"chain_id": 6073471695170043905,
"height": 128431,
"tip": "9f3c…e21a",
"genesis": "04b7…c9d0",
"queued": 0,
"pools": [
{ "asset": 1, "native": 20000000000000, "other": 10000000000000, "fee_bps": 30 }
]
}| Field | Meaning |
|---|---|
chain_id | Identifier of the network, bound into every signature and block hash. |
height | Height of the latest finalized block. |
tip | Hash of the latest finalized block, as hex. |
genesis | Hash of the first block, as hex. Two test networks can share a chain_id; this tells them apart. |
queued | Payments waiting for a block. |
pools[].asset | The coin the pool trades against the native one (1 is TUSD). |
pools[].native, pools[].other | What the pool holds, in units (1 coin = 100,000,000 units), as of the latest finalized block. |
pools[].fee_bps | The pool's fee in parts of ten thousand; 30 is 0.3%. |
GET /v1/blocks/{height}
Returns one finalized block as application/octet-stream. A finalized block never changes, so the response carries Cache-Control: public, max-age=31536000, immutable. A non-numeric height answers invalid_height, an unknown one not_found.
GET /v1/blocks?from=N
Returns the blocks that hold payments from height N on, as one block batch (application/octet-stream), together with the last height the answer accounts for (through). A wallet catching up asks again from the height after through. The answer grows with the chain, so it is sent with Cache-Control: no-store. from must be a whole number.
POST /v1/transactions
Send a finished payment in the wire encoding as the request body. The wallet builds it; the format is defined in the wire.rs module of tint-core: fixed-width fields, one valid encoding per value, no trailing bytes.
curl -s -X POST https://node.tint.top/v1/transactions \
-H 'content-type: application/octet-stream' \
--data-binary @payment.bin{ "txid": "5be1…07af" }A transaction starts with the asset (u32), an expiry height (u64) and a kind (0 for a payment, 1 for a swap), followed by a bundle: flags, a value balance, an anchor, the number of actions and the actions with their proof. A swap adds the coin bought, the amount paid in and a second bundle.
Errors
Errors are JSON: {"error":{"code":"...","message":"..."}}.
| HTTP | code | Meaning |
|---|---|---|
| 400 | malformed | The body is not a payment. |
| 400 | invalid_height | The height or from is not a whole number. |
| 404 | not_found | No finalized block at this height, or no such resource. |
| 409 | double_spend | A note of this payment is already spent. |
| 409 | conflict | Another waiting payment spends one of the same notes. |
| 409 | price_moved | The pool no longer gives what this swap asks for. |
| 413 | too_large | The payment is larger than the chain allows. |
| 422 | rejected | The payment breaks a rule of the chain; the message says which. |
| 429 | rate_limited | This client address has used its request budget. Retry after the Retry-After header. |
| 500 | internal | The node could not check or encode. |
| 503 | busy, queue_full | The node is verifying other payments, or its queue is full. Try again; Retry-After: 1. |
Rate limits
By default each client address may make 50 requests per second sustained, with a burst of 100, and at most 4 payments are verified at once across all clients. Operators can change both. Behind a reverse proxy the node counts clients by the last address the proxy wrote in X-Forwarded-For; it does not believe earlier entries.
Example: follow the chain
import json, time, urllib.request
BASE = "https://node.tint.top"
while True:
with urllib.request.urlopen(f"{BASE}/v1/status", timeout=10) as r:
status = json.load(r)
print(status["height"], status["queued"])
time.sleep(5)What a node operator can still learn from this interface is the network address a payment came from. Amounts, senders and recipients are inside the proof and the encrypted notes.