Skip to content

API reference

Fourteen endpoints, five channels, one envelope.

Written from the contract between the API and this application (packages/api-contracts). The API is not deployed for this site yet; examples use https://<api-host> in place of its address. Every value says where it came from.

Conventions

  • Base path /api/v1. All endpoints are GET, public and read-only. There is no authentication because nothing can be written.
  • Numbers. Every WAD amount (prices, quantities, collateral, fractions, rates) is a decimal string of an 18-decimal integer, e.g. "60000000000000000000000" for 60,000. Convert with BigInt; never parseFloat. Counts, ids and basis points are plain JSON numbers.
  • Times are ISO-8601 strings in UTC. Addresses are 0x plus 40 hex digits, lower-cased by the indexer; hashes 0x plus 64.
  • Markets can be addressed by numeric id or symbol wherever a path has :marketId.
  • Enumerations are the on-chain uint8 values, listed on the developer portal; label fields (statusLabel, assetClassLabel) accompany them where a human reads the response.
  • Schemas are zod objects exported from @elysta/api-contracts; this page is written from them. Parse responses with them and a shape the API never promised cannot reach your code.

Envelope and provenance

Every successful response is { data, meta }. The meta object is how the API keeps the thesis's promise that an estimate is never presented as a confirmed value.

{
  "data": { ... },
  "meta": {
    "source": "indexed",
    "observedAt": "2026-11-08T14:03:21.512Z",
    "block": 1842211,
    "chainId": 99901,
    "freshness": "live"
  }
}
FieldTypeNotes
sourcechain | indexed | computed | estimated | unavailableWhere the figures came from (THESIS.md §38). An estimate is never labelled as confirmed.
observedAttimeWhen the API assembled the response.
blockinteger | nullThe chain block the figures describe, when known.
chainIdinteger
freshnesslive | delayed | stale | unavailableOptional. From the indexer's heartbeat and lag: live under 30 s and 50 blocks, delayed under 5 min, stale beyond; unavailable when it has never reported.

Freshness

Judged from the indexer’s heartbeat and how far its checkpoint is behind the chain head: live when the heartbeat is under 30 seconds old and the lag under 50 blocks, delayed under 5 minutes, stale beyond, and unavailable when the indexer has never reported, because then nothing can be said. Individual figures carry their own provenance where it differs from the response: a market’s mark.source and openInterest.source, for instance.

Errors and rate limits

Every failure leaves the API as { error: { code, message, details? } } with a matching HTTP status.

{ "error": { "code": "BAD_REQUEST", "message": "cursor must have the form <block>:<logIndex>" } }
CodeHTTP statusMeaning
NOT_FOUND404No such market, account route or resource.
BAD_REQUEST400A malformed parameter: an unknown interval, a cursor not of the form <block>:<logIndex>, an empty types list.
RATE_LIMITED429More requests per minute from one client address than the deployment allows.
UNAVAILABLE503The API is up but a dependency (database, chain RPC) is not. The figures could not be read; nothing is estimated in their place.
INTERNAL500A bug, including a response that failed its own contract validation.

Rate limits

Configured per deployment: RATE_LIMIT_PER_MINUTE requests per minute per client address, default 600, after which the API answers 429 RATE_LIMITED. The WebSocket pings on WS_HEARTBEAT_SECONDS (default 20) and drops a client that misses two pings in a row; the welcome message reports the live value. Browser origins are allow-listed with WEB_ORIGINS.

Markets

Public market data: the registry’s configuration and status, the validated index, the mark, the book, trades, candles and funding.

GET

/api/v1/markets

Every listed market with its parameters, validated index, mark, last trade, 24-hour figures, open interest and funding.

Returns data: marketSummary[]

curl $API/api/v1/markets
GET

/api/v1/markets/:marketId

One market.

:marketId path
integer | symbol — The on-chain id or the symbol, e.g. 1 or BTC-USD.

Returns data: marketSummary

curl $API/api/v1/markets/BTC-USD
GET

/api/v1/markets/:marketId/orderbook

The order book, read from the on-chain book: aggregated levels with order counts and cumulative quantity, best bid and ask, spread, mid, mark and index.

:marketId path
integer | symbol — The on-chain id or the symbol, e.g. 1 or BTC-USD.
levels query
integer — Levels per side. The terminal asks for 20.

Returns data: orderBook

curl "$API/api/v1/markets/BTC-USD/orderbook?levels=10"
GET

/api/v1/markets/:marketId/trades

Recent fills, newest first, each with its transaction and block.

:marketId path
integer | symbol — The on-chain id or the symbol, e.g. 1 or BTC-USD.
limit query
integer — The terminal asks for 60.

