Api

JSON-RPC Interface

Call Hydra App over plain HTTP+JSON, no protobuf code generation

Hydra App speaks JSON-RPC 2.0 on the same port as the gRPC server. Use it from any HTTP client without generating protobuf stubs.

If you can run a gRPC client (Node, Go, Rust, Python), prefer that — it's typed and has streaming. JSON-RPC is the right fit for shell scripts, lightweight integrations, debugging, and languages without good gRPC tooling.

Overview

PropertyValue
ProtocolJSON-RPC 2.0
TransportHTTP POST
URLhttp://<host>:<settings.server_port> (default http://127.0.0.1:5003)
PathAnything — /, /jsonrpc, /rpc all route to the same handler
Content-Typeapplication/json
Streaming methodsNot supported over JSON-RPC. Calls to event_subscribe*, orderbook_subscribe*, swap_subscribeSimpleSwaps return -32603 Internal error. Use the gRPC interface for streams.

Examples on this page assume http://127.0.0.1:5003. If you're going through an SSH tunnel, replace with whatever local port the tunnel forwards.

Quick start

curl -s -X POST http://127.0.0.1:5003 \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"app_getNetworks"}'

Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "networks": [
      { "protocol": "PROTOCOL_BITCOIN", "id": "0a03cf40" },
      { "protocol": "PROTOCOL_EVM",     "id": "11155111" },
      { "protocol": "PROTOCOL_EVM",     "id": "421614" }
    ]
  }
}

Params shape — important

Hydra App's JSON-RPC handler uses OpenRPC by-name params. There are two valid forms; bare object params don't work.

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "wallet_getBalances",
  "params": [
    { "network": { "protocol": "PROTOCOL_BITCOIN", "id": "0a03cf40" } }
  ]
}

The third form returns -32602 Invalid params: missing field 'req'. Use positional — it's idiomatic JSON-RPC and matches what rpc.discover advertises.

For methods with no params (app_getNetworks, app_getPublicKey, app_getReferral, orderbook_cancelAllOrders, etc.) you can omit the params field entirely or send [].

Enum encoding

Proto enum values are accepted as either the full constant name (string) or the numeric value (int):

// Both of these work:
{ "protocol": "PROTOCOL_BITCOIN", "id": "0a03cf40" }
{ "protocol": 1,                   "id": "0a03cf40" }

Responses use the string form. If you compare returned values, expect "PROTOCOL_BITCOIN" not 1.

EnumString valueInt value
ProtocolPROTOCOL_BITCOIN1
ProtocolPROTOCOL_EVM2
OrderSideORDER_SIDE_BUY1
OrderSideORDER_SIDE_SELL2

See the proto files in /proto for the complete enum tables.

Discovering methods

Every Hydra App build exposes the full OpenRPC schema via rpc.discover:

curl -s -X POST http://127.0.0.1:5003 \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"rpc.discover"}' \
  | jq '.result.methods | length'
# → 119

The result includes every method's JSON-Schema for params and result. This is the authoritative reference — it always matches the proto your app was built from. Use it to discover param fields, type structures, and return shapes:

# Pretty-print the schema for one method
curl -s -X POST http://127.0.0.1:5003 \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"rpc.discover"}' \
  | jq '.result.methods[] | select(.name == "wallet_getBalances")'

Naming convention

Methods follow <service>_<methodCamelCase>. The service name is the lowercase JSON-RPC namespace (the same one shown in each per-service doc page); the method name is the gRPC method in lowerCamelCase.

gRPCJSON-RPC
AppService.GetNetworksapp_getNetworks
WalletService.GetBalanceswallet_getBalances
OrderbookService.CreateOrderorderbook_createOrder
LiquidityService.RequestChannelLiquidityliquidity_requestChannelLiquidity
WatchOnlyNodeService.GetChannelwatchOnlyNode_getChannel

The liquidity namespace is the renamed rental → lease flow; older docs that mention rental_* methods are out of date — those methods don't exist on current builds.

Field-name conversion

Proto fields use snake_case; JSON-RPC requests and responses use camelCase:

Proto fieldJSON field
payment_requestpaymentRequest
lease_duration_secondsleaseDurationSeconds
priority_fee_per_unitpriorityFeePerUnit

When in doubt, run rpc.discover for the method and read the schema.

Worked examples

Examples below are verified against a live Hydra App on Bitcoin Signet + Ethereum Sepolia + Arbitrum Sepolia. Copy verbatim and substitute your endpoint port if different from 5003.

Get balances on Bitcoin Signet

curl -s -X POST http://127.0.0.1:5003 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "wallet_getBalances",
    "params": [
      { "network": { "protocol": "PROTOCOL_BITCOIN", "id": "0a03cf40" } }
    ]
  }' | jq
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "balances": {
      "0x0000000000000000000000000000000000000000000000000000000000000000": {
        "onchain": {
          "confirmed":      { "value": "1.66883943" },
          "trustedPending": { "value": "0.00000000" },
          "pending":        { "value": "0.00000000" }
        },
        "offchain": {
          "freeLocal":               { "value": "0.24623440" },
          "freeRemote":              { "value": "0.24376560" },
          "pendingLocal":            { "value": "0" },
          "pendingRemote":           { "value": "0" },
          "unavailableLocal":        { "value": "0" },
          "unavailableRemote":       { "value": "0" },
          "payingLocal":             { "value": "0.00000000" },
          "payingRemote":            { "value": "0.00000000" },
          "unspendableLocalReserve": { "value": "0.00500660" },
          "unspendableRemoteReserve":{ "value": "0.00499340" }
        }
      }
    }
  }
}

Map keys are asset IDs (zero-padded hash for native assets, ERC-20 contract for tokens). All values are DecimalString — human-readable BTC, not satoshis. See Common Patterns: Amounts & Decimals.

