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
| Surface | Auth |
|---|
Public REST (/events, /ohlcv, /stats, /fees, /staking, /staking/history, /leaderboard, /orderbook, /queue/*, /market-hours) | None |
/health | Optional 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 /ws | None |
GET /events
Returns Anchor events decoded from on-chain logs, in chronological order. Cursor-paginated.
Query params:
| Param | Type | Notes |
|---|
cursor | string | Opaque cursor from a previous response’s nextCursor. Omit for the first page. |
market | string | Optional market filter. Use canonical symbol (e.g. AAPL) or GLOBAL for cross-market events. |
event_type | string | Optional event-name filter (PositionOpened, PositionClosed, FundingUpdated, etc.). |
limit | number | Page 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:
| Param | Type | Notes |
|---|
market | string | Canonical symbol, required. |
resolution | string | Candle width — e.g. 1m, 5m, 1h, 1d. |
from | number | Window start, unix seconds. |
to | number | Window end, unix seconds. |
limit | number | Max 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:
| Param | Type | Allowed | Default |
|---|
timeframe | string | 24h, 7d, 30d, all | 7d |
sort | string | pnl, volume, trades, winRate | pnl |
limit | number | ≤ 100 | 100 |
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:
| Limit | Value | Behavior on breach |
|---|
| Server-wide client cap | 500 | New connections refused |
| Inbound messages per client | 120/minute | Connection 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
| Status | Meaning |
|---|
400 | Invalid params (bad resolution, unparseable cursor, etc.) |
404 | Unknown market — not in VALID_MARKETS |
429 | Rate limit exceeded; webhook paths exempt |
500 | Server-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.