XChain Platform Sync: API Reference
Overview
xchain-sync replicates the indexer and decoder databases from authoritative
source nodes to validators and ecosystem replicas. It serves two surfaces over
one shared port (3006):
- a REST API for snapshots, status, schema, and operator control, and
- a WebSocket API for real-time block streaming.
A replicating client bootstraps from a REST snapshot, then subscribes over
WebSocket for live blocks, falling back to the incremental snapshot endpoint to
fill any gap after a disconnect. This reference catalogs the endpoints and the
WebSocket protocol; for deployment, server/client modes, and worked examples see
OPERATIONS.md.
Path parameters used throughout:
:dbTypeisindexerordecoder.:chainisbitcoin,litecoin, ordogecoin.:networkismainnet,testnet, orregtest.
Authentication
When SYNC_API_KEY is set, every REST and WebSocket endpoint requires a bearer
token:
Authorization: Bearer <SYNC_API_KEY>
Requests without a valid token receive 401 Unauthorized. When SYNC_API_KEY
is unset, authentication is disabled and all endpoints are open. WebSocket
clients may additionally send an Ed25519 auth message after connecting (see
below); authenticated validators may receive priority handling.
REST API
| Method | Path | Purpose |
|---|---|---|
| GET | /health |
Liveness plus per-database circuit-breaker state. Returns 503 (same body) when any database circuit is open, so monitoring can tell a healthy replicator from one stalled on a database outage that a bare liveness probe would miss. |
| GET | /status |
Per-chain applied block heights and per-subscriber lag across every served combination. |
| GET | /status/:dbType/:chain/:network |
The same status for a single combination. |
| GET | /catalog |
The (dbType, chain, network) combinations this server serves. |
| GET | /snapshot/:dbType/:chain/:network |
Full database snapshot for bootstrapping a fresh replica. |
| GET | /snapshot/:dbType/:chain/:network/since/:blockHeight |
Incremental snapshot of everything after :blockHeight, used to fill a gap after a disconnect before re-subscribing. |
| GET | /snapshot-rows/:dbType/:chain/:network/:table?after_id=&limit= |
One id-ordered page of an append-only lookup table (e.g. index_transactions), so a truncated replica can sync a multi-million-row table in bounded pages instead of one full dump. :table is allowlisted to the pageable lookup set. Server mode only. |
| GET | /snapshot-dispensers/:dbType/:chain/:network?after_tx=&after_addr=&limit= |
One keyset page of the decoder dispensers table for the client’s replace-table reconcile (the table has no monotonic id and rows soft-expire, so the client periodically re-dumps and swaps it in atomically). Decoder-only. Server mode only. |
| GET | /schema/:dbType/:chain/:network |
The expected table schema, so a client can self-heal a drifted replica. |
| GET | /checkpoint/:dbType/:chain/:network/latest |
Newest hub-mirrored, federation-signed quorum checkpoint. Indexer DB only. |
| GET | /checkpoint/:dbType/:chain/:network/:height |
The signed checkpoint at a given block height (highest checkpoint_seq for that height). Indexer DB only. |
| GET | /checkpoints/:dbType/:chain/:network/range?from=&to= |
The signed-checkpoint chain over a block range, oldest first, one row per block (used by a client replica to roll its pinned trust root forward). Indexer DB only. |
| GET | /transparency/:dbType/:chain/:network/roots |
Transparency-log root entries. Indexer DB only (a decoder request returns 400). |
| GET | /transparency/:dbType/:chain/:network/proof/:block_index |
Merkle inclusion proof for the entry at :block_index. Indexer DB only. |
| GET | /transparency/:dbType/:chain/:network/root/latest |
The latest committed Merkle root. Indexer DB only. |
| POST | /validator-heartbeat/:dbType/:chain/:network |
REST fallback to the WebSocket heartbeat, for clients that cannot hold a persistent socket. The validator POSTs its applied height. Server mode only, rate-limited per IP. |
| GET | /validator-status |
Per-validator heartbeat state (applied height + computed lag) for every chain, nested coin to network to dbType. Server mode only. |
| GET | /validator-status/:dbType/:chain/:network |
The same for a single combination. |
| POST | /halt/clear/:dbType/:chain/:network |
Clear a consensus-divergence halt on a client after operator investigation. A halted client detected a hash mismatch between sources and stopped applying blocks; restart the service after clearing for a clean catch-up. |
WebSocket API
Subscribing
ws://host:3006/subscribe/:dbType/:chain/:network
For dbType=indexer, an optional ?sync_mode= query parameter selects the
tables streamed: full (default, all tables) or infra-only (only the
cross-chain infrastructure tables: stakes, delegations, validator_rewards,
prices, reward_claims, and the index_* lookup tables). Per-IP connection
limit: WS_MAX_PER_IP (default 100).
Server to client
statussent on connect and every 60 seconds. Carriesblock_height,block_time, and the consensus hashes:ledger_hash/actions_hash/contract_hashforindexer, orblock_hashfordecoder.blocksent per processed block. Same identity fields asstatusplus adataobject of changed rows by table; tables with no rows for the block are omitted to keep messages small.reorgsent when a reorganization is detected, carrying theblock_indexfrom which the client must roll back.
Client to server
auth(optional, within 5s of connecting):{ type, pubkey, sig, ts }, an Ed25519 signature over the timestamp. Authenticated validators may get priority handling.heartbeat(optional):{ type: "heartbeat", appliedBlock }reports the highest block the client has fully applied, so the server can compute lag (lag = lastSentBlock - appliedBlock) and surface it inGET /statusand the validator-status endpoints. Best-effort: the server silently ignores malformed or unknown messages, and a client that never heartbeats still receives blocks but reportsnulllag. Use this (or thePOST /validator-heartbeatfallback) to stay visible to operators.
sequenceDiagram
participant C as Client
participant S as Sync server
C->>S: connect ws /subscribe/:dbType/:chain/:network
opt auth, within 5s of connecting
C->>S: auth (pubkey, sig, ts)
end
S-->>C: status, on connect and every 60 seconds
S-->>C: block, per processed block
opt heartbeat
C->>S: heartbeat (appliedBlock)
end
S-->>C: reorg, block_index to roll back from
Note over C,S: disconnect
C->>S: reconnect after 5 seconds
C->>S: fetch incremental snapshot since last applied block
S-->>C: incremental snapshot
C->>S: resume subscription
Backpressure and reconnection
Backpressure is byte-based, not message-count based. The server drops a subscriber
when its send buffer exceeds WS_BACKPRESSURE_MAX_BYTES (default 16 MiB), or when
the buffer is non-empty and has not drained at all for WS_BACKPRESSURE_STALL_MS
(default 30s); any downward progress resets the stall timer, so a slow-but-draining
replica is not dropped. The client should reconnect (the reference client waits 5 seconds),
compare its last applied height against the initial status message, fetch an
incremental snapshot via /snapshot/.../since/:blockHeight to fill any gap, and
then resume the subscription.
Machine-readable spec
Unlike the encoder and hub (which serve OpenRPC) and the explorer (OpenAPI),
xchain-sync does not yet ship a machine-readable API spec. Generating one
(OpenAPI for the REST surface, plus an AsyncAPI or prose schema for the
WebSocket messages) is a tracked documentation gap.