Get the latest block number on Sepolia

curl -s -X POST http://127.0.0.1:5003 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "blockchain_getBlockNumber",
    "params": [
      { "network": { "protocol": "PROTOCOL_EVM", "id": "11155111" } }
    ]
  }'
# → {"jsonrpc":"2.0","id":2,"result":{"blockNumber":"10755913"}}

Get a Signet deposit address

curl -s -X POST http://127.0.0.1:5003 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "wallet_getDepositAddress",
    "params": [
      { "network": { "protocol": "PROTOCOL_BITCOIN", "id": "0a03cf40" } }
    ]
  }'
# → {"jsonrpc":"2.0","id":3,"result":{"address":"tb1p..."}}

Get the application's public key

curl -s -X POST http://127.0.0.1:5003 \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":4,"method":"app_getPublicKey"}'
# → {"jsonrpc":"2.0","id":4,"result":{"publicKey":"<base64-32-bytes>"}}

Place a market sell order on the BTC/USDC pair

Streaming the fill confirmation is not available on JSON-RPC — for that you'll need to call orderbook_subscribeMarketEvents over gRPC. The example below just places the order; the order ID it returns can be polled with orderbook_getOrder.

curl -s -X POST http://127.0.0.1:5003 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 5,
    "method": "orderbook_createOrder",
    "params": [{
      "orderVariant": {
        "marketOrder": {
          "base":  { "protocol": "PROTOCOL_BITCOIN", "networkId": "0a03cf40", "assetId": "BTC" },
          "quote": { "protocol": "PROTOCOL_EVM",     "networkId": "11155111", "assetId": "ERC20:0x8cd0dA3d001b013336918b8Bc4e56D9DDa1347E0" },
          "side":  "ORDER_SIDE_SELL",
          "amount": { "base": { "amount": { "value": "0.0005" } } }
        }
      }
    }]
  }'
# → {"jsonrpc":"2.0","id":5,"result":{"orderId":"..."}}

Provision a service-backed channel via the Lease API

curl -s -X POST http://127.0.0.1:5003 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 6,
    "method": "liquidity_requestChannelLiquidity",
    "params": [{
      "network": { "protocol": "PROTOCOL_BITCOIN", "id": "0a03cf40" },
      "operation": {
        "open": {
          "assetLiquidity": {
            "BTC": {
              "serverAmount": { "value": "0" },
              "clientAmount": { "value": "0.001" }
            }
          }
        }
      },
      "txFeeRate": {
        "maxFeePerUnit":      { "value": "50" },
        "priorityFeePerUnit": { "value": "5" }
      },
      "paymentNetwork":  { "protocol": "PROTOCOL_BITCOIN", "id": "0a03cf40" },
      "paymentAssetId":  "BTC",
      "offchainFeePayment": {}
    }]
  }'
# → {"jsonrpc":"2.0","id":6,"result":{"txid":"...","channelId":"..."}}

Idiomatic clients

class HydraClient {
  private nextId = 0
  constructor(private url = 'http://127.0.0.1:5003') {}

  async call<R = unknown>(method: string, ...positionalArgs: unknown[]): Promise<R> {
    const body = {
      jsonrpc: '2.0',
      id: ++this.nextId,
      method,
      ...(positionalArgs.length ? { params: positionalArgs } : {}),
    }
    const resp = await fetch(this.url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body),
    })
    const json = await resp.json()
    if (json.error) {
      const e = new Error(`${json.error.code}: ${json.error.message}`)
      ;(e as any).rpcError = json.error
      throw e
    }
    return json.result as R
  }
}

const hydra = new HydraClient()

const { networks } = await hydra.call<{ networks: Array<{ protocol: string; id: string }> }>(
  'app_getNetworks',
)
console.log(networks)

const balances = await hydra.call(
  'wallet_getBalances',
  { network: { protocol: 'PROTOCOL_BITCOIN', id: '0a03cf40' } },
)
console.log(balances)

Batched requests

JSON-RPC 2.0 batching is supported — send an array of requests, get an array of responses (order may differ; match by id).

curl -s -X POST http://127.0.0.1:5003 \
  -H "Content-Type: application/json" \
  -d '[
    {"jsonrpc":"2.0","id":1,"method":"app_getNetworks"},
    {"jsonrpc":"2.0","id":2,"method":"app_getPublicKey"}
  ]'

Errors

Standard JSON-RPC 2.0 error envelope:

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32602,
    "message": "Invalid params",
    "data": "missing field `req` at line 1 column 42"
  }
}
CodeMeaningWhat it usually indicates for Hydra
-32700Parse errorMalformed JSON
-32600Invalid RequestMissing jsonrpc / method
-32601Method not foundMethod name is wrong (typo, or a method that's only on a newer build — check rpc.discover)
-32602Invalid paramsWrong shape — most common cause is a flat {...} instead of [{...}] or { "req": {...} }; second most common is a missing required field
-32603Internal errorServer-side problem; check Hydra App logs

Application-level errors (insufficient balance, channel not active, peer unreachable, lease provider not initialized, etc.) come back as JSON-RPC errors in the -32000 to -32099 range — -32010 is the most commonly observed for runtime preconditions. Example:

{ "jsonrpc": "2.0", "id": 6,
  "error": { "code": -32010, "message": "Liquidity manager not initialized" } }

The message mirrors the underlying gRPC status text. See Errors for retry guidance per gRPC status — the same rules apply here.

See also

  • Setup Guide — including how server_port maps to the JSON-RPC URL
  • Bot Quickstart — same flow over native gRPC
  • Common PatternsDecimalString, fee structures, amounts
  • Errors — full gRPC status code catalog with retry guidance
  • rpc.discover — authoritative method reference baked into every Hydra App build

Copyright © 2025