nanos_

For developers

Nano RPC protocol: how to call a node, keep it safe and which commands you need

JSON over HTTP POST to your node: the default port, enable_control, a safe send sequence, a short table of the commands you need and common errors.

Checked against 10 sources (9 protocol docs or code) · By

In short

The Nano RPC protocol is JSON sent in an HTTP POST request to your node, which answers with JSON. The default RPC port is 7076, and the server binds to localhost unless you change it. Dangerous calls need the enable_control option, so keep RPC private or limited to loopback addresses.

On this page
  1. How the RPC protocol works
  2. Turn RPC on and keep it private
  3. Make your first call
  4. Units, IDs and block fields
  5. Send a payment safely
  6. Receive a payment
  7. Use the wallet built into the node
  8. Keys, seeds and addresses
  9. Get notified instead of polling
  10. Commands you will actually use
  11. Common errors
  12. Where to go next

Software talks to a Nano node by RPC: an HTTP POST with a JSON body, and a JSON answer. Two habits prevent most of the damage people do with it. Keep the RPC port reachable only from the machine that needs it, and never retry a send without an id. The full command list lives in the node documentation. Here is the short path: switch RPC on privately, make a first call, send a payment without sending the wrong amount, then the commands you will actually use.

How the RPC protocol works

The RPC protocol accepts JSON HTTP POST requests. Each request names a command and its options, and the reference documents the response to expect for each one. The traffic is plain HTTP carrying JSON object bodies, with no encryption of its own. Secure RPC comes from placing a reverse proxy in front of the node.

The protocol is open and fully documented, and a JSON RPC interface is one of the project’s listed features. To try a call without running a node, the example on its page sends requests to a free public node run by SomeNano, which has a daily request limit. Your own node gives your app its own trusted connection to the network.

Turn RPC on and keep it private

  • To communicate by RPC, set the RPC enable option in the config-node.toml file.
  • The default port for RPC access is 7076, and only people you want in control of the node should reach it.
  • The server binds to localhost by default for security reasons, and the port and address are options in config-rpc.toml.
  • Docker images use a default configuration that allows RPC access only to the machine hosting the container.

There are 3 different ways to enable RPC for the node. Pick by the setup and security you want; running RPC inside the node process is the easiest.

Mode Settings in the node and RPC config Notes
In process rpc.enable = true and rpc.child_process.enable = false The default since V19.0, and the easiest
Child process rpc.enable = true, rpc.child_process.enable = true, rpc.child_process.rpc_path = [path to nano_rpc], plus ipc.tcp.enable = true Available from V19.0 only
Out of node process rpc.enable = false, rpc.child_process.enable = false, plus node.ipc.tcp.enable = true Available from V19.0 only; the RPC server runs on its own and talks to the node over IPC

The enable_control switch

Certain RPC calls are sensitive or dangerous by nature, and they require the enable_control setting before they can be used. The option lives in the config-rpc.toml file. Many commands in the reference carry the label enable_control required, and the table below marks them.

Turning it on means anyone with access to your RPC can run these commands, so it is only recommended where RPC access is restricted to local and loopback addresses. Exposing the port externally with enable_control on could expose your wallet, allow the node to be stopped remotely and open the node to other dangerous activity. If your RPC is exposed to external addresses, the node prints warnings to stdout and its logs. If you do turn it on, you must carefully review any access granted to the RPC port. Verify the address and enable_control settings in config-rpc.toml on every node, alongside the other access checks. More advanced options for the process the RPC server runs under are covered in the guide to running Nano as a service.

Make your first call

From a second terminal, check that RPC is enabled with curl. The version command is a harmless first call: it returns version information for RPC, Store, Protocol and Node.

curl -d '{"action": "version"}' http://127.0.0.1:7076

On some systems you may need to use the IPv6 loopback address [::1] in place of 127.0.0.1, for example when mapping Docker ports. Once a node is running as a service, you manage it with RPC commands or the command line interface.

Units, IDs and block fields

Amounts are where RPC integrations go wrong, so read these before writing code.

  • Final balances are recorded rather than transaction amounts, so API calls must be done carefully to avoid loss of funds.
  • A transaction amount is the difference in balance between consecutive blocks.
  • When you create a block, the balance is the final balance of the account after the block, written in raw units as a decimal integer. It is critical that this is the balance after the new block.
  • The nano_to_raw command converts a nano amount (10^30 raw) into raw (10^0).
  • Each block holds the current state of the account, so its type is always state.
  • Instead of the link field, block_create accepts the optional source and destination fields.
  • Since V19.0 the node returns only nano_ addresses, where earlier versions returned xrb_ addresses.

