Skip to content

Consensus API

The Consensus server exposes four route groups. Routes marked x402 require a micropayment before the request is processed. All request and response bodies are JSON unless otherwise noted.

Base URL: https://consensus.canister.software


Unauthenticated utility routes. No payment required.


Returns server identity and the active payment address for each supported network.

Response

FieldTypeDescription
namestringServer name
versionstringServer version
statusstringAlways "running"
payment_networks.evmobject{ chain, address } for the EVM network
payment_networks.solanaobject{ chain, address } for Solana
payment_networks.icpobject{ chain, address } for ICP
facilitatorstringFacilitator URL used for payment verification
// Response
{
"name": "Consensus x402 Server",
"version": "2.0.0",
"status": "running",
"payment_networks": {
"evm": { "chain": "Base Sepolia", "address": "0x..." },
"solana": { "chain": "Devnet", "address": "<base58>" },
"icp": { "chain": "TESTICP", "address": "<principal>" }
},
"facilitator": "https://facilitator.canister.software"
}

Returns live operational metrics for the proxy, WebSocket sessions, and nodes.

Response

FieldTypeDescription
statusstringAlways "healthy"
timestampstringISO 8601 timestamp
proxy.cache_sizenumberEntries currently in the response cache
proxy.total_requestsnumberTotal requests handled since startup
proxy.cache_hitsnumberTotal cache hits since startup
websocket.active_sessionsnumberCurrently open WebSocket sessions
websocket.pending_tokensnumberIssued tokens not yet consumed
nodes.total_nodesnumberRegistered active nodes
nodes.current_join_pricenumberCurrent node join price in USD
// Response
{
"status": "healthy",
"timestamp": "2025-01-01T00:00:00.000Z",
"proxy": {
"cache_size": 142,
"total_requests": 8301,
"cache_hits": 6204
},
"websocket": {
"active_sessions": 3,
"pending_tokens": 1
},
"nodes": {
"total_nodes": 5,
"current_join_price": 350
}
}

Returns detailed proxy cache and routing statistics.

Response

FieldTypeDescription
cache_sizenumberCurrent cache entry count
pending_requestsnumberIn-flight deduplicated requests
paid_keysnumberKeys marked paid but not yet cached
total_requestsnumberLifetime request count
cache_hitsnumberLifetime cache hits
cache_missesnumberLifetime cache misses
cache_hit_ratestringHit rate as a percentage string
uptimenumberProcess uptime in seconds
router_statsobjectPer-node routing counters
// Response
{
"cache_size": 142,
"pending_requests": 2,
"paid_keys": 0,
"total_requests": 8301,
"cache_hits": 6204,
"cache_misses": 2097,
"cache_hit_rate": "74.74%",
"uptime": 43201.5,
"router_stats": {}
}

x402 — Payment Required POST /proxy requires a valid x402 payment header. Cache hits are served before the payment check — if the response is already cached, it is returned immediately at no cost.


Routes an outbound HTTP request through the Consensus network. Identical requests return a cached response without re-executing the upstream call.

Request body

FieldTypeRequiredDescription
target_urlstringThe upstream URL to fetch
methodstringHTTP method. Defaults to GET
headersobjectHeaders to forward to the upstream
bodyanyRequest body for POST / PUT / PATCH

Request headers

HeaderTypeDescription
x-verboseanyReturn full metadata alongside the response body
x-cache-ttlnumberOverride cache TTL in seconds. Defaults to 300
x-node-regionstringPrefer a proxy node in this geographic region
x-node-domainstringRoute through a specific node domain
x-node-excludestringExclude a specific node domain from selection
x-idempotency-keystringManual deduplication key. Auto-generated if omitted
x-api-keystringScopes the deduplication key — requests with different keys never share cache entries

Payment

NetworkChainPrice
EVMBase Sepolia (eip155:84532)$0.001
SolanaDevnet (solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1)$0.001
ICPTESTICP (icp:1:xafvr-biaaa-aaaai-aql5q-cai)100000 e8s