Returns data: trade[]

curl "$API/api/v1/markets/BTC-USD/trades?limit=50"
GET

/api/v1/markets/:marketId/candles

OHLCV candles built from indexed fills, ascending.

:marketId path
integer | symbol — The on-chain id or the symbol, e.g. 1 or BTC-USD.
interval query
1m | 5m | 15m | 1h | 4h | 1d
limit query
integer — Newest buckets to return. The terminal asks for 500.

Returns data: { marketId: integer, interval, candles: candle[] }

Candles exist only where trades happened. A new market or a quiet interval returns fewer candles, not flat ones.

curl "$API/api/v1/markets/BTC-USD/candles?interval=1h&limit=168"
GET

/api/v1/markets/:marketId/funding

The market's funding configuration, cumulative index, next settlement time, predicted rate (an estimate) and settlement history.

:marketId path
integer | symbol — The on-chain id or the symbol, e.g. 1 or BTC-USD.
limit query
integer — Settlements to return, newest first.

Returns data: { marketId, intervalSeconds, maxRatePerInterval: wad, cumulativeIndex: wad | null, nextFundingAt: time | null, predictedRate: wad | null, history: fundingEvent[] }

curl "$API/api/v1/markets/BTC-USD/funding?limit=24"

Accounts

Balances, positions, orders and history for any address. Nothing here is private: every figure is public chain data, served faster.

GET

/api/v1/accounts/:address

Balances, open positions and open orders for an address, in one response.

:address path
address — Any address; an account the indexer has never seen returns empty collections and zero balances.

Returns data: accountView

curl $API/api/v1/accounts/0xYourAddress
GET

/api/v1/accounts/:address/positions

Open positions only.

:address path
address — Any address; an account the indexer has never seen returns empty collections and zero balances.

Returns data: positionView[]

curl $API/api/v1/accounts/0xYourAddress/positions
GET

/api/v1/accounts/:address/orders

Open orders only. Historical orders are in the history feed.

:address path
address — Any address; an account the indexer has never seen returns empty collections and zero balances.

Returns data: orderView[]

curl $API/api/v1/accounts/0xYourAddress/orders
GET

/api/v1/accounts/:address/history

Everything that happened to an account, newest first, each item tied to its transaction: fills, order events, deposits, withdrawals, funding payments, liquidations, margin adjustments and realised PnL.

:address path
address — Any address; an account the indexer has never seen returns empty collections and zero balances.
limit query
integer — Items per page. The web app asks for 100.
types query
string — Comma-separated subset of fill, order, deposit, withdrawal, funding, liquidation, margin, realized. Default: all.
cursor query
string — Keyset cursor of the form <block>:<logIndex>; pass the previous page's nextCursor.

Returns data: { items: historyItem[], nextCursor: string | null }

curl "$API/api/v1/accounts/0xYourAddress/history?types=fill,funding&limit=50"

Protocol and leaderboard

GET

/api/v1/protocol/status

The status page's source: chain connection, contracts, indexer, keeper, API, WebSocket, per-market oracle health, market status counts and the vault's figures.

Returns data: protocolStatus

curl $API/api/v1/protocol/status
GET

/api/v1/protocol/insurance-fund

The insurance fund balance, protocol fees, bad debt and PnL pool as last read from the vault, with the fund's event history.

Returns data: insuranceFundView

curl $API/api/v1/protocol/insurance-fund
GET

/api/v1/protocol/liquidations

Recent liquidations across all markets, newest first.

limit query
integer — The risk page asks for 50.

Returns data: liquidationView[]

curl "$API/api/v1/protocol/liquidations?limit=20"
GET

/api/v1/leaderboard

Accounts ranked by realised PnL from indexed fills over a window. basis says exactly how the ranking was computed.

window query
24h | 7d | 30d | all

Returns data: { window, entries: leaderboardEntry[], basis: string }

curl "$API/api/v1/leaderboard?window=7d"

Leaderboard basis

The leaderboard ranks accounts by realised PnL from indexed fills over the window, and the response’s basis string states the exact computation. It is derived data: a ranking, not a balance, and it is labelled as such.

Response shapes

Each object the endpoints return, field by field, in the order the schema declares them.

marketSummary