The wallet ID is a series of 32 random bytes and is not the seed. It is a purely local UUID that points to wallet data in your node’s database file, so it will not recover funds if that database is lost or corrupted. It is the value you get back from wallet_create, and the one the node expects in a “wallet” field.

Older code and answers say pending where newer ones say receivable. Many calls and responses still accept and return both words.

Send a payment safely

Editor's takeTake the slow path with every send: read account_info first, attach a unique id, and wait for confirmation before you tell anyone the money has moved.

Key management with your own keys follows two steps, creation and broadcast.

  • Call account_info first to gather the details for the transaction.
  • Obtain the current account balance and the head block in the same atomic account_info call, and only populate the transaction data from that single call.
  • Build the block with block_create. It generally takes longer than other RPC commands, because the node has to generate the proof of work.
  • Set json_block, available since V19.0, to get a block that is easier to parse and always recommended.
  • Broadcast the result with process, and always include the optional subtype parameter.
  • A block that is slow to confirm can be submitted to process again with identical data, at no risk.
  • If it fails to appear, call republish with the oldest missing hash.

On the node wallet, the send command takes a different route, and the id field is the safety mechanism. If a request times out, the send may or may not have gone through. With a unique id on every send, a second call with the same id sends no additional Nano and returns the first block. A subsequent send with the same identifier is ignored by the node. Integrations using the node wallet must ensure idempotency, and the id field provides it internally.

The usual cause of a double withdrawal is error handling that retries the send without the “id” parameter. The BitGrail court case shows what duplicate requests look like: a technical report written for the court found that, after a single withdrawal request from a user, the exchange’s software had sent the Nano node several requests, and the node had no way to recognise them as duplicates. The id field is local to your node instance, so it does not protect you when requests go to different instances that manage the same seed. Having a seed loaded in several online nodes is strongly discouraged. On success, the node returns the hash of the transaction’s block.

The stale balance, and checking confirmation

The commonest way to lose funds by hand is a stale balance. Read balance and head block from one account_info call. Say an account reads a balance of 5 nano, then a receive of 10 nano lands, making the balance 15 nano. A 3 nano send computed as 5 - 3 = 2 nano still refers to the current head block, so the network would accept it, and 12 nano would reach the recipient. Wrong arithmetic is the cousin of that: because balances are recorded, a wrong calculation can turn an intended receive into a send to a non-existent address.

To check a send in code, read the confirmed field from the block_info RPC. During low-traffic periods confirmation typically lands within a few seconds, so wait 5 seconds before asking. If the block is still unconfirmed, block_confirm starts an election when none is running. The recommended notification route is the WebSocket confirmation topic. Make the handler idempotent, because the same block can be announced more than once.

A complete send, three calls

The three calls below are the whole send. Every value except the placeholders comes from the key management guide’s own example, which sends 1 nano (10^30 raw) from one account to another.

First, read the account. Setting “representative”: “true” makes the node also return the account’s representative address, which the new block needs.

curl -d '{
  "action": "account_info",
  "representative": "true",
  "account": "nano_1e5aqegc1jb7qe964u4adzmcezyo6o146zb8hm6dft8tkp79za3sxwjym5rx"
}' http://127.0.0.1:7076

The response carries the frontier, the balance in raw (4618869000000000000000000000000) and the representative.

Second, build the block. The balance you write is the balance after the send, so it is the old balance minus the amount, here 3618869000000000000000000000000. Replace the key placeholder with your own private key and never paste a real one into a shared terminal.

curl -d '{
  "action": "block_create",
  "json_block": "true",
  "type": "state",
  "previous": "92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D",
  "account": "nano_1e5aqegc1jb7qe964u4adzmcezyo6o146zb8hm6dft8tkp79za3sxwjym5rx",
  "representative": "nano_1stofnrxuz3cai7ze75o174bpm7scwj9jn3nxsn8ntzg784jf1gzn1jjdkou",
  "balance": "3618869000000000000000000000000",
  "link": "nano_1q3hqecaw15cjt7thbtxu3pbzr1eihtzzpzxguoc37bj1wc5ffoh7w74gi6p",
  "key": "<PRIVATE_KEY>"
}' http://127.0.0.1:7076

The node returns the block hash and the finished block, with signature and work filled in, in a form that is already ready for process.

Third, broadcast it. Pass that block to process with the subtype set to send. A successful broadcast returns the block’s hash.

curl -d '{
  "action": "process",
  "json_block": "true",
  "subtype": "send",
  "block": { "...": "the block object from step two" }
}' http://127.0.0.1:7076