Cache hits are free. The deduplication key is computed before the payment check. If an identical request (same URL, method, semantic headers, and body) is already cached, the response is returned at no cost.

Response — default

FieldTypeDescription
statusnumberHTTP status code from the upstream
statusTextstringHTTP status text
dataanyParsed upstream response body

Response — verbose (x-verbose: true)

Includes all default fields, plus:

FieldTypeDescription
headersobjectResponse headers from the upstream
meta.cachedbooleanWhether the response was served from cache
meta.dedupe_keystringSHA-256 deduplication key computed for this request
meta.processing_msnumberTime taken to process the request
meta.timestampstringISO 8601 timestamp
// Request
POST /proxy
{
"target_url": "https://api.example.com/prices",
"method": "GET",
"headers": { "accept": "application/json" }
}
// Response (default)
{
"status": 200,
"statusText": "OK",
"data": { "btc": 65000, "eth": 3400 }
}
// Response (x-verbose: true)
{
"status": 200,
"statusText": "OK",
"headers": { "content-type": "application/json" },
"data": { "btc": 65000, "eth": 3400 },
"meta": {
"cached": false,
"dedupe_key": "a1b2c3d4e5f6...",
"processing_ms": 214,
"timestamp": "2025-01-01T00:00:00.000Z"
}
}

Routes for registering and monitoring proxy nodes.


Returns all registered nodes and the current join price. No parameters.

Response

FieldTypeDescription
totalnumberTotal registered nodes
current_join_pricenumberCurrent join price in USD
nodesarrayArray of node summaries
nodes[].node_idstring12-character hex node identifier
nodes[].domainstringAssigned subdomain
nodes[].statusstring"active" or "inactive"
nodes[].regionstring | nullGeographic region
nodes[].benchmark_scorenumberPerformance score (0–100)
nodes[].ipv6stringNode IPv6 address
nodes[].ipv4string | nullNode IPv4 address
nodes[].portnumberNode port
nodes[].created_atstringISO 8601 registration timestamp
nodes[].heartbeatobjectLast heartbeat metadata
// Response
{
"total": 3,
"current_join_price": 250,
"nodes": [
{
"node_id": "a1b2c3d4e5f6",
"domain": "a1b2c3d4e5f6.consensus.canister.software",
"status": "active",
"region": "eu-west",
"benchmark_score": 87,
"ipv6": "2001:db8::1",
"ipv4": null,
"port": 8080,
"created_at": "2025-01-01T00:00:00.000Z",
"heartbeat": { "last_seen": "2025-01-01T01:00:00.000Z", "rps": 120, "p95_ms": 42 }
}
]
}

x402 — Payment Required Join price is dynamic. It starts at $100 and increases by $50 per registered node, capped at $1,000:

price = min($100 + n × $50, $1000)

n is the number of nodes already registered at request time. The price is computed fresh for each payment challenge.

Registers a new node with the Consensus network. The server runs a benchmark test against test_endpoint, provisions a DNS subdomain, and stores the node record.

The node must send a heartbeat to POST /node/heartbeat/:node_id every 5 minutes to remain active.

Request body

FieldTypeRequiredDescription
pubkey_pemstringNode public key in PEM format
algstringKey algorithm — "secp256k1" or "ed25519"
ipv6stringNode’s public IPv6 address (must contain :)
portnumberPort the node listens on
test_endpointstringURL used for the benchmark test
contactstringOperator contact — email or handle
evm_addressstringEVM reward address — 0x..., exactly 42 chars
solana_addressstringSolana reward address — 32–44 chars
ipv4stringOptional IPv4 address
regionstringGeographic region hint (e.g. "eu-west", "us-east")

Payment

NetworkChainPrice
EVMBase Sepolia (eip155:84532)Dynamic — see formula
SolanaDevnet (solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1)Dynamic — see formula

Response

FieldTypeDescription
successbooleantrue on successful registration
node_idstringAssigned 12-character hex node ID
domainstringAssigned subdomain — <node_id>.consensus.canister.software
ipv6stringRegistered IPv6
ipv4string | nullRegistered IPv4 if provided
portnumberRegistered port
statusstringAlways "active" on success
benchmark_scorenumberScore from benchmark. Minimum to pass: 60
price_paidnumberUSD price at time of registration
processing_time_msnumberTotal registration duration
next_stepsstring[]Guidance on DNS propagation and heartbeat schedule

