Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.parquet.exchange/llms.txt

Use this file to discover all available pages before exploring further.

The Parquet indexer is the public read API for on-chain perp activity and market-hours metadata. It ingests Anchor event logs and price updates over Helius webhooks, persists them to Postgres, and exposes a REST surface plus a /ws WebSocket for realtime broadcasts. Base URL: https://mis-mac-mini.tail3d8671.ts.net The host is served via a Tailscale Funnel from the Mac Mini that runs Parquet’s price-pusher, keeper, and indexer. External clients hit a public Funnel IP — there is no Caddy edge or custom-domain proxy in this stack. Rate limit: an in-process limit enforced by the indexer service; overflow returns 429. The /webhook/helius/* paths are exempt (they are reserved for Helius and authenticated separately). CORS: FRONTEND_ORIGIN (or CORS_ORIGIN) is a comma-separated allowlist; if unset it defaults to [parquet.exchange, parquet-trade.vercel.app, localhost:3000]. Any *-mugshotbots-projects.vercel.app preview URL is also auto-allowed via regex so fresh preview deploys work without an env edit. A Private Network Access preflight middleware sits before cors() and emits Access-Control-Allow-Private-Network: true for opted-in requests — needed only when the client is itself on the Tailscale network. Server-to-server callers off the tailnet are unaffected. All timestamps in request params and response payloads are unix seconds unless explicitly stated otherwise.

Authentication