Then wait for the block to confirm before you tell anyone the payment arrived: see how to send Nano for the full path from address to confirmation, and proof of work for the work_generate call.

Receive a payment

Receiving funds is very similar to sending, starting with account_info to get block details for the account frontier. The two differences from a send are the link and balance fields.

The first transaction of an account is crafted in a slightly different way:

  • To open an account, you must have sent funds to it with a send from another account, and those funds are then receivable on the receiving account.
  • Start by listing the receivable transactions of the unopened account, and limit the response to the highest value by combining sorting and count.
  • Build the block as for any receive. The only difference from a normal receive is the previous field.

Every one of these blocks must reach the network. The process command accepts the block as stringified JSON data, and a successful broadcast returns the broadcast block’s hash. In V20.0 to V21.3 the node watched blocks for confirmation after process by default; the V22.0+ node has no work watcher.

If a block fails to broadcast, later blocks on the account chain are not accepted, because their previous field refers to a block that does not exist. Rebroadcasting the missing block makes the later blocks valid in the network’s ledger. It may take a few seconds for a transaction to appear on the network, and republishing does not create new transactions. Account chains must be continuous and unbroken. On success, republish returns the hashes of all republished blocks.

Use the wallet built into the node

The node has a built-in private-key manager that suits smaller operations of fewer than 1000 accounts. External key management allows more powerful and robust systems at the cost of added complexity, and it is recommended for larger operations. If you manage more than 1000 accounts, build a separate system for keys and accounts outside the node.

  • To create an account, first create a wallet to hold the seed that will create the account.
  • The wallet ID is not a seed, and many RPC commands need it. If you lose it, it can only be recovered via a command line command.
  • After creating a wallet and backing up the seed (not the wallet ID), populate the wallet with accounts. For many accounts at once, accounts_create is more efficient.
  • If you change the seed in a wallet, accounts created later derive from that seed. Changing seeds on a wallet that already has accounts can cause accidental loss of funds from improper backup, so only change the seed of a wallet with no accounts.
  • Restoring a seed is safest in a new wallet.
  • Keep the seed very secure.

As long as the node is synced and the node wallet is unlocked, it automatically creates and signs receive blocks for all accounts in the wallet. If a receive is not generated automatically, use the receive command. The easiest way to catch up is to tell the node to check all accounts in all wallets for receivable blocks, and this becomes increasingly computationally expensive as the number of accounts grows. The send command sends funds from an account in the wallet to a destination address.

Keys, seeds and addresses

A seed is 32 random bytes, usually shown as a 64 character, uppercase hexadecimal string, and private keys derive deterministically from it. Anyone who knows an account’s private key can transact as if they own that account. The last 8 characters of an address are a Blake2b-40 checksum of the public key, which helps find typos, and the nano_ and xrb_ prefixes are interchangeable. The full treatment is in seed and private key and address format.

Three block rules matter when you build blocks yourself. The first block on an account must be receiving funds. In the completed, signed transaction JSON, the link field is always hexadecimal. Use the correct partnering public key when signing, since an incorrect one may leak information about your private key.

Get notified instead of polling

The node offers notification of confirmed blocks over WebSockets, which has higher throughput than the HTTP callback and uses a single ingoing connection instead of an outgoing connection for every block. The HTTP callback is still available, and both can run at the same time. Services must handle duplicate notifications, as well as missed blocks, because WebSockets do not provide guaranteed delivery. Multiple confirmation notifications for the same block hash may arrive from a single tracking mechanism, so integrations must track block hashes outside the node and block unwanted repeat actions. After an upgrade from version 18 or earlier, the node performs a confirmation height upgrade, and WebSocket notifications may include confirmations for old blocks. Missed blocks happen for reasons such as intermittent network issues and containers reaching capacity in the node or in clients.

WebSockets are available in version 19.0+ only. Their options are set in the config-node.toml file, and with the example configuration localhost clients connect to ws://[::1]:7078. In Docker, set the bind address to ::ffff:0.0.0.0 and map the port accordingly. Support for wss:// is deprecated from V27, and secure WebSockets come from a reverse proxy. Every WebSocket action can optionally request an acknowledgement.

The HTTP callback sends JSON POST requests with every confirmed block to the callback server configured for the node. For a callback, fetch the block by the hash you receive rather than trusting the data it carries.

Commands you will actually use

The full catalogue is far longer than a typical app ever calls. This table keeps the ones that matter for reading accounts, sending and receiving, checking the node and converting units, grouped by task. Commands marked control need the enable_control setting from the section above.

