JSON-RPC Errors
Errors returned by the Pod RPC server follow the standard JSON-RPC 2.0 error format. In addition to the standard codes (-32600, -32602, -32603, etc.), Pod defines a small set of domain-specific codes for transaction validation, execution reverts, and account recovery.
Error response format
Every error is returned as a JSON-RPC 2.0 error object:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32000,
"message": "transaction validation failed",
"data": "Insufficient balance"
}
}code
integer
Numeric error code (see below).
message
string
Short, human-readable description of the error category.
data
string / object / array (optional)
Extra context. Its shape depends on the error code — see each entry below.
Pod error codes
3
execution reverted
A contract-level (enshrined application) execution failed. data is an ABI-encoded revert.
-32000
transaction validation failed
A protocol-level validation check failed (nonce, balance, chain ID, gas price, …).
-32003
Transaction rejected: …
A quorum of validators rejected the transaction.
999
Account locked
The account is locked pending recovery. data carries the recovery target.
997
Empty transaction required to make progress
From pod_sendRawTransaction: the account's head nonce will not finalize on its own. data carries the nonce to unblock.
-32002
Chain has no block yet
eth_blockNumber before the first block exists.
-32025
not available in your region
eth_sendRawTransaction / pod_sendRawTransaction from a geo-restricted source.
-32020 … -32023
(see below)
A websocket subscription was closed by the server. Delivered as a notification, not a response — see Subscription close notifications.
3 — execution reverted
Returned when an enshrined application (the order book, the bridge, or minting) rejects a transaction at execution time. The response mirrors the EIP-1474 / geth-style execution revert so that standard wallets and libraries (viem, ethers, alloy) decode the reason automatically.
data is a hex string: the 4-byte Error(string) selector (0x08c379a0) followed by the ABI-encoded reason string.
The decoded reason string is one of the contract-level validation messages below (e.g. CLOB validation failed: …).
-32000 — transaction validation failed
Returned when a protocol-level check fails before the transaction is attested. data is a plain string holding the specific reason — one of the validation messages below.
-32003 — transaction rejected
Returned when at least f + 1 validators reject a transaction (the rejection quorum). The message summarizes the distinct reasons; data is an array of objects, one per rejection.
999 — account locked
Returned when you submit a transaction for an account that is locked due to a pending recovery. data identifies the recovery target — see Recover a locked account.
data field
Type
Description
recovery_target
hash
Transaction hash of the recovery target.
recovery_target_nonce
number
Nonce of the recovery target.
997 — empty transaction required
Returned by pod_sendRawTransaction when the account's head nonce has no certificate and will not finalize on its own. Submit a deadline-free empty self-transfer (to = your own address, value 0, no calldata) at data.nonce; once that finalizes (or the head becomes recoverable), resubmit.
data field
Type
Description
nonce
number
The head nonce to submit the empty self-transfer at.
errors
string[]
Any validator rejections observed while waiting for attestations.
Subscription close notifications
The codes above answer a request. A websocket subscription created with eth_subscribe can also be ended by the server, and that arrives as a notification rather than a response: same eth_subscription method as a normal update, but carrying error in place of result.
The subscription is over once this arrives; nothing further is sent for it. The connection itself stays open, so any other subscription on it keeps working.
-32020
subscription lagged behind the tick broadcast
The server dropped ticks before this subscriber read them, rather than serve a stream with a gap in it. Resubscribe with since = resume_since.
-32021
node is shutting down
The node is going away. Reconnect (to another node, if you have one) and resubscribe with since = resume_since.
-32022
subscriber did not accept a notification in time
The connection would not take a notification within the server's send timeout. Resubscribe with since = resume_since once you can keep up.
-32023
failed to serialize a subscription notification
A server-side bug. Do not retry in a loop — an immediate resubscribe will likely reproduce it. Please report it.
data field
Type
Description
resumable
boolean
Whether resubscribing recovers the stream. false only for -32023.
resume_since
number
Solution time (µs) of the newest tick this subscription is square on — every update it owed you was delivered (a tick that matched none of your filters owed nothing, so it counts). Pass it back as since. Absent if it is square on nothing yet; then reuse your original since.
resume_since_book
string
pod_orders_v2 only, and only when the batch it stopped in was partly delivered: the last book you were sent. Pass it back as since_book alongside resume_since. Absent when the batch landed whole.
missed
number
-32020 only: how many ticks the broadcast dropped.
resume_since names a whole tick, because since selects whole ticks. So if the subscription closed midway through one, resuming redelivers that tick in full and you may see a few deltas twice. That is deliberate — a repeated delta is something a client can dedupe, whereas one that was never sent is unrecoverable.
pod_orders_v2 is the exception, because it can resume inside a tick. A batch settles many orderbooks and is delivered as one frame each, so a close there also carries resume_since_book; pass both back and you receive exactly the frames you never got, with nothing replayed. That is the case the two-part cursor exists for — a close midway through a batch is precisely when you cannot tell where you got to, since frames the connection already accepted may not have reached your code yet.
On the delta channels (pod_orderbook, pod_orders, pod_orders_v2, pod_candles, pod_withdrawals, pod_transfers, pod_activity), eth_subscribe rejects a since older than the node's replay buffer, or any since while the buffer is empty, with -32602 since too old; backfill via REST then resubscribe. Backfill the gap over REST, then resubscribe with a recent since.
A close is the only signal that a delta stream lost data — the stream itself never has holes. Treat the absence of updates as an idle market only while the subscription is open.
Client support. Not every websocket client surfaces this frame, so check yours before relying on the reason being readable.
alloy(Rust) rejects a subscription notification carryingerrorand treats it as a connection error: it tears the websocket down and reconnects rather than surfacing the payload. You observe the close as a reconnect.
jsonrpsee(Rust) removes the subscription and discards the payload, so the stream ends with no reason attached.The pod TypeScript SDK (
@pod-network/trade-sdk) delivers it tosubscribe'sonErroras aPodSubscriptionClosedError—code,resumable,missedandresumeSincemapped to properties, with the full payload onraw— and drops the subscription. On a resumable close it has already advanced that subscription'ssincetoresume_since, sosub.resubscribe()is enough. It does not yet supportpod_orders_v2, soresume_since_bookreaches it only viaraw.With the Rust clients, read the raw websocket frame to act on the reason. The subscription is over regardless of whether your client reports it.
Transaction validation messages
The reason carried in data (for code -32000) or encoded inside the revert (for code 3) is one of the following. Messages with {…} placeholders are filled in with the offending values.
Protocol-level (returned with code -32000)
Invalid chain ID: {chain_id}
The transaction's chain ID does not match the network.
Transaction is blacklisted
The transaction is on the blocklist.
Account has pending transaction
The account already has an unexecuted (pending) transaction.
Future nonce: tx nonce {tx_nonce}, expected {expected}
The nonce is higher than the account's next expected nonce.
Past nonce: tx nonce {tx_nonce}, expected {expected}
The nonce is lower than expected (already used).
Insufficient balance
The account cannot cover the transaction.
Underpriced tx: max_fee_per_gas {max_fee_per_gas} < base fee {base_fee}
maxFeePerGas is below the current base fee.
Recovery validation failed: {reason}
A recovery transaction failed validation.
Contract-level (returned with code 3, inside the revert)
CLOB validation failed: {reason}
Order book transaction validation failed.
Bridge validation failed: {reason}
Bridge / cross-chain operation validation failed.
Mint validation failed: {reason}
Mint operation validation failed.
Standard JSON-RPC codes
Pod also returns the standard codes defined by the JSON-RPC 2.0 specification:
-32600
Invalid Request
Malformed request, or an unauthorized call to an admin-only method.
-32602
Invalid Params
Parameters could not be parsed (e.g. a malformed hash or raw transaction), or a subscription since older than the replay buffer.
-32603
Internal Error
An internal server error while servicing the request.
Last updated