Error responses

StatusCondition
400Missing required field
400alg is not "secp256k1" or "ed25519"
400ipv6 does not contain :
400evm_address is not 42 chars or missing 0x prefix
400solana_address length outside 32–44 chars
400Benchmark score below 60 — response includes score and details
409IPv6 already registered — response includes existing_node_id
500DNS provisioning failure
// Request
POST /node/join
{
"pubkey_pem": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----",
"alg": "ed25519",
"ipv6": "2001:db8::1",
"port": 8080,
"test_endpoint": "https://[2001:db8::1]:8080/health",
"contact": "operator@example.com",
"evm_address": "0xAbCd...1234",
"solana_address": "So1ana...pubkey",
"region": "eu-west"
}
// Response (success)
{
"success": true,
"node_id": "a1b2c3d4e5f6",
"domain": "a1b2c3d4e5f6.consensus.canister.software",
"ipv6": "2001:db8::1",
"ipv4": null,
"port": 8080,
"status": "active",
"benchmark_score": 87,
"price_paid": 250,
"processing_time_ms": 1842,
"next_steps": [
"DNS propagation may take up to 5 minutes",
"Send heartbeat every 5 minutes to /node/heartbeat/a1b2c3d4e5f6",
"Monitor status at /node/status/a1b2c3d4e5f6"
]
}
// Response (benchmark failed)
{
"error": "Node performance below minimum requirements",
"score": 45,
"required_score": 60,
"details": {
"fetch": { "avg_latency_ms": 980 },
"cpu": { "hashes_per_second": 12000 }
}
}
// Response (duplicate IPv6)
{
"error": "IPv6 already registered",
"existing_node_id": "f6e5d4c3b2a1"
}

Signals the node is alive. Must be called every 5 minutes.

Path parameters

ParameterTypeRequiredDescription
node_idstringThe node ID returned from POST /node/join

Request body

FieldTypeRequiredDescription
rpsnumberCurrent requests per second
p95_msnumber95th-percentile response latency in milliseconds
versionstringNode software version

Response

FieldTypeDescription
successbooleantrue if recorded
node_idstringEcho of the path parameter
messagestringConfirmation message
next_heartbeat_innumberSeconds until next heartbeat is expected — always 300
// Request
POST /node/heartbeat/a1b2c3d4e5f6
{ "rps": 120, "p95_ms": 38, "version": "1.2.0" }
// Response
{
"success": true,
"node_id": "a1b2c3d4e5f6",
"message": "Heartbeat recorded",
"next_heartbeat_in": 300
}

Returns the full record for a single node.

Path parameters

ParameterTypeRequiredDescription
node_idstringThe node ID returned from POST /node/join

Response

FieldTypeDescription
node_idstringNode identifier
domainstringAssigned subdomain
statusstring"active" or "inactive"
regionstring | nullGeographic region
capabilities.benchmark_scorenumberPerformance score
capabilities.fetch_latencynumberAverage fetch latency from benchmark (ms)
capabilities.cpu_performancenumberCPU hashes per second from benchmark
capabilities.ipv6stringNode IPv6 address
capabilities.ipv4string | nullNode IPv4 address
capabilities.portnumberNode port
created_atstringISO 8601 registration timestamp
updated_atstringISO 8601 last-update timestamp
heartbeat.last_seenstringISO 8601 timestamp of last heartbeat
heartbeat.rpsnumberRPS from last heartbeat
heartbeat.p95_msnumberp95 latency from last heartbeat
// Response
{
"node_id": "a1b2c3d4e5f6",
"domain": "a1b2c3d4e5f6.consensus.canister.software",
"status": "active",
"region": "eu-west",
"capabilities": {
"benchmark_score": 87,
"fetch_latency": 42,
"cpu_performance": 980000,
"ipv6": "2001:db8::1",
"ipv4": null,
"port": 8080
},
"created_at": "2025-01-01T00:00:00.000Z",
"updated_at": "2025-01-01T01:00:00.000Z",
"heartbeat": {
"last_seen": "2025-01-01T01:00:00.000Z",
"rps": 120,
"p95_ms": 42
}
}