Read an account
Command Use it to
account_balance See how much an account owns and how much has not yet been received
account_info Get the frontier, open block, change representative block, balance, last-modified timestamp and block count of an account; it returns “Account not found” for an account with no first transaction
account_history List the send and receive blocks of an account, newest first
accounts_balances Get balance and receivable amounts for a list of accounts in one call
receivable List block hashes not yet received by an account, available since V23.0
account_key and account_get Convert between an account number and its public key

For several of these the node can include unconfirmed blocks, so do not rely on them where only confirmed data may be used. Version 22.0 added an include_only_confirmed option to account_balance.

Read blocks and the ledger
Command Use it to
block_info and blocks_info Retrieve the JSON of one block or many blocks by hash
block_count Report the number of blocks in the ledger and the unchecked, synchronizing blocks
confirmation_quorum Read the node’s election settings and the observed network state
representatives_online List online representatives that have voted recently
Build, sign and publish blocks
Command Use it to
block_create Build a signed block from your input, which suits offline signing; control
process Publish a block to the network
republish Rebroadcast blocks starting at a given hash
block_confirm Ask known online representatives to confirm a block
work_generate Generate work for a block; control
work_validate Check whether work is valid for a block
Node wallet
Command Use it to
wallet_create Create a new random wallet ID; control
account_create Add the next deterministic key of the wallet as a new account; control
send Send an amount from an account in the wallet to a destination; control
receive Receive a receivable block for an account in the wallet; control
wallet_balances Get balances for all accounts in a wallet
wallet_lock Lock a wallet; control
Check peers, node status and units
Command Use it to
version Return version information for RPC, Store, Protocol and Node
peers List online peers with their network protocol version
telemetry Return metrics from other nodes on the network
stop Shut the node down safely; control
nano_to_raw and raw_to_nano Convert between nano (10^30 raw) and raw (10^0)
validate_account_number Check an account number using its checksum

Use the node wallet only for development and testing: production integrations need custom external key management. Handle keys outside the node as described above.

Common errors

  • “Account not found”: account_info answers this for an account that has not received a first transaction and so has no entry on the ledger. Open it with a receive.
  • “Wallet not found”: the wallet ID you sent does not exist in the node. The ID is local to that node and is not the seed.
  • “Bad seed”: the seed field holds non-hexadecimal characters or is too long.
  • Errors inside a batch: since version 25.0, accounts_balances reports errors in a separate errors entry, and an account missing from the ledger no longer returns an error.
  • A call is refused or nothing answers: check that RPC is switched on in config-node.toml and that you are calling the address and port set in config-rpc.toml. A control command also needs enable_control.
  • A block never confirms: call process again with the same block data, which carries no risk, and use republish if it still fails to appear.

Where to go next

The full command list, with every option and response, is in the RPC protocol reference. The developers hub collects the integration guides as they are published. To understand what a node does with your calls, read how Nano works and run a Nano node. Fees and work are covered in Nano fees. The primary texts are the RPC protocol reference, the integration basics, the key management guide and the node configuration guide.

RelatedHow Nano (XNO) works, from block to confirmationHow to run a Nano node: setup, ports, hardwareHow to send Nano (XNO), step by step

Sources

  1. RPC Protocol · Nano documentation (docs.nano.org) · docs.nano.org · checked October 21, 2026
  2. Integration Guides: The Basics · Nano documentation (docs.nano.org) · docs.nano.org · checked October 21, 2026
  3. Running a Node: Configuration · Nano documentation (docs.nano.org) · docs.nano.org · checked October 21, 2026
  4. Running a Node: Security · Nano documentation (docs.nano.org) · docs.nano.org · checked October 21, 2026
  5. Integration Guides: Key Management · Nano documentation (docs.nano.org) · docs.nano.org · checked October 21, 2026
  6. Integration Guides: Advanced Integration Topics · Nano documentation (docs.nano.org) · docs.nano.org · checked October 21, 2026
  7. Integration Guides: WebSockets · Nano documentation (docs.nano.org) · docs.nano.org · checked October 21, 2026
  8. Nano: zero fees, zero borders, zero limits · Nano (nano.org) · nano.org · checked October 21, 2026
  9. Nano (cryptocurrency) · Wikipedia · en.wikipedia.org · checked October 21, 2026
  10. Integration Guides: Block Confirmation Tracking · Nano documentation (docs.nano.org) · docs.nano.org · checked October 21, 2026

Published . Limits and defaults can change with node releases: each source shows the day it was checked. How we check facts · Report an error.