SurfaceAuth
Public REST (/events, /ohlcv, /stats, /fees, /staking, /staking/history, /leaderboard, /orderbook, /queue/*, /market-hours)None
/healthOptional Authorization: Bearer <HEALTH_AUTH_TOKEN> when HEALTH_AUTH_TOKEN is configured server-side
/webhook/helius/*Shared secret only — internal use, not for public consumers
WebSocket /wsNone

GET /events

Returns Anchor events decoded from on-chain logs, in chronological order. Cursor-paginated. Query params:
ParamTypeNotes
cursorstringOpaque cursor from a previous response’s nextCursor. Omit for the first page.
marketstringOptional market filter. Use canonical symbol (e.g. AAPL) or GLOBAL for cross-market events.
event_typestringOptional event-name filter (PositionOpened, PositionClosed, FundingUpdated, etc.).
limitnumberPage size, capped server-side.
Response:
type EventsResponse = {
  events: Array<{
    signature: string;
    event_index: number;
    slot: number;
    block_time: number;       // unix seconds
    market: string;           // canonical symbol or "GLOBAL"
    event_type: string;
    payload: Record<string, unknown>;
  }>;
  nextCursor: string | null;
};
curl 'https://mis-mac-mini.tail3d8671.ts.net/events?market=AAPL&event_type=PositionOpened&limit=50'
{
  "events": [
    {
      "signature": "5Kp...",
      "event_index": 0,
      "slot": 312456789,
      "block_time": 1715731200,
      "market": "AAPL",
      "event_type": "PositionOpened",
      "payload": { "trader": "...", "sizeUsdc": "1000000000", "side": 0 }
    }
  ],
  "nextCursor": "eyJzbG90IjozMTI0NTY3ODl9"
}
Valid markets are the 15 active symbols: AAPL, MSFT, NVDA, GOOGL, AMZN, META, TSLA, COIN, MSTR, PLTR, AMD, SPY, QQQ, GLD, USO. SMCI is recognized for historical reasons but has no oracle feed.
event_index counts only successfully-decoded Anchor events within a transaction. Non-matching log lines do not advance it, so gaps in event_index are expected.

GET /ohlcv

Time-windowed OHLCV candles built from indexed price rows. Query params:
ParamTypeNotes
marketstringCanonical symbol, required.
resolutionstringCandle width — e.g. 1m, 5m, 1h, 1d.
fromnumberWindow start, unix seconds.
tonumberWindow end, unix seconds.
limitnumberMax candles returned.
Response:
type OhlcvResponse = {
  market: string;
  resolution: string;
  candles: Array<{
    t: number;   // bucket start, unix seconds
    o: number;   // open  — raw mantissa / 100 (USD)
    h: number;
    l: number;
    c: number;
    v: number;   // volume in USDC
  }>;
};
curl 'https://mis-mac-mini.tail3d8671.ts.net/ohlcv?market=AAPL&resolution=1m&from=1715731200&to=1715734800'
/ohlcv returns raw mantissa / 100 for price fields. The WebSocket price feed broadcasts mantissa × 10^7 (1e9-normalized). Both yield USD but the divisors differ — see WebSocket /ws below. Do not mix formats.

GET /stats?market=SYMBOL

24-hour rolling stats for one market. Cached server-side for 30 seconds per market. Response:
type StatsResponse = {
  market: string;
  volume24h: string;        // USDC
  high24h: number;          // raw mantissa / 100
  low24h: number;
  priceChange24h: number;   // signed delta
  priceChangePct24h: number;
  trades24h: number;
};
curl 'https://mis-mac-mini.tail3d8671.ts.net/stats?market=NVDA'

GET /fees?market=SYMBOL

Fee accrual and distribution totals for the trailing 24 hours, plus the most recent distribution. 30-second per-market cache.
type FeesResponse = {
  market: string;
  accrued24h: string;       // USDC raw
  swept24h: string;
  distributed24h: string;
  lastDistribution: {
    signature: string;
    block_time: number;
    amount: string;
  } | null;
};

GET /staking

Global staking snapshot. APR, staker count, recent distributions. 60-second cache.
type StakingResponse = {
  aprBps: number;
  totalStaked: string;
  stakers: number;
  recentDistributions: Array<{
    signature: string;
    block_time: number;
    amount: string;
  }>;
};

GET /staking/history?owner=PUBKEY

Per-owner staking event history (stake, unstake, claim, restake). Cursor-paginated. owner is required.
type StakingHistoryResponse = {
  owner: string;
  entries: Array<{
    signature: string;
    block_time: number;
    event_type: string;
    payload: Record<string, unknown>;
  }>;
  nextCursor: string | null;
};

GET /leaderboard

Trader PnL aggregation. CTE-joined from PositionOpened (volume, size) and PositionClosed (PnL, trades, wins). 60-second cache. Query params:
ParamTypeAllowedDefault
timeframestring24h, 7d, 30d, all7d
sortstringpnl, volume, trades, winRatepnl
limitnumber≤ 100100
type LeaderboardResponse = {
  timeframe: string;
  sort: string;
  rows: Array<{
    trader: string;
    pnl: string;
    volume: string;
    trades: number;
    wins: number;
    winRate: number;       // 0..1
  }>;
};

GET /orderbook?market=SYMBOL

Aggregated trigger orderbook from on-chain OrderAccounts for the given market. Each side is capped at 25 levels. Behind a 3-second singleflight cache — concurrent requests share one RPC call. The underlying getProgramAccounts call has a 5-second AbortController timeout.
type OrderbookResponse = {
  market: string;
  bids: Array<{ price: number; size: string }>;   // descending
  asks: Array<{ price: number; size: string }>;   // ascending
  spread: number | null;
  midPrice: number | null;
};

GET /queue/:marketId

V4 LP-payout queue snapshot for one market. Derived from indexed queue events (Enqueued, Harvested, EntryVoided, SideBucketCredited, QueueDrained, PhantomCreditDrained).
type QueueMarketResponse = {
  marketId: string;
  head: number;
  tail: number;
  totalOwed: string;                     // USDC raw
  depth: number;                         // pending entries
  sideBucket: string | null;             // see caveat
  estimatedDrainSeconds: number | null;  // see caveat
};
sideBucket and estimatedDrainSeconds return null until the pool-queue snapshot poller lands. The apply_queue_collateral_draw CPI emits no event, so event handlers cannot reconstruct collateral_drawn alone — the poller will populate the pool_queue_snapshots table by reading on-chain UserQueueClaims directly. Until then, only the event-derived fields (head, tail, totalOwed, depth) are accurate.
See Payout queue for the user-facing semantics of these fields.

GET /queue/user/:wallet

Per-wallet view: every queue entry across markets plus a per-market claims aggregate.
type QueueUserResponse = {
  wallet: string;
  entries: Array<{
    market_id: string;
    idx: number;
    status: "pending" | "harvested" | "voided";
    amount: string;
    enqueued_at: number;
    slot: number;
  }>;
  claims: Array<{
    market_id: string;
    unpaid_owed: string;
    collateral_drawn: string;
    phantom_unpaid_owed: string;
  }>;
};

GET /market-hours

Proxy to Alpaca’s /v2/clock plus the next-session edges from /v2/calendar, cached server-side for 30 seconds. The frontend’s MarketClosedBanner polls this every 60 seconds to surface the RTH state before traders sign.
type MarketHoursResponse = {
  is_open: boolean;
  timestamp: string;   // ISO 8601, UTC
  next_open: string;   // ISO 8601, UTC
  next_close: string;  // ISO 8601, UTC
  session: "regular" | "early_close" | "closed";
};
curl 'https://mis-mac-mini.tail3d8671.ts.net/market-hours'
is_open: false is the canonical signal that any state-changing on-chain instruction will revert with PriceStale — the price-pusher’s loop short-circuits outside RTH, so the oracle ages out within a few seconds of the close. Clients should disable the trade panel and queue intent locally until the next next_open.

GET /health

Liveness + downstream check. Has an in-memory fast path and a Postgres slow path. When HEALTH_AUTH_TOKEN is set on the server, callers must send Authorization: Bearer <token>.
type HealthResponse = {
  ok: boolean;
  db: "ok" | "down";
  lastEvent: { signature: string; block_time: number } | null;
  lastPrice: { market: string; ts: number } | null;
  queueDepth: number;        // insert queue, not the LP queue
  watchdog: "ok" | "stale";  // 120s threshold
};

POST /webhook/helius/

Helius-delivered raw transaction payloads. Gated by Authorization: <HELIUS_WEBHOOK_SECRET> with constant-time comparison. Decoded via decodeAnyEventLog (events) or decodeUpdatePriceTx (prices) and enqueued for batched DB insertion.
Documented for completeness. Public consumers should never call these — they are reserved for the Helius integration and have no stable contract for external use.
A 503 response from /webhook/helius/* means the indexer’s in-memory insert queue is full (default INSERT_QUEUE_MAX_SIZE=10000). Helius retries inside its own delivery window; no action required from external callers.

WebSocket /ws

Realtime price and event broadcasts. Connection: wss://mis-mac-mini.tail3d8671.ts.net/ws The WebSocket stays connected across the RTH halt, but price messages stop arriving — the push loop short-circuits outside session hours. Message shapes:
type PriceMessage = {
  type: "price";
  market: string;
  // mantissa * 10^7 (1e9-normalized — divide by 1e9 for USD)
  price: string;
  ts: number;
};

type EventMessage = {
  type: "event";
  market: string;        // canonical symbol, or "GLOBAL"
  event_type: string;
  signature: string;
  payload: Record<string, unknown>;
};
Limits:
LimitValueBehavior on breach
Server-wide client cap500New connections refused
Inbound messages per client120/minuteConnection closed with code 1008
GLOBAL events (e.g. parameter updates, distributions) broadcast to every connected client regardless of any subscription state.
Price format split. The WebSocket emits mantissa × 10^7 (1e9-normalized — e.g. 114000000000 for $114.00). The REST /ohlcv endpoint returns raw mantissa / 100 (e.g. 11400 for the same price). Both produce USD when scaled correctly, but the divisors are different (1e9 vs 100). Pick the right one per data source.

Rate limits and errors

StatusMeaning
400Invalid params (bad resolution, unparseable cursor, etc.)
404Unknown market — not in VALID_MARKETS
429Rate limit exceeded; webhook paths exempt
500Server-side decode or DB error
503/webhook/helius/* only — insert queue full, Helius will retry
For background on rate-limit enforcement, see the indexer operator runbook.

See also

  • Payout queue — user-facing semantics of /queue/* responses.
  • Contracts — program IDs that emit the events this API indexes.
  • SDK — typed client wrappers around these endpoints.