FieldTypeNotes
idintegerThe on-chain market id.
symbolstringe.g. BTC-USD
underlyingstring
assetClass0 | 1 | 2 | 3Crypto, Equity, Commodity, Index
assetClassLabelstring
status0 | 1 | 2 | 3 | 4Inactive, Active, ReduceOnly, Paused, Halted
statusLabelstring
sessionOpenbooleanWhether the market's session is open now.
tradingOpenbooleanisTradingOpen: status Active or ReduceOnly, session open and a valid index.
priceDecimalsintegerDisplay hint; the chain always uses WAD.
qtyDecimalsinteger
tickSizewad
lotSizewad
minOrderQtywad
maxOrderQtywad
maxLeveragenumber
initialMarginFractionwad
maintenanceMarginFractionwad
makerFeewad
takerFeewad
fundingIntervalSecondsinteger
maxFundingRatePerIntervalwad
maxImpactBpsinteger
priceBandBpsinteger
markBandBpsinteger
indexindexQuoteThe validated index price and its health.
mark{ price: wad | null, source: provenance }
last{ price: wad, at: time, side: 0 | 1 } | nullThe most recent fill; null when the market has never traded.
change24h{ absolute: wad, bps: number } | nullnull without a trade 24 hours ago to compare against.
volume24h{ base: wad, quote: wad, trades: integer }From indexed fills only.
openInterest{ long: wad, short: wad, notional: wad | null, source: provenance }Read from the risk engine; never estimated.
funding{ lastRate, lastSettledAt, cumulativeIndex, nextFundingAt, predictedRate }Each nullable. predictedRate is labelled an estimate.
oracle{ adapter: address, feedSource: string | null, heartbeatSeconds: integer, maxDeviationBps: integer }

indexQuote

FieldTypeNotes
pricewad | null
publishedAttime | nullThe source observation time, not the block time.
validbooleanFalse when missing, stale, or the market is halted or unlisted.
healthno_price | ok | stale | deviation
ageSecondsnumber | null

orderBook

FieldTypeNotes
marketIdinteger
bidsbookLevel[]Best first.
asksbookLevel[]Best first.
bestBidwad | null
bestAskwad | null
spreadwad | null
spreadBpsnumber | null
midwad | null
markwad | null
indexwad | null
depthLevelsintegerHow many levels per side were requested.

bookLevel

FieldTypeNotes
pricewad
qtywadTotal resting quantity at the level.
countintegerOrders at the level.
cumulativewadQuantity from the best level to this one.

trade

FieldTypeNotes
seqintegerThe Fill event's sequence number.
marketIdinteger
pricewad
qtywad
takerSide0 | 1Buy or Sell
makeraddress
takeraddress
makerOrderIdinteger
takerOrderIdinteger
makerFeewad
takerFeewad
txHashhash
blockNumberinteger
timetime

candle

Built from indexed fills only. An interval with no trades has no candle; nothing is synthesised.

FieldTypeNotes
ttimeBucket start.
o, h, l, cwad
vBasewad
vQuotewad
nintegerTrades in the bucket.

fundingEvent

FieldTypeNotes
marketIdinteger
settledAttime
ratewadSigned. Positive: longs paid shorts.
cumulativeIndexwad
markPricewad
indexPricewad
premiumTwapwad
txHashhash
blockNumberinteger

positionView

FieldTypeNotes
marketIdinteger
symbolstring
qtywadAbsolute quantity; side carries the direction.
sidelong | short
avgEntryPricewad
markPricewad | null
marginwad
notionalwad | null
leveragewad | nullnotional / margin
unrealizedPnlwad | null
pendingFundingwad | nullPositive: the position would receive at its next touch.
liquidationPricewad | null
marginRatiowad | null
realizedPnlwad
fundingPaidwad
feesPaidwad
openedAttime | null
updatedAttime

orderView

FieldTypeNotes
orderIdinteger
marketIdinteger
symbolstring
side0 | 1
kind0 | 1Limit, Market
tif0 | 1 | 2 | 3GTC, IOC, FOK, PostOnly
pricewad
qtywad
remainingwad
filledwad
reservedwad
reduceOnlyboolean
expiresAttime | null
status0 | 1 | 2 | 3 | 4None, Open, Filled, Cancelled, Expired
cancelReasoninteger | nullCancelReason when cancelled.
seqinteger
createdAttime
updatedAttime
txHashhashThe placing transaction.

balances

FieldTypeNotes
availablewad
reservedwad
lockedwad
unrealizedPnlwad | null
pendingFundingwad | null
equitywad | nullavailable + reserved + locked + unrealized PnL + pending funding
depositedTotalwad
withdrawnTotalwad
feesPaidTotalwad
realizedPnlTotalwad
fundingTotalwad
reconciledboolean | nullWhether the replayed ledger matched accountOf on chain at the last check.
reconciledAttime | null

accountView

FieldTypeNotes
addressaddress
balancesbalances
positionspositionView[]
openOrdersorderView[]

historyItem

