API routes
The backend HTTP surface — every /api/v1 route, its query parameters, and its response shape.
The console backend exposes the route service over a small HTTP
API under /api/v1. Every route runs on the Node runtime and reflects only public chain
state — plus the relayer. All numeric field values are returned as decimal strings (they
are field elements / uint256, too large for JSON numbers).
GET /api/v1/health
Indexer liveness. Syncs, then reports.
{ "status": "ok", "lastBlock": "12345678", "leafCount": 42 }On failure it returns 503 with { "status": "error", "error": "…" }.
GET /api/v1/pool/state
Current pool summary.
{ "root": "1234…", "leafCount": 42, "treeDepth": 32, "lastBlock": "12345678" }GET /api/v1/tree/proof
Merkle inclusion proof for a leaf.
| Query | Type | Notes |
|---|---|---|
leaf | integer ≥ 0 | the leaf index |
{
"leaf": "1234…",
"pathElements": ["…", "…"],
"pathIndices": [0, 1, 0],
"root": "1234…"
}A non-integer or negative leaf returns 400. The SDK uses this to build the input for the
circuit.
GET /api/v1/notes/scan
The encrypted note blobs the caller trial-decrypts to discover what it owns.
| Query | Type | Notes |
|---|---|---|
since | bigint | block number to scan from (default 0) |
[
{
"leafIndex": 7,
"commitment": "1234…",
"encryptedNote": "0x…",
"blockNumber": "12345670"
}
]The server cannot read these blobs — only a holder of the matching viewing key can (viewing keys).
GET /api/v1/association/status
Whether an association root is currently approved.
| Query | Type | Notes |
|---|---|---|
root | bigint | association root (default 0) |
{ "root": "1234…", "valid": true, "uri": "https://asp.example/set/…" }uri is null when the root isn't known/approved.
POST /api/v1/relay
Submit a client-built proof on-chain. The relayer pays gas with its server key, so the
recipient address has no funding history. It cannot alter recipient, withdrawAmount, or
feeAmount — they are bound into the proof.
Request body (values are decimal/hex strings, produced by the SDK):
{
"arity": "1x2", // "1x2" | "2x2"
"proof": {
"a": ["…", "…"],
"b": [["…", "…"], ["…", "…"]],
"c": ["…", "…"]
},
"nullifiers": ["…"], // 1 for 1x2, 2 for 2x2
"outCommitments": ["…", "…"],
"data": {
"root": "…",
"associationRoot": "…",
"withdrawAmount": "…",
"token": "0x…",
"recipient": "0x…",
"feeAmount": "…",
"relayer": "0x…"
},
"encryptedNotes": ["0x…", "0x…"]
}Response:
{ "txHash": "0x…" }Failure modes: 503 { "error": "relayer not configured" } if no HESTIA_RELAYER_KEY is set;
400 { "error": "…" } if submission reverts or the body is malformed.
Notes
- Read routes use the Node runtime and are dynamic (always reflect the latest sync); there is no caching layer in front of them.
- The API mirrors the
@hestia/routehandlers exactly, so self-hosting the route service gives you the same surface — see self-hosting.