Opening a paid WebSocket session is a two-step process:

  1. Acquire a tokenGET /ws with x402 payment. Returns a short-lived token.
  2. Connect — upgrade to WSS /ws-connect?token=<token> within 60 seconds.

x402 — Payment Required Price is computed from the billing model and session parameters passed as query strings. Payment is charged upfront for the full session before the token is issued.

Pays for and acquires a WebSocket session token.

Query parameters

ParameterTypeDefaultRequiredDescription
modelstringhybridBilling model — hybrid, time, or data
minutesnumber5Session duration limit in minutes. Consumed by hybrid and time
megabytesnumber50Data transfer limit in MB. Consumed by hybrid and data

Billing models

ModelBilled byParams that affect price
hybridTime and dataminutes + megabytes
timeDuration onlyminutes
dataData transfer onlymegabytes

Payment

Price is computed server-side from model, minutes, and megabytes before the payment challenge is issued. All three networks are accepted.

NetworkChain
EVMBase Sepolia (eip155:84532)
SolanaDevnet (solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1)
ICPTESTICP (icp:1:xafvr-biaaa-aaaai-aql5q-cai)

Response

FieldTypeDescription
tokenstring64-character hex session token
connect_urlstringPre-formed wss:// URL with the token appended
expires_innumberSeconds until the token expires — always 60
// Request
GET /ws?model=hybrid&minutes=10&megabytes=100
// Response
{
"token": "a1b2c3...64-char-hex",
"connect_url": "wss://consensus.canister.software/ws-connect?token=a1b2c3...64-char-hex",
"expires_in": 60
}

Upgrades to a WebSocket connection using a valid session token. Tokens expire 60 seconds after issuance and are single-use.

Query parameters

ParameterTypeRequiredDescription
tokenstringThe token returned by GET /ws. Consumed on connection

Connection headers (optional, sent during the WebSocket upgrade)

HeaderTypeDescription
x-node-regionstringPrefer a proxy node in this geographic region
x-node-domainstringRoute the session through a specific node domain
x-node-excludestringExclude a specific node domain from selection

On connect — session_start message

Sent by the server immediately after the handshake completes:

FieldTypeDescription
typestringAlways "session_start"
sessionIdstringUUID for this session
modelstringBilling model from the token
served_bystringNode ID serving the session, or "local" for self-fallback
limits.timeSecondsnumberMaximum session duration in seconds
limits.dataMBnumberMaximum data transfer in megabytes
pricing.totalCostnumberTotal USD charged for this session
pricing.pricePerMinutenumberRate per minute
pricing.pricePerMBnumberRate per megabyte

On limit reached — session_expired message

Sent immediately before the server closes the connection:

FieldTypeDescription
typestringAlways "session_expired"
reasonstring"time_limit_reached" or "data_limit_reached"
finalUsage.durationMinutesnumberTotal session duration in minutes
finalUsage.dataMBnumberTotal data transferred in megabytes

HTTP error conditions (before upgrade)

StatusCondition
401Token not found or already consumed
401Token expired (older than 60 seconds)
// session_start
{
"type": "session_start",
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"model": "hybrid",
"served_by": "a1b2c3d4e5f6",
"limits": { "timeSeconds": 600, "dataMB": 100 },
"pricing": { "totalCost": 0.042, "pricePerMinute": 0.003, "pricePerMB": 0.0001 }
}
// session_expired (time limit)
{
"type": "session_expired",
"reason": "time_limit_reached",
"finalUsage": { "durationMinutes": 10.0, "dataMB": 38.4 }
}
// session_expired (data limit)
{
"type": "session_expired",
"reason": "data_limit_reached",
"finalUsage": { "durationMinutes": 4.2, "dataMB": 100.0 }
}