Skip to content

Usage

This guide covers the two main clients in @canister-software/consensus-cli:

  • ProxyClient — routes outbound HTTP through decentralized proxy nodes with automatic x402 micropayments
  • SocketClient — opens paid WebSocket sessions with automatic token acquisition and reconnection

Both clients receive the fetchWithPayment instance you created in Client Setup. That is the only dependency.

Terminal window
npm install @canister-software/consensus-cli

ProxyClient(fetchWithPayment, options) returns a framework-agnostic proxy controller. It routes outbound HTTP through Consensus nodes, supports Express-compatible middleware, and can stand down to direct fetch when a spend limit is reached.

OptionTypeDefaultDescription
mode"inclusive" | "exclusive""inclusive"inclusive proxies all routes except those in routes. exclusive proxies only the listed routes.
routesstring[][]Route paths to include or exclude depending on mode.
matchSubroutesbooleanfalseWhen true, a route match also applies to all sub-paths beneath it.
strategy"auto" | "manual""auto"auto intercepts fetch() calls transparently. manual exposes req.consensus.fetch() for explicit control.
cache_ttlnumberTTL in seconds for node-level response caching.
verbosebooleanfalseEnables verbose response metadata from the proxy node.
node_regionstringPrefer nodes in a specific geographic region.
node_domainstringRoute through a specific node domain.
node_excludestringExclude a specific node domain from selection.
limit_usdnumberMax proxy spend in USD (up to 6 decimals). When reached, proxying stands down to direct fetch.
on_limit_reached(budget) => voidCallback fired once when stand-down is activated.

In auto mode, ProxyClient intercepts the global fetch() within the request context — your route handlers require no changes.

import express from 'express'
import { ProxyClient } from '@canister-software/consensus-cli'
const app = express()
// Proxy only /price — all other routes use direct fetch
app.use(
ProxyClient(fetchWithPayment, {
mode: 'exclusive',
routes: ['/price'],
matchSubroutes: false,
strategy: 'auto',
cache_ttl: 60,
verbose: true,
})
)
// No changes needed — fetch() is automatically proxied for /price
app.get('/price', async (_req, res) => {
const response = await fetch('https://api.example.com/price')
res.json(await response.json())
})

In manual mode the proxy is not applied automatically. Use req.consensus.fetch() to explicitly proxy individual requests, or req.consensus.request() for a lower-level structured payload:

app.use(ProxyClient(fetchWithPayment, { strategy: 'manual' }))
app.get('/data', async (req, res) => {
// Proxied fetch — returns a standard Response
const response = await req.consensus.fetch('https://api.example.com/data')
res.json(await response.json())
// Or use the structured request helper — returns a ProxyResponseShape
const result = await req.consensus.request({
target_url: 'https://api.example.com/data',
method: 'GET',
})
res.json(result.data)
})

Both req.consensus.fetch() and req.consensus.request() accept per-request options as a third argument to override node routing at the call level:

const response = await req.consensus.fetch(
'https://api.example.com/data',
{ method: 'GET' },
{ node_region: 'us-east', cache_ttl: 30 }
)

Use runWithPath() to scope interception in any server framework and createFetch() for explicit route-scoped fetch — no Express required:

const proxy = ProxyClient(fetchWithPayment, {
mode: 'exclusive',
routes: ['/api'],
limit_usd: 1.25,
})
// Scope interception to a path in any framework
await proxy.runWithPath('/api', async () => {
const response = await fetch('https://api.example.com/data')
console.log(await response.json())
})
// Or create scoped fetch functions directly
const apiFetch = proxy.createFetch('/api')
const directFetch = proxy.createFetch('/health')

Set limit_usd to cap total proxy spend. When the limit is reached, all subsequent requests fall through to direct fetch automatically — nothing throws, nothing breaks:

ProxyClient(fetchWithPayment, {
limit_usd: 0.50,
on_limit_reached: (budget) => {
console.warn(`Proxy spend limit reached. Total spent: $${budget.spent_usd}`)
},
})

SocketClient(fetchWithPayment, options) opens paid WebSocket sessions through the Consensus Network. Token acquisition, spend tracking, and reconnection are all handled automatically.

OptionTypeDefaultDescription
openTimeoutMsnumber12000Milliseconds to wait for the WebSocket connection to open before timing out.
reconnectIntervalMsnumber2000Milliseconds between automatic reconnection attempts.
defaultsConsensusSocketTokenParamsDefault token parameters applied to every requestToken() call unless overridden.
limit_usdnumberMax WebSocket spend in USD. If the next token quote would exceed the remaining budget, the token request is blocked.
on_limit_reached(budget) => voidCallback fired once when the spend limit is reached.
webSocketFactoryconstructorauto-detectedCustom WebSocket constructor. Auto-detected as browser WebSocket or Node.js ws if not provided.
ModelDescription
"hybrid"Billed by both time and data (default).
"time"Billed by duration only (minutes).
"data"Billed by data transfer only (megabytes).
import { SocketClient } from '@canister-software/consensus-cli'
const client = SocketClient(fetchWithPayment, {
reconnectIntervalMs: 2000,
})
// 1. Request a session token — payment happens here
const auth = await client.requestToken({
model: 'time',
minutes: 5,
megabytes: 0,
})
// 2. Connect using the token — returns a managed session
const session = await client.connect(auth)
session.on('open', () => console.log('Connected'))
session.on('message', (msg) => console.log('Received:', msg))
session.on('error', (err) => console.error('Error:', err))
session.on('close', () => console.log('Disconnected'))
session.send('hello')
// Close when done — also suppresses automatic reconnection
session.close()

Target specific proxy nodes for your WebSocket session at token-request time:

const auth = await client.requestToken({
model: 'hybrid',
minutes: 10,
megabytes: 100,
nodeRegion: 'eu-west',
nodeExclude: 'node.example.com',
})

Sessions reconnect automatically on unexpected disconnects. SocketClient re-requests a fresh token using the same parameters from the last requestToken() call, then re-establishes the WebSocket.

To stop reconnection permanently, call session.close() — this sets an internal flag that suppresses all further retry attempts.

// Control retry pacing
const client = SocketClient(fetchWithPayment, {
reconnectIntervalMs: 5000, // retry every 5 s
})

Both requestToken() and connect() support a { safe: true } option. Instead of throwing on error, they return a result object:

const result = await client.requestToken(
{ model: 'time', minutes: 1 },
{ safe: true }
)
if (!result.ok) {
console.error('Token request failed:', result.error)
} else {
const session = await client.connect(result.data)
}

Inspect the current connection state at any time:

const state = session.getState()
// {
// connected: boolean,
// reconnecting: boolean,
// closedByCaller: boolean,
// }

CommandDescription
consensus setupCreate a wallet and register it with the x402 proxy.
consensus setup --forceForce re-create the account, resetting any existing configuration.
consensus helpShow help message.

Run consensus setup once to initialize your environment. The CLI will:

  1. Create a wallet using your CDP credentials
  2. Generate .consensus-config.json containing your wallet and delegation credentials
  3. Export wallet authorization to the x402 proxy for payment delegation
  4. Add .consensus-config.json to .gitignore automatically to prevent accidental commits