Read API and streams
The /api/dusk/v1 routes serve live snapshots, event history and change streams, each tied to one deployment and stamped with its freshness.
Updated Nov 9, 2026
The native routes under /api/dusk/v1 are how the app reads live state without touching RPC in the browser. Every response is bound to one deployment identity, says how fresh it is, and never invents a value.
The envelope
Successful protocol-data responses carry a dusk-deployment.v2 envelope. This excerpt shows selected fields; complete responses also include the genesis hash, ProgramData addresses, upgrade authorities, delegate identity, raw IDL hashes, executable hashes and deploymentIdentitySha256.
{
"success": true,
"deployment": {
"schemaVersion": "dusk-deployment.v2",
"network": "devnet",
"programId": "2rYBVfGubUVbHrLeut6Dn4A9ShM695dGSPCErdsGo16Y",
"programDataSlot": "517844520",
"leverageDelegateProgramDataSlot": "517825628",
"idlSha256": "65de3da39b3f57495f47f331a66b26b620f24756858404b2281f5e6268bbbe58",
"commitment": "confirmed",
"sourceSlot": 517844521,
"observedAt": "2026-11-02T11:59:01.970Z"
},
"data": {}
}The active projection revision is devnet-2026-11-02-governable-tranches. It carries the deployed tranche interface and starts after the Dusk and leverage-delegate deployments are both complete.
Revision-bearing data contracts expose the active revision in their provenance or coverage metadata; it is separate from the envelope's buildRevision, which identifies the API build. /health and /status remain operational endpoints that can explain an unavailable deployment envelope.
Clients configure the identity they expect and reject anything else; a mismatch is 409 protocol_identity_mismatch. Range reads that cannot meet the requested consistency return 503 freshness_unavailable with metadata and no decision-bearing data; commitment is never silently downgraded.
Deployment envelope timestamps are ISO 8601 strings. Data timestamps and expiry fields follow each payload's schema; an expiry is never renewed by delivery or a cache hit.
Freshness states: ready (bounds met, no known gap), catching_up (a backfill is running), reconciling (canonical pointers may still change), stale (your max_lag_slots bound was exceeded). confirmed is the app's default commitment; finalized is required for irreversible accounting and durable keeper deduplication.
Routes
Identity and configuration
| Route | Purpose |
|---|---|
GET /config | The indexed market configuration and build-pinned deployment envelope; minimumSourceSlot asks for an envelope at or after a slot |
GET /health, /status, /metrics, /provenance | Operational endpoints |
Markets
| Route | Purpose |
|---|---|
GET /markets | Indexed markets |
GET /markets/state and /markets/state/:market | Cached live snapshots with source slot and expiry |
GET /markets/:market and /markets/:market/events | Market detail and its finalized events (events, since, until, limit, offset) |
GET /history/quotes/:market | Sampled program spot-price candles: since (required), until, side (base or quote), resolutionSeconds (60, 300, 900, 3600, 14400, 86400), at most 2,000 buckets |
GET /prices/:mint | Price history for a mint: market, at, maxAgeSeconds, limit, offset |
GET /virtual-book/:market/stream | Depth snapshots as server-sent events |
GET /markets/:market/operator | Market operator, optional manager and the single pending bundle from one confirmed bank |
Quote candles are decimal strings in NAD, already normalized for mint decimals: divide by 10⁹ and nothing else. They are program previews, not trade candles; empty buckets are not filled and historyRangeComplete is always false.
Owners
| Route | Purpose |
|---|---|
GET /owners/:owner/accounts/:kind | The complete set of borrow, leverage, yield or referral-accrual accounts, as base64 SDK bytes from one confirmed bank; at most 500 |
GET /owners/:owner/leverage-valuations/:address | An exact simulated full-close receipt for one leverage position, with verification slot |
GET /owners/:owner/portfolio-snapshots | Portfolio value history: since, until, sampleSeconds |
GET /owners/:owner/yield-claims | Recorded yield claims: market, since, until |
GET /owners/:owner/yield | Claimable yield for every LP held and every unsettled hLP order escrow |
GET /owners/:owner/referral-partner | Referral terms and per-market accruals, net of current transfer fees |
GET /liquidations | Liquidatable borrow positions with the program's previewed flash and emergency terms |
History and analytics
| Route | Purpose |
|---|---|
GET /history/events | Finalized canonical events with cursors; version=2 adds HlpOpened, HlpClosed and YieldClaimed; category=activity&owner= gives a wallet's receipts |
GET /history/orders | Order history |
GET /analytics/activity | Activity aggregates with volumes split into spot, credit and margin: since, until, market |
GET /analytics/yield-rates | Recorded claimable srLP swap-fee and interest rates over a window of one hour to ninety days: since, until, market |
Spot, credit and margin volumes overlap by definition and must not be summed. Yield rates are APR components of realized, claimable yield, not a total APY.
Payload streams
| Route | Purpose |
|---|---|
GET /payload-snapshot and GET /payloads | A full initial snapshot, then complete revisions as dusk-payload events. kind=wallet&owner= publishes positions, valuations and orders together; kind=statistics&range=24h or all publishes activity aggregates and collateral exposure |
Wallet payloads publish atomically to the consumer: a position whose close simulation was rejected appears as unavailable with a reason, never as a zero. Captures pace at two seconds for wallets and five for statistics.
Change notifications
GET /changes is a server-sent event stream of hints, not data:
{
"schemaVersion": "dusk-read-change.v1",
"streamId": "…",
"sequence": 12,
"kind": "change",
"sourceSlot": 502849270
}The first frame is a resync; heartbeats every two seconds have slot 0; change bursts coalesce to the highest slot in a 250 ms window. A frame tells you to re-read through your normal API or RPC boundary; it carries no reserves, prices or balances.
Reconnects get a new resync, and missed frames are not replayed. Each API process allows 128 open streams and closes a backpressured one rather than queueing.
The same hints are available as gRPC-Web: omnipair.stream.StreamService/StreamDuskChanges, whose DuskChange.envelope_json carries the identical frame. The app itself subscribes over Socket.IO at the API's /socket.io path; GET /changes stays for clients that predate it. The legacy StreamSwapsUpdates RPC cannot supply Dusk prices and stays for V1 consumers only.
Consumer rules
For an app:
- Request
confirmedwith amax_lag_slotsthat suits the view. - Show degraded or reconciling state; never treat cached data as current.
- Treat the wallet's own confirmation as authoritative for an action you just sent; the indexer may trail it.
- Invalidate cached pages when the identity or reconciliation generation changes.
For a keeper:
- Use the indexer only to discover and prioritise.
- Require the full identity in every response.
- Re-fetch all decision accounts from RPC immediately before execution.
- Rebuild and simulate against the pinned IDL before signing.
The indexer never authorises a liquidation, an order execution, a bid or an arbitrage on its own.
Canonical history
Normal endpoints expose only rows referenced by canonical events. Ingestion stores an alternate blockhash first, reconciles the winning block through RPC, rolls back affected projections in one transaction, then advances the canonical pointer.
A finalized canonical row is never replaced; conflicting finalized evidence marks the service unavailable and pages an operator. Pagination cursors encode the identity, commitment, the canonical ordering tuple and the reconciliation generation, so a stale cursor is 409 cursor_invalidated rather than a silent gap.