A discriminated union on type. Every variant carries type, time, txHash and blockNumber.

FieldTypeNotes
fillmarketId, symbol, side, price, qty, fee, role: maker | taker, orderId
ordermarketId, symbol, orderId, event: placed | rested | filled | cancelled | expired, side, price, qty, remaining, cancelReason
depositamount: wad, tokenAmount: wad | null
withdrawalamount: wad, tokenAmount: wad | null
fundingmarketId, symbol, payment: wadPositive: paid.
liquidationmarketId, symbol, role: liquidated | liquidator, qty, markPrice, penalty, incentive, full
marginmarketId, symbol, delta: wad, margin: wad
realizedmarketId, symbol, realizedPnl, fee, fillPrice, fillQty

protocolStatus

What the status page shows. Component statuses are ok | degraded | down | unknown, judged from heartbeats: ok under 45 s, degraded under 3 min, down beyond, unknown when never reported.

FieldTypeNotes
environmentlocal | testnet | mainnet
chain{ chainId, name, rpcOk, headBlock: integer | null, headBlockAt: time | null, explorerUrl: string | null }
contracts{ collateralToken, ElystaCollateralVault, ElystaMarketRegistryOracle, ElystaKeeperOracleAdapter, ElystaPerpRiskEngine, ElystaOrderBookEngine: address; startBlock: integer; testCollateral: boolean }
indexer{ status, lastBlock: integer | null, lagBlocks: integer | null, heartbeatAt: time | null, details: object | null }
keeper{ status, lastPostAt: time | null, heartbeatAt: time | null, details: object | null }
api{ status, startedAt: time, version: string }
websocket{ status, clients: integer }
oracle{ marketId, symbol, health, publishedAt: time | null, ageSeconds: number | null, feedSource: string | null }[]
markets{ total, active, reduceOnly, paused, halted, inactive: integer }
vault{ insuranceFund, protocolFees, badDebt, pnlPool, totalAvailable, totalReserved, totalLocked, assets: wad | null; balanced: boolean | null }balanced is assets == liabilities + pnlPool when every term is present.

insuranceFundView

FieldTypeNotes
balancewad | null
protocolFeeswad | null
badDebtwad | null
pnlPoolwad | null
events{ time, txHash, blockNumber, kind: string, amount: wad, account: address | null, balanceAfter: wad | null }[]kind: funded, fee_share, cover, bad_debt.

liquidationView

FieldTypeNotes
timetime
txHashhash
blockNumberinteger
marketIdinteger
symbolstring
accountaddress
liquidatoraddress
qtywad
markPricewad
penaltywad
liquidatorIncentivewad
insuranceContributionwad
badDebtwad
fullboolean

leaderboardEntry

FieldTypeNotes
accountaddress
realizedPnlwad
volumewad
tradesinteger
fundingNetwad
feesPaidwad

WebSocket

Connect to the API's /api/v1/ws endpoint once it is deployed. Subscribe to channels with JSON messages; receive typed messages with block, transaction, observation time and freshness.

Client messages

MessageNotes
{ "op": "subscribe", "channel": "book", "marketId": 1 }Channels markets, book and trades take a marketId.
{ "op": "subscribe", "channel": "account", "account": "0xYourAddress" }The account channel takes an address.
{ "op": "subscribe", "channel": "protocol" }Protocol status needs no parameter.
{ "op": "unsubscribe", "channel": "book", "marketId": 1 }The same parameters as the subscription.
{ "op": "ping" }Answered with pong.

Server messages

Every server message has type, block (integer or null), txHash (hash or null), observedAt and freshness, then data. Market-scoped messages add marketId; account-scoped messages add account.

typeChanneldataNotes
welcome—{ chainId, version, heartbeatSeconds }Sent once on connect.
pong—{}
subscribed—{ channel, marketId?, account? }
error—{ code, message }
market.updatemarketsmarketSummarymarketId on the message.
market.statusmarkets{ status, statusLabel, reason }reason is a StatusReason code.
funding.settledmarketsfundingEvent
liquidationmarketsliquidationView
book.updatebookorderBook
tradetradestrade
order.updateaccountorderViewaccount on the message.
position.updateaccountpositionView
balance.updateaccountbalances
protocol.statusprotocolprotocolStatus

Behaviour

  • The first message is welcome, with the chain id, the API version and the heartbeat interval.
  • A subscription is acknowledged with subscribed; a malformed request gets error and the connection stays open.
  • Messages are pushed when the indexer records a change; they are hints to re-read, not a ledger. Confirm anything that matters against a REST read or the chain.
  • Reconnect on close and resubscribe; there is no replay. Use the REST endpoints to fill the gap.