{"openapi":"3.1.0","info":{"title":"HYPO public read API","version":"1.0.0","summary":"54 public read endpoints across HL / PM / Kalshi.","description":"Public HTTP API for sovereign-store records, analytics, and cross-venue arbitrage discovery. CC0-licensed responses.\n\n## Tiers & auth\nMost endpoints are FREE and need no auth (the funnel: feed, movers, m2m index/quote). PREMIUM endpoints (stats, m2m bundle, arb) require an API key when monetization is enabled — provide it via `Authorization: Bearer <key>` or `X-API-Key`. Tiers: free / pro / enterprise, with monthly quota + per-key rate + per-VENUE entitlement. Premium responses carry `X-Api-Tier`, `X-RateLimit-*`, `X-Quota-*`; denials are 401 (key) / 402 (tier) / 403 (venue) / 429 (rate|quota). Check your usage at `/api/account/usage`. See the `x-monetization` extension + docs/MONETIZATION.md.\n\n## Versioning\nTwo-tier: (1) the spec is versioned by deployment via `info.version` (currently 1.0.0); breaking changes bump it and are announced via /api/changelog. (2) ADDITIONALLY every `/api/m2m/*` response body carries an explicit `apiVersion` string (currently `v1`) — within a version only backwards-compatible optional fields are added; a breaking change bumps it. Routes OUTSIDE `/api/m2m/*` are NOT body-versioned: detect breaking changes on those via `info.version`/deployment, not a per-response field. See the `x-api-versioning` extension for a machine-readable summary.","license":{"name":"CC0 1.0 Universal","url":"https://creativecommons.org/publicdomain/zero/1.0/"},"contact":{"name":"hypo.markets","url":"https://hypo.markets"},"x-api-versioning":{"model":"two-tier","deployment":{"field":"info.version","current":"1.0.0"},"body":{"scope":"/api/m2m/*","field":"apiVersion","current":"v1","policy":"additive-within-version; breaking changes bump the version"}}},"servers":[{"url":"https://hypo.markets","description":"Production"}],"tags":[{"name":"history","description":"Sovereign history store reads + aggregation"},{"name":"asset analytics","description":"Per-asset risk / slippage / carry"},{"name":"arbitrage","description":"Cross-venue arb opportunities + simulation"},{"name":"movers","description":"24h gainers + losers across venues"},{"name":"correlations","description":"Pairwise Pearson + Spearman matrices"},{"name":"health","description":"Probe aggregators (in-process + upstream)"},{"name":"account","description":"API-key usage/quota (self-service) + admin key management"}],"x-monetization":{"enabledEnv":"HYPO_PAID_GATING","authHeaders":["Authorization: Bearer <key>","X-API-Key: <key>"],"tiers":["free","pro","enterprise"],"premiumFeatures":["stats","m2m-bundle","arb","history","history-export","realtime"],"usageEndpoint":"/api/account/usage","docs":"https://hypo.markets/api-docs"},"paths":{"/api/history/{venue}/{asset}":{"get":{"operationId":"api_history_venue_asset","summary":"Raw JSONL window from the sovereign history store","description":"Return the recorded observations for one (venue, asset) over an optional time window. Records are sorted chronologically.\n\nResponse shape: `{ ok, venue, asset, count, records: [{t, p, v?, s?}] }`","tags":["history"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"venue","in":"path","required":true,"description":"Path-param venue (URL slug).","schema":{"type":"string"}},{"name":"asset","in":"path","required":true,"description":"Path-param asset (URL slug).","schema":{"type":"string"}},{"name":"fromMs","in":"query","required":false,"description":"Inclusive lower-bound timestamp (epoch ms).","schema":{"type":"integer"}},{"name":"toMs","in":"query","required":false,"description":"Inclusive upper-bound timestamp (epoch ms).","schema":{"type":"integer"}},{"name":"limit","in":"query","required":false,"description":"Max records returned. Default 1000, cap 50000.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=30, stale-while-revalidate=60`","schema":{"type":"string"}},"X-Api-Tier":{"description":"Caller's resolved tier (free|pro|enterprise).","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Per-key requests/minute for the tier.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current minute.","schema":{"type":"integer"}},"X-Quota-Limit":{"description":"Monthly request quota (omitted on unlimited tiers).","schema":{"type":"integer"}},"X-Quota-Remaining":{"description":"Monthly quota remaining.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing / invalid / revoked / expired API key (when gating is enabled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Payment required — feature 'history' needs tier 'pro'+","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Key not entitled to the requested venue (per-venue entitlement)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-key rate limit or monthly quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/history/{venue}/{asset}/csv":{"get":{"operationId":"api_history_venue_asset_csv","summary":"RFC 4180 CSV export of the JSONL window","description":"Same record selection as /api/history/[venue]/[asset], but emits text/csv with a Content-Disposition header so browsers offer a download. Columns: t, p, v, s.\n\nResponse shape: `text/csv with header row `t,p,v,s` + CRLF-terminated records (RFC 4180)`","tags":["history"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"venue","in":"path","required":true,"description":"Path-param venue (URL slug).","schema":{"type":"string"}},{"name":"asset","in":"path","required":true,"description":"Path-param asset (URL slug).","schema":{"type":"string"}},{"name":"fromMs","in":"query","required":false,"description":"Inclusive lower bound (epoch ms).","schema":{"type":"integer"}},{"name":"toMs","in":"query","required":false,"description":"Inclusive upper bound (epoch ms).","schema":{"type":"integer"}},{"name":"limit","in":"query","required":false,"description":"Max records, default 1000, cap 50000.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=30, stale-while-revalidate=60`","schema":{"type":"string"}},"X-Api-Tier":{"description":"Caller's resolved tier (free|pro|enterprise).","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Per-key requests/minute for the tier.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current minute.","schema":{"type":"integer"}},"X-Quota-Limit":{"description":"Monthly request quota (omitted on unlimited tiers).","schema":{"type":"integer"}},"X-Quota-Remaining":{"description":"Monthly quota remaining.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing / invalid / revoked / expired API key (when gating is enabled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Payment required — feature 'history-export' needs tier 'enterprise'+","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Key not entitled to the requested venue (per-venue entitlement)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-key rate limit or monthly quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/history/{venue}/{asset}/aggregate":{"get":{"operationId":"api_history_venue_asset_aggregate","summary":"OHLC + count + volume + VWAP bucketed downsampling","description":"Group raw records into fixed-width time buckets aligned to the UNIX epoch. Buckets tile cleanly across adjacent fromMs/toMs windows.\n\nResponse shape: `{ ok, venue, asset, bucketMs, op, count, buckets: [{t, open?, high?, low?, close?, count?, volume?, vwap?}] }`","tags":["history"],"parameters":[{"name":"venue","in":"path","required":true,"description":"Path-param venue (URL slug).","schema":{"type":"string"}},{"name":"asset","in":"path","required":true,"description":"Path-param asset (URL slug).","schema":{"type":"string"}},{"name":"bucketMs","in":"query","required":true,"description":"Bucket width in milliseconds; must be > 0.","schema":{"type":"integer"}},{"name":"fromMs","in":"query","required":false,"description":"Inclusive lower bound.","schema":{"type":"integer"}},{"name":"toMs","in":"query","required":false,"description":"Inclusive upper bound.","schema":{"type":"integer"}},{"name":"op","in":"query","required":false,"description":"One of \"all\" (default), \"ohlc\", \"vwap\".","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=30, stale-while-revalidate=60`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/history/assets":{"get":{"operationId":"api_history_assets","summary":"Sovereign store-wide (venue, asset) index","description":"List every asset the store knows about with bytes-on-disk and firstMs/lastMs observation timestamps (both EXACT, O(1) head/tail reads). Each asset's `count` is a byte-derived ESTIMATE (countIsEstimate=true; perAssetCountIsEstimate=true at top level) so the index is constant-time regardless of store size — use recordCount for an exact value. Optional ?venue= filter.\n\nResponse shape: `{ ok, count, totalBytes, perAssetCountIsEstimate, assets: [{venue, asset, bytes, firstMs, lastMs, count, countIsEstimate}] }`","tags":["history"],"parameters":[{"name":"venue","in":"query","required":false,"description":"Restrict to a single venue id.","schema":{"type":"string","enum":["polymarket","kalshi","hyperliquid","limitless"]}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=60, stale-while-revalidate=120`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/asset/{asset}/risk":{"get":{"operationId":"api_asset_asset_risk","summary":"Realized vol + max drawdown + sharpe from the store","description":"Log-return risk metrics computed inline from the sovereign history store. Falls back to an empty result when the store has fewer than `minBars` observations.\n\nResponse shape: `{ ok, asset, kind, venue, storeKey, source: 'store' | 'fallback', periodsPerYear, minBars, metrics: {bars, returns, meanLogReturn, realizedVol, annualizedVol, annualizedReturn, sharpe, sharpeAdjAC, ulcerIndex, painIndex, modifiedVar95, martinRatio, cdar95, gainToPain, sterlingRatio, omega, rollSpreadBps, maxDrawdown, peakPrice, troughPrice, drawdownBars} }`","tags":["asset analytics"],"parameters":[{"name":"asset","in":"path","required":true,"description":"Path-param asset (URL slug).","schema":{"type":"string"}},{"name":"minBars","in":"query","required":false,"description":"Drop threshold for store→fallback switch. Default 30, cap 5000.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=30, stale-while-revalidate=60`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/asset/{asset}/slippage":{"get":{"operationId":"api_asset_asset_slippage","summary":"Order-book walking simulator — avg fill, slippage in bp, tier breakdown","description":"Walks the live order book (HL L2 or PM CLOB) and reports the volume-weighted average execution price, signed slippage versus mid, per-bp tier breakdown, and worst-fill level for the requested size.\n\nResponse shape: `{ ok, asset, kind, venue, generatedAt, mode: 'live', params, result: {side, midPrice, topPrice, baseFilled, quoteFilled, remaining, fullyFilled, avgPrice, slippageBps, worstFillPrice, worstFillBps, levelsConsumed, tiers: [{bpsFrom, bpsTo, baseFilled, quoteFilled}]} }`","tags":["asset analytics"],"parameters":[{"name":"asset","in":"path","required":true,"description":"Path-param asset (URL slug).","schema":{"type":"string"}},{"name":"size","in":"query","required":true,"description":"Quote (USD) or base (contracts), per sizeUnit. > 0, ≤ 1B.","schema":{"type":"number"}},{"name":"side","in":"query","required":true,"description":"\"buy\" or \"sell\".","schema":{"type":"string"}},{"name":"sizeUnit","in":"query","required":false,"description":"\"quote\" (default, USD notional) or \"base\" (contracts).","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=10, stale-while-revalidate=20`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/asset/{asset}/cascades":{"get":{"operationId":"api_asset_asset_cascades","summary":"Liquidation-cascade detector (price-only proxy)","description":"Walks records and flags clusters where price falls (or rises with direction=up) by >= minDrawdownPct off a trailing windowMs peak/trough. Useful for spotting candidate forced-unwind events. CAVEAT: no exchange liquidation feed wired — purely a price-based proxy.\n\nResponse shape: `{ ok, asset, kind, venue, storeKey, params, summary: {clustersFound, deepestDrawdownFrac, totalCascadeBars, direction}, cascades: [{tStart, tEnd, bars, peakPrice, troughPrice, drawdownFrac}] }`","tags":["asset analytics"],"parameters":[{"name":"asset","in":"path","required":true,"description":"Path-param asset (URL slug).","schema":{"type":"string"}},{"name":"windowMs","in":"query","required":false,"description":"Trailing window ms. Default 60000, cap 86400000.","schema":{"type":"integer"}},{"name":"minDrawdownPct","in":"query","required":false,"description":"Threshold drawdown fraction, in (0, 1). Default 0.01.","schema":{"type":"number"}},{"name":"direction","in":"query","required":false,"description":"\"down\" (default) or \"up\".","schema":{"type":"string"}},{"name":"fromMs","in":"query","required":false,"description":"Inclusive lower bound (epoch ms).","schema":{"type":"integer"}},{"name":"toMs","in":"query","required":false,"description":"Inclusive upper bound (epoch ms).","schema":{"type":"integer"}},{"name":"limit","in":"query","required":false,"description":"Max records read, default 5000, cap 50000.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=15, stale-while-revalidate=30`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/asset/{asset}/stream":{"get":{"operationId":"api_asset_asset_stream","summary":"SSE live data push for an asset (replaces client polling)","description":"Server-Sent Events stream emitting one event per asset-pulse update — the live price/snapshot fanned out from a single upstream fetch to all subscribers. Premium realtime tier; charged per stream-connect when prepaid billing is enabled.\n\nResponse shape: `text/event-stream: one JSON data: frame per pulse; ': connected' initial comment; ': heartbeat' every 25s`","tags":["asset analytics"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"asset","in":"path","required":true,"description":"Path-param asset (URL slug).","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=0, stale-while-revalidate=0`","schema":{"type":"string"}},"X-Api-Tier":{"description":"Caller's resolved tier (free|pro|enterprise).","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Per-key requests/minute for the tier.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current minute.","schema":{"type":"integer"}},"X-Quota-Limit":{"description":"Monthly request quota (omitted on unlimited tiers).","schema":{"type":"integer"}},"X-Quota-Remaining":{"description":"Monthly quota remaining.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing / invalid / revoked / expired API key (when gating is enabled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Payment required — feature 'realtime' needs tier 'pro'+","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Key not entitled to the requested venue (per-venue entitlement)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-key rate limit or monthly quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/asset/{asset}/stream-flow":{"get":{"operationId":"api_asset_asset_stream_flow","summary":"SSE live order-flow delta (tick-rule classified)","description":"Server-Sent Events stream emitting one event per asset-pulse update. Each event carries the new price, prev price, side (buy/sell/unclassified), signed weight, and per-connection cumulative. Per-client state — comparing cumulatives across clients is meaningless.\n\nResponse shape: `text/event-stream: { t, p, prevP, v, weight, side, signed, cumulative } per event; ': connected' initial comment; ': heartbeat' every 25s`","tags":["asset analytics"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"asset","in":"path","required":true,"description":"Path-param asset (URL slug).","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=0, stale-while-revalidate=0`","schema":{"type":"string"}},"X-Api-Tier":{"description":"Caller's resolved tier (free|pro|enterprise).","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Per-key requests/minute for the tier.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current minute.","schema":{"type":"integer"}},"X-Quota-Limit":{"description":"Monthly request quota (omitted on unlimited tiers).","schema":{"type":"integer"}},"X-Quota-Remaining":{"description":"Monthly quota remaining.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing / invalid / revoked / expired API key (when gating is enabled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Payment required — feature 'realtime' needs tier 'pro'+","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Key not entitled to the requested venue (per-venue entitlement)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-key rate limit or monthly quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/asset/{asset}/flow":{"get":{"operationId":"api_asset_asset_flow","summary":"Order-flow imbalance via the tick rule (Lee-Ready 1991)","description":"Classifies each recorded tick as buy- or sell-initiated using the price tick vs the previous record; zero ticks inherit the prior direction. Reports buy/sell volume, imbalance ratio, a VPIN-lite order-flow toxicity score in [0,1] (mean absolute imbalance across non-overlapping rollingWindow buckets — persistent one-sided pressure does not wash out the way it does in the net imbalance), full per-record series with cumulative delta, and a trailing rollingDelta array for charting.\n\nResponse shape: `{ ok, asset, kind, venue, storeKey, params, summary: {bars, buyBars, sellBars, unclassifiedBars, buyVolume, sellVolume, netDelta, imbalance, vpin, hasRealVolume, impactLambda:number|null, impactLambdaBps:number|null, impactFromRealVolume}, series: [{t, side, weight, signed, cumulative}], rollingDelta, rollingWindow }`","tags":["asset analytics"],"parameters":[{"name":"asset","in":"path","required":true,"description":"Path-param asset (URL slug).","schema":{"type":"string"}},{"name":"fromMs","in":"query","required":false,"description":"Inclusive lower bound (epoch ms).","schema":{"type":"integer"}},{"name":"toMs","in":"query","required":false,"description":"Inclusive upper bound (epoch ms).","schema":{"type":"integer"}},{"name":"limit","in":"query","required":false,"description":"Max records read, default 5000, cap 50000.","schema":{"type":"integer"}},{"name":"rollingWindow","in":"query","required":false,"description":"Window size for rolling delta, default 60, max 5000.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=15, stale-while-revalidate=30`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/asset/{asset}/volprofile":{"get":{"operationId":"api_asset_asset_volprofile","summary":"Volume profile + POC from the sovereign store","description":"Builds a price-volume histogram from the recorded observations. For each price bin: bar count + cumulative volume (or count-weighted when records carry no volume). POC (point of control) is the highest-volume bin and gets isPoc:true.\n\nResponse shape: `{ ok, asset, kind, venue, storeKey, params, recordsUsed, hasRealVolume, pocIdx, buckets: [{priceLow, priceHigh, count, volume, isPoc}] }`","tags":["asset analytics"],"parameters":[{"name":"asset","in":"path","required":true,"description":"Path-param asset (URL slug).","schema":{"type":"string"}},{"name":"priceStep","in":"query","required":true,"description":"Bin width in price units; must be > 0.","schema":{"type":"number"}},{"name":"fromMs","in":"query","required":false,"description":"Inclusive lower bound (epoch ms).","schema":{"type":"integer"}},{"name":"toMs","in":"query","required":false,"description":"Inclusive upper bound (epoch ms).","schema":{"type":"integer"}},{"name":"limit","in":"query","required":false,"description":"Max records read, default 5000, cap 50000.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=30, stale-while-revalidate=60`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/asset/{asset}/carry":{"get":{"operationId":"api_asset_asset_carry","summary":"HL funding-rate carry — hourly + annualized + side breakdown","description":"Per-hour funding rate, annualised rate, and per-side carry view (long + short) with days-to-1% and days-to-10% horizons. HL perps only; other kinds 404 with an explanatory message.\n\nResponse shape: `{ ok, asset, kind: 'hl-perp', venue, coin, markPx, generatedAt, snapshotAgeMs, carry: {fundingHourly, fundingAnnualizedFrac, longSide, shortSide, flat} }`","tags":["asset analytics"],"parameters":[{"name":"asset","in":"path","required":true,"description":"Path-param asset (URL slug).","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=60, stale-while-revalidate=120`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/arb/sim":{"get":{"operationId":"api_arb_sim","summary":"Cross-venue arb portfolio simulator with equal-split allocation","description":"Allocates capital across the live cross-venue arb opportunities and reports total expected edge, capital efficiency, per-venue exposure, and a ranked top-N selection. Paper sim — no fees, slippage, or settlement risk modelled.\n\nResponse shape: `{ ok, generatedAt, snapshotAges, params, summary: {opportunitiesConsidered, opportunitiesSelected, capitalDeployed, expectedProfit, expectedEdgePct, capitalEfficiency, avgSpread, minSpread, maxSpread, venueExposure}, selected: [...] }`","tags":["arbitrage"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"capital","in":"query","required":true,"description":"Total USD to deploy. (0, 1B].","schema":{"type":"number"}},{"name":"maxSpread","in":"query","required":false,"description":"Spread filter ceiling, fraction. (0, 1]. Default 0.50.","schema":{"type":"number"}},{"name":"minLegs","in":"query","required":false,"description":"Minimum venues per opportunity. Default 2.","schema":{"type":"integer"}},{"name":"topN","in":"query","required":false,"description":"Cap on selected opportunities. Default 10, max 100.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=15, stale-while-revalidate=30`","schema":{"type":"string"}},"X-Api-Tier":{"description":"Caller's resolved tier (free|pro|enterprise).","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Per-key requests/minute for the tier.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current minute.","schema":{"type":"integer"}},"X-Quota-Limit":{"description":"Monthly request quota (omitted on unlimited tiers).","schema":{"type":"integer"}},"X-Quota-Remaining":{"description":"Monthly quota remaining.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing / invalid / revoked / expired API key (when gating is enabled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Payment required — feature 'arb' needs tier 'pro'+","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Key not entitled to the requested venue (per-venue entitlement)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-key rate limit or monthly quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/stats":{"get":{"operationId":"api_stats","summary":"Tier-1 statistical metrics for the curated cross-venue asset set","description":"Per-asset return/risk statistics over a 30d window. returnMode is LOG for HL perps (price levels, $) and DIFF for bounded probability series (PM/Kalshi/HL-pred) — Sharpe/Sortino/Calmar are SUPPRESSED on binary venues (√periodsPerYear annualisation is dimensionless; a `horizon` block with daysToResolve/perDayMove/terminalVariance is emitted instead). periodsPerYear is fixed at 8760. Results are cached ~20s in-process; `cached` + `resultsAgeMs` disclose staleness. `coverage`/`perVenue` describe which venues this engine actually has (a sovereign engine narrows). Each result carries `type` (the venue/kind discriminant, e.g. hl-perp) — there is no separate `venue` field and `stats` is FLAT (no nested `returns`). Shares the returnMode convention with the m2m bundle, but NOTE the window/cadence differ: this endpoint uses 30d of 1h candles (periodsPerYear 8760), while the bundle's risk block uses its own 24h history window annualised by a CADENCE-DERIVED periodsPerYear — so realizedVol is comparable in magnitude but not identical; don't cross-map the two as the same number.\n\nResponse shape: `{ now, elapsedMs, cached, resultsAgeMs, requested, returned, venue, coverage, perVenue, snapshot, results: [{asset, type, name, markPx, changePct, stats: {n, returnMode, framing, annualisedVol, sharpe|null, sharpeSuppressedReason, sortino, var95, es95, maxDrawdown, hurst, autocorr1, halfLifeMR, verdict, horizon?}}] }`","tags":["asset analytics"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"venue","in":"query","required":false,"description":"Restrict to one of hl/hl-pred/pm/kalshi.","schema":{"type":"string"}},{"name":"max","in":"query","required":false,"description":"Cap the curated asset count.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=15, stale-while-revalidate=30`","schema":{"type":"string"}},"X-Api-Tier":{"description":"Caller's resolved tier (free|pro|enterprise).","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Per-key requests/minute for the tier.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current minute.","schema":{"type":"integer"}},"X-Quota-Limit":{"description":"Monthly request quota (omitted on unlimited tiers).","schema":{"type":"integer"}},"X-Quota-Remaining":{"description":"Monthly quota remaining.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing / invalid / revoked / expired API key (when gating is enabled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Payment required — feature 'stats' needs tier 'pro'+","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Key not entitled to the requested venue (per-venue entitlement)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-key rate limit or monthly quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/movers":{"get":{"operationId":"api_movers","summary":"Top 24h gainers + losers across HL perps, PM, Kalshi","description":"Ranks every live asset by absolute 24h change and splits into gainers / losers. HL perps derive from the feed's native percent change; PM and Kalshi use a store-delta computed from the sovereign history store over the trailing 24 hours. NOTE: each row's `changeFrac` is a FRACTION (0.366 = +36.6%) — deliberately distinct from /api/feed's `changePct` which is a PERCENT (they differ by 100x).\n\nResponse shape: `{ ok, generatedAt, params, gainers: [{asset, name, kind, venue, changeFrac, currentPrice, source}], losers: [{asset, name, kind, venue, changeFrac, currentPrice, source}], totals: {considered, eligible, avgAbsChange} }`","tags":["movers"],"parameters":[{"name":"topN","in":"query","required":false,"description":"Cap per direction. Default 25, max 100.","schema":{"type":"integer"}},{"name":"minPriceChangePct","in":"query","required":false,"description":"Filter floor as a fraction (0.05 = 5%). Default 0.","schema":{"type":"number"}},{"name":"kind","in":"query","required":false,"description":"Comma-separated subset of hl-perp, hl-pred, pm, kalshi.","schema":{"type":"string","description":"comma-separated values"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=60, stale-while-revalidate=120`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/correlations":{"get":{"operationId":"api_correlations","summary":"Pairwise Pearson + Spearman matrix over log returns","description":"Pulls log returns from the sovereign store for N (2..20) assets over a trailing window and computes the symmetric N×N correlation matrix. Both methods by default; assets with insufficient store data are dropped and surfaced in `dropped`.\n\nResponse shape: `{ ok, generatedAt, params, surviving, dropped, barsPerAsset, alignedReturnsLen, pearson: {labels, matrix} | null, spearman: {labels, matrix} | null }`","tags":["correlations"],"parameters":[{"name":"assets","in":"query","required":true,"description":"Comma-separated asset ids. 2..20 entries, no duplicates.","schema":{"type":"string","description":"comma-separated values"}},{"name":"window","in":"query","required":false,"description":"Trailing window in hours. Default 24, max 720 (30d).","schema":{"type":"number"}},{"name":"method","in":"query","required":false,"description":"\"both\" (default), \"pearson\", or \"spearman\".","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=30, stale-while-revalidate=60`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/health/endpoints":{"get":{"operationId":"api_health_endpoints","summary":"In-process probe aggregator for the 9 night-build endpoints","description":"Invokes each route handler in-process (no HTTP loopback) and reports status code, latency, and last-checked ISO timestamp per probe. Cached for 30s globally.\n\nResponse shape: `{ ok, generatedAt, summary: {healthy, degraded, errored, total, avgLatencyMs, maxLatencyMs, verdict}, probes: [{endpoint, sampleUrl, status, statusCode, sampleLatencyMs, lastChecked, error}] }`","tags":["health"],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=30, stale-while-revalidate=60`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api":{"get":{"operationId":"api","summary":"Endpoint index — minimal JSON list of every public route","description":"Lightweight catalog endpoint. Returns `{ ok, count, catalog, endpoints }` where each endpoint carries path + method + summary + group + cacheSeconds. Useful for tooling that just needs the URL list without the full OpenAPI spec.\n\nResponse shape: `{ ok, count, catalog: {docs, spec}, endpoints: [{path, method, summary, group, cacheSeconds}] }`","tags":["health"],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=3600, stale-while-revalidate=7200`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/openapi.json":{"get":{"operationId":"api_openapi_json","summary":"OpenAPI 3.1 spec for every public read endpoint","description":"Machine-readable schema describing every endpoint in this catalog. Generated from lib/api-catalog at request time so adding a new route auto-propagates to OpenAPI consumers. Static-cacheable; the spec only changes when the catalog does.\n\nResponse shape: `OpenAPI 3.1.0 document: { openapi, info, servers, tags, paths, components }`","tags":["health"],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=3600, stale-while-revalidate=7200`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/alerts/stale":{"get":{"operationId":"api_alerts_stale","summary":"JSON mirror of /alerts staleness dashboard","description":"Returns the same data as /alerts in JSON so external monitors can poll without scraping HTML. Default threshold 5 minutes; configurable via ?thresholdMin=.\n\nResponse shape: `{ ok, generatedAt, params: {thresholdMin, thresholdMs, limit, sortBy, venue}, summary: {totalAssets, staleCount, neverRecordedCount, byVenue}, truncated: {stale, neverRecorded}, stale: [...], neverRecorded: [...] }`","tags":["health"],"parameters":[{"name":"thresholdMin","in":"query","required":false,"description":"Staleness threshold in minutes. Default 5, max 1440.","schema":{"type":"number"}},{"name":"limit","in":"query","required":false,"description":"Cap on stale[] and neverRecorded[] arrays. Default 1000, max 10000. Summary tally reflects the full count regardless.","schema":{"type":"integer"}},{"name":"sortBy","in":"query","required":false,"description":"Sort key for the visible window: staleness | asset | venue. Defaults: staleness for stale[], asset for neverRecorded[]. Applied AFTER the limit slice.","schema":{"type":"string"}},{"name":"venue","in":"query","required":false,"description":"Restrict to a single venue id (e.g. hyperliquid, polymarket, kalshi). 400 on unknown. Applied BEFORE detection — summary tally and limit cap reflect the filtered subset.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=60, stale-while-revalidate=120`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/alerts/stale.csv":{"get":{"operationId":"api_alerts_stale_csv","summary":"RFC 4180 CSV mirror of /api/alerts/stale","description":"Same query surface and validation envelope as the JSON endpoint, but emits text/csv with a download attachment. Columns: kind (stale|never_recorded), venue, asset, lastIso, staleForMs, count, bytes. Stale rows precede never-recorded rows. Summary tally is dropped; consumers wanting that should call /api/alerts/stale.\n\nResponse shape: `text/csv with header row: kind,venue,asset,lastIso,staleForMs,count,bytes`","tags":["health"],"parameters":[{"name":"thresholdMin","in":"query","required":false,"description":"Staleness threshold in minutes. Default 5, max 1440.","schema":{"type":"number"}},{"name":"limit","in":"query","required":false,"description":"Cap per-block (stale and neverRecorded each capped). Default 1000, max 10000.","schema":{"type":"integer"}},{"name":"sortBy","in":"query","required":false,"description":"Sort key: staleness | asset | venue. Applied AFTER the per-block limit slice.","schema":{"type":"string"}},{"name":"venue","in":"query","required":false,"description":"Restrict to a single venue id. 400 on unknown. Filter applied BEFORE detection; the filename also embeds the venue.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=60, stale-while-revalidate=120`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/alerts/stale/history":{"get":{"operationId":"api_alerts_stale_history","summary":"Rolling-snapshot of the staleness verdict over the past N hours","description":"Single-snapshot foundation today; future expansion will read a sovereign JSONL window so a dashboard can chart verdict drift over the last 6/12/24h. ?hours= is validated up-front so consumers can wire against the response shape now.\n\nResponse shape: `{ ok, generatedAt, params: {hours, thresholdMin, thresholdMs}, snapshots: [{ts, verdict, summary}], note }`","tags":["health"],"parameters":[{"name":"hours","in":"query","required":false,"description":"Window in hours. Default 6, max 48. Validated today; reserved for the future historical snapshotter.","schema":{"type":"number"}},{"name":"thresholdMin","in":"query","required":false,"description":"Staleness threshold in minutes. Default 5, max 1440.","schema":{"type":"number"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=60, stale-while-revalidate=120`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/alerts/stale/snapshot":{"post":{"operationId":"api_alerts_stale_snapshot","summary":"Cron trigger that persists one staleness snapshot","description":"Computes the current verdict + summary and appends one record to the sovereign stale-snapshots JSONL store. Rate-limited to one write per 60s (returns 200 with skipped:true if called sooner). When the HYPO_SNAPSHOT_TOKEN env is set, requests must carry a matching X-Snapshot-Token header (401 otherwise); when unset, the endpoint is open (intended for fully-local dev).\n\nResponse shape: `{ ok, skipped, written?: {ts, verdict, summary}, reason?, lastSnapshotAgeMs? }`","tags":["health"],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=0, stale-while-revalidate=0`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/m2m/index":{"get":{"operationId":"api_m2m_index","summary":"M2M discovery — every asset a bot can query, grouped by venue","description":"Returns the full asset universe a bot can pass to /api/m2m/[asset]/bundle or /api/m2m/batch. Each entry carries the asset id, venue, kind, lastPrice and isStale hint so a bot can pre-filter (e.g. 'only fresh hyperliquid perps') before committing to per-asset round-trips. Cached 60s — the universe changes slowly.\n\nResponse shape: `{ ok, generatedAt, apiVersion, scope, summary:{total, byVenue, freshCount, staleCount}, generatedAtMs, filters:{kind:string[], fresh:boolean, minBasisBps:number|null, limit:number|null, offset}, returned, truncated, entries:[{asset, venue, kind, lastPrice, name:string|null, isStale, fairPrice:number|null, basisBps:number|null}] · params: ?kind=perp|binary|pred (CSV) ?fresh=true ?minBasisBps=N (cross-venue mispricing screen: perp mark-vs-oracle basis + binary cross-venue consensus edge) ?limit=N ?offset=N, bundleEndpoint, batchEndpoint }`","tags":["asset analytics"],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=60, stale-while-revalidate=120`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/m2m/batch":{"post":{"operationId":"api_m2m_batch","summary":"M2M batch — many bundles in one round-trip (portfolio scan)","description":"POST {assets:[id, id, ...]} (max 50 per request) and get back an array of bundles. Server-side composition lets the snapshot fan-out and assetIndex cache reuse across all assets in the batch, saving disk reads vs N independent /bundle calls. Per-asset errors don't fail the batch: each item is either a successful bundle or {ok:false, asset, error}. CORS preflight (OPTIONS) supported for browser-based bots.\n\nResponse shape: `{ ok, generatedAt, apiVersion, requestedCount, bundles:[ ...bundle | {ok:false, asset, error} ] } — each successful bundle has the same shape as /api/m2m/[asset]/bundle (freshness.historyLastEverMs included; series?:{...} when body.series=true)`","tags":["asset analytics"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["assets"],"properties":{"assets":{"type":"array","items":{"type":"string"},"description":"Asset ids (max 50). Each follows the hl-/pm-/kalshi-/hl-pred- prefix convention.","minItems":1,"maxItems":50},"series":{"type":"boolean","description":"Opt-in: include the downsampled price series on every bundle in the batch. Default false."}}}}}},"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=0, stale-while-revalidate=0`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/m2m/{asset}/stream-signal":{"get":{"operationId":"api_m2m_asset_stream_signal","summary":"M2M SSE — push signal updates as the asset pulse refreshes","description":"Server-Sent Events stream of freshly-composed M2M bundles. Bots subscribe once, get signal updates pushed every time the asset's pulse refreshes (cadence floor 1500ms, idle bump every 15s during quiet periods). Same bundle shape as the REST /bundle endpoint — bots can pipe both through the same parser. Per-connection state tracked server-side. Heartbeat comment every 25s for proxy keepalive.\n\nResponse shape: `text/event-stream — each data: line is a JSON M2MBundle with an added `cause` field (\"init\"|\"pulse\"|\"idle\")`","tags":["asset analytics"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"asset","in":"path","required":true,"description":"Path-param asset (URL slug).","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=0, stale-while-revalidate=0`","schema":{"type":"string"}},"X-Api-Tier":{"description":"Caller's resolved tier (free|pro|enterprise).","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Per-key requests/minute for the tier.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current minute.","schema":{"type":"integer"}},"X-Quota-Limit":{"description":"Monthly request quota (omitted on unlimited tiers).","schema":{"type":"integer"}},"X-Quota-Remaining":{"description":"Monthly quota remaining.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing / invalid / revoked / expired API key (when gating is enabled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Payment required — feature 'realtime' needs tier 'pro'+","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Key not entitled to the requested venue (per-venue entitlement)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-key rate limit or monthly quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/m2m/{asset}/bundle":{"get":{"operationId":"api_m2m_asset_bundle","summary":"M2M unified bundle — one round-trip, every signal a bot needs","description":"Machine-to-machine endpoint for AI agents and trading bots. Composes price, freshness, risk metrics, flow lean, carry hint, synthesized direction + confidence, and a venue-native URL with suggested side. HYPO is data-only — bots execute on the venue's native API via tradeHint.venueNativeUrl. Versioned: response always carries apiVersion. Sub-15s polling cached at the CDN; faster cadence consumers should use the per-asset SSE streams.\n\nResponse shape: `{ ok, generatedAt, generatedAtMs, apiVersion, schema:{version, optionalBlocks[]}, asset, venue, storeKey, freshness:{feedAgeMs, historyLastMs, historyLastEverMs, isStale}, name:string|null, price:{last, bid, ask, spreadBps, change24hPct, unit:'usd'|'probability', change24hSource:'snapshot'|'store-delta'|null, fairValue:number|null, basisBps:number|null}, fairValue:{value:number|null, unit:'usd'|'probability', basisBps:number|null, source:'hl-oracle'|'consensus'|'none', confidence:number|null, dispersionBps:number|null, nVenues:number|null, isStale}, liquidity:{liquidityUsd, openInterestUsd, volume24hUsd, volumeTotalUsd, source:'snapshot'|null}, horizon:{endDate, daysToResolve, resolvedFrom:'closeTime'|'endDate'|null, terminalVariance}|null, risk:{realizedVolAnnualizedPct, maxDrawdownPct, sharpe, ulcerIndexPct, painIndexPct, modifiedVar95Pct, martinRatio, cdar95Pct, gainToPain, sterlingRatio, omega, rollSpreadBps, barsUsed, source:'store'|'fallback'|'insufficient', returnBasis:'log'|'diff'|null, windowMs, periodsPerYear}, flow:{imbalance, lean:'BID'|'ASK'|null}, carry:{fundingAprPct, side:'longs_pay'|'shorts_pay'|'flat'}, signals:{direction:'LONG'|'SHORT'|'NEUTRAL', confidence, reasons[]}, tradeHint:{venueNativeUrl, suggestedSide:'BUY'|'SELL'|null, notes[]}, series?:{prices[], points, windowMs} (opt-in via ?series=true) }`","tags":["asset analytics"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"asset","in":"path","required":true,"description":"Path-param asset (URL slug).","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=15, stale-while-revalidate=30`","schema":{"type":"string"}},"X-Api-Tier":{"description":"Caller's resolved tier (free|pro|enterprise).","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Per-key requests/minute for the tier.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current minute.","schema":{"type":"integer"}},"X-Quota-Limit":{"description":"Monthly request quota (omitted on unlimited tiers).","schema":{"type":"integer"}},"X-Quota-Remaining":{"description":"Monthly quota remaining.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing / invalid / revoked / expired API key (when gating is enabled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Payment required — feature 'm2m-bundle' needs tier 'pro'+","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Key not entitled to the requested venue (per-venue entitlement)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-key rate limit or monthly quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/feed/health":{"get":{"operationId":"api_feed_health","summary":"Feed-cache poller diagnostics (per-upstream counters + last error)","description":"Surfaces the state of the in-process feed-cache poller: per-upstream ok/fail counters, last error message, snapshot age, pollersRunning boolean. Verdict OK when all three upstreams have snapshots, WARN when partial, DOWN when no poller is running OR every poll has failed since start (HTTP 503 on DOWN). Also a per-venue `venues` freshness map and a `degraded` map (+`anyDegraded`): a degraded venue is one actively erroring (failCount>0, lastError set) while its last snapshot is still fresh — a LEADING indicator (the pre-120s-stale-cliff window) that does NOT change the verdict, so transient 429s don't flap OK.\n\nResponse shape: `{ ok, generatedAt, verdict, venues:{hyperliquid, polymarket, kalshi, limitless}, degraded:{hyperliquid, polymarket, kalshi, limitless}, anyDegraded, health:{ startedAt, uptimeMs, pollersRunning, hyperliquid:{lastFetchedAt, ageMs, okCount, failCount, lastError, pollIntervalMs, hasSnapshot}, polymarket:{...}, kalshi:{...}, limitless:{...} } }`","tags":["health"],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=5, stale-while-revalidate=10`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/health/anomalies":{"get":{"operationId":"api_health_anomalies","summary":"Live data-integrity invariant checker (alertable verdict)","description":"Runs detectAnomalies() over the current in-memory snapshot and surfaces invariant violations for an external alerting cron: stale/missing/empty venues, probabilities outside [0,1], non-positive HL prices, the Kalshi empty-no-book noProb=0.50 artifact, gross overround, and money-path health (billing write failures, low disk, held over-cap deposits). verdict is ok | degraded (a warning present) | critical. Sovereign-aware: an engine is only faulted for the venue(s) it serves (apex=all three). PAGE ON `alertable === true`, NOT `verdict !== 'ok'` — `verdict` flaps to degraded/critical during the ~90s post-restart warm-up grace while the first poll lands, but `alertable` is false during that grace (and true only on a real problem).\n\nResponse shape: `{ ok, verdict, warming, alertable, scope, counts:{ total, critical, warning }, anomalies:[{ severity, code, venue, message, count? }], generatedAt }`","tags":["health"],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=5, stale-while-revalidate=10`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/health/snapshotter":{"get":{"operationId":"api_health_snapshotter","summary":"Liveness check on the sovereign stale-snapshotter file","description":"Returns the same stats /status renders plus a coarse verdict (OK / WARN / DOWN) classified by the age of the most recent snapshot. Returns HTTP 503 on DOWN so naive monitors (`curl --fail`, uptime-kuma's HTTP status mode) get the right signal without parsing the body. Thresholds: OK <= 10 min, WARN <= 30 min, DOWN beyond that or file absent.\n\nResponse shape: `{ ok, generatedAt, verdict, reason, thresholds: {warnAfterMs, downAfterMs}, stats: {exists, sizeBytes, lastTs, lastTsMs, ageMs, count24h} }`","tags":["health"],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=30, stale-while-revalidate=60`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/health/upstream":{"get":{"operationId":"api_health_upstream","summary":"Network probe over HL, PM, Kalshi data sources","description":"Goes over the wire to each upstream with a 5s AbortController timeout per probe. Surfaces network errors, rate-limit codes, and round-trip latency. Independent from /api/health/endpoints.\n\nResponse shape: `{ ok, generatedAt, scope, summary: {healthy, degraded, errored, total, avgLatencyMs, maxLatencyMs, verdict}, probes: [{upstream, probedUrl, method, status, statusCode, latencyMs, lastChecked, error}] }`","tags":["health"],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=30, stale-while-revalidate=60`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/feed":{"get":{"operationId":"api_feed","summary":"Apex feed: the full HL + PM + Kalshi snapshot with ages and stale flags","description":"The same FeedSnapshot every server-side page reads via readFeed(). Includes per-venue snapshots, ages in ms, stale booleans (gated by STALE_THRESHOLD_MS = 120s), and the last error per venue. Force-dynamic; consumers should respect the cache-control header.\n\nResponse shape: `{ ok, hyperliquid, polymarket, kalshi, limitless, ages:{hyperliquid, polymarket, kalshi, limitless}, stale:{hyperliquid, polymarket, kalshi, limitless}, errors:{hyperliquid, polymarket, kalshi, limitless}, bodyAgeMs }  // bodyAgeMs: age of this (≤1s micro-cached) body. Kalshi markets also carry volume24hUsd (price-weighted $) + volume24hContracts (raw count).`","tags":["asset analytics"],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=4, stale-while-revalidate=8`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/asset/{asset}":{"get":{"operationId":"api_asset_asset","summary":"Per-asset dashboard payload — candles + book/history + snapshot","description":"The composite payload powering /feed/[asset]'s server-rendered dashboard. Includes raw quote/market/prediction object, candles or price-history series, and the live order book (HL perps only). The shape varies by asset kind (hl-perp / hl-pred / pm / kalshi) — discriminate on the `type` field.\n\nResponse shape: `{ ok, asset, type: 'hl-perp'|'hl-pred'|'pm'|'kalshi', now, snapshot:{fetchedAt, ageMs, stale}, ...kindSpecificFields }`","tags":["asset analytics"],"parameters":[{"name":"asset","in":"path","required":true,"description":"Path parameter — asset id (hl-BTC, pm-{slug}, kalshi-{ticker}, hl-pred-{slug-or-outcomeId}).","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=10, stale-while-revalidate=20`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/asset/{asset}/stats":{"get":{"operationId":"api_asset_asset_stats","summary":"Pre-computed analytical statistics — Sharpe, Sortino, Calmar, Hurst, VaR, ES","description":"Heavier statistical pack than /api/asset/[asset]/risk: includes Sharpe + Sortino + Calmar + AC-adjusted Sharpe (Lo 2002) + Omega + Hurst + ACF + ADF + KPSS + variance ratio + Jarque-Bera + Ljung-Box (+ a lag [1,2,3,5,10] LB profile) + Roll (1984) implied spread (bps) + Ulcer Index + multi-tail Expected Shortfall (es90/es95/es99 + tail-concentration) + Brier-on-self-history + drawdown summary. Computed from upstream candles (no store dependency). NOTE: for binary venues (PM, Kalshi, HL-pred) the response is HORIZON-FRAMED — annualised Sharpe/Sortino/Calmar/AC-Sharpe are suppressed (null, with returns.framing='horizon' + sharpeSuppressedReason) because √periodsPerYear scaling is dimensionless on a bounded [0,1] probability; a `horizon` block (daysToResolve, perDayMove, perHorizonMove, terminalVariance=p(1−p), resolvedFrom, available) keyed to time-to-resolution is returned instead. HL perps remain annualised (framing='annualised', horizon=null). Omega/Ulcer/Roll-spread/multi-tail-ES/LB-profile are emitted for BOTH framings (not √T-annualised).\n\nResponse shape: `{ ok, asset, type, now, snapshot, stats:{...analyticalMetrics, returns:{sharpe:number|null, sortino:number|null, calmar:number|null, sharpeAdjAC:number|null, omega:number|null, gainToPain:number|null, annualisedVol, returnMode:'log'|'diff', framing:'annualised'|'horizon', sharpeSuppressedReason:string|null}, risk:{var95, var99, es95, es90, es99, esTailConcentration:number|null, ulcerIndex, cdar95, drawdownAtRisk95, sterling:number|null, tailIndex:number|null, tailIndexWide:number|null, maxDrawdown, ...}, structure:{hurst, autocorr1, autocorr2, trendT, halfLifeMR:number|null, permEntropy, permEntropyTieFrac, rollSpreadBps}, tests:{ljungBox, ljungBoxProfile:[{lag, stat, p, reject}], adf, kpss, runs, varianceRatio, jarqueBera}, horizon:{daysToResolve, barsPerDay, perDayMove, perHorizonMove, terminalVariance, resolvedFrom:'closeTime'|'endDate'|null, available}|null}, benchmark?:{benchmarkAsset, up:number|null, down:number|null, captureSpread:number|null, alignedBars}|null (HL perps only) }`","tags":["asset analytics"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"asset","in":"path","required":true,"description":"Path parameter — asset id.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=15, stale-while-revalidate=30`","schema":{"type":"string"}},"X-Api-Tier":{"description":"Caller's resolved tier (free|pro|enterprise).","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Per-key requests/minute for the tier.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current minute.","schema":{"type":"integer"}},"X-Quota-Limit":{"description":"Monthly request quota (omitted on unlimited tiers).","schema":{"type":"integer"}},"X-Quota-Remaining":{"description":"Monthly quota remaining.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing / invalid / revoked / expired API key (when gating is enabled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Payment required — feature 'stats' needs tier 'pro'+","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Key not entitled to the requested venue (per-venue entitlement)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-key rate limit or monthly quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/book/{asset}":{"get":{"operationId":"api_book_asset","summary":"Live order book for an HL perp or PM market","description":"Bid + ask ladders from the venue's depth API. HL perps return L2 levels; PM returns the CLOB's yes-side book. Use /api/asset/[asset]/slippage for size-aware execution simulation.\n\nResponse shape: `{ ok, asset, kind, bids:[{price, size}], asks:[{price, size}], fetchedAt }`","tags":["asset analytics"],"parameters":[{"name":"asset","in":"path","required":true,"description":"Path parameter — asset id.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=4, stale-while-revalidate=8`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/m2m/{asset}/quote":{"get":{"operationId":"api_m2m_asset_quote","summary":"Ultra-low-latency price tick — current price + freshness only","description":"Micro-endpoint companion to /api/m2m/[asset]/bundle. Returns ONLY price (last/bid/ask/spreadBps/change24hPct) + freshness (feedAgeMs/isStale). No risk metrics, no signal synthesis, no history-store read by default. Warm latency ~1-2ms vs ~50ms for /bundle. Use for latency-sensitive bot scans across many assets. IMPORTANT for movement scanners: `change24hPct` is populated only for HL perps (snapshot-native); for Polymarket/Kalshi/Limitless it is ALWAYS null here (`change24hSource` is null) because the binary-venue 24h delta is a history-store computation done only in /bundle — to scan binary-venue movement use /movers or /bundle, not /quote. Optional ?activity=true adds historyLastEverMs (costs ~1-2ms of tail-read disk IO) for callers that want the 'last seen' signal alongside the live tick. Versioned: apiVersion='v1' (new optional fields are backwards-compatible).\n\nResponse shape: `{ ok, generatedAt, apiVersion, asset, venue, storeKey, price:{last, bid, ask, spreadBps, change24hPct (null on PM/Kalshi/Limitless — use /movers|/bundle), change24hSource:'snapshot'|null, fairValue:number|null, basisBps:number|null (perp HL-oracle, snapshot-local)}, freshness:{feedAgeMs, isStale, historyLastEverMs?} (opt-in ?activity=true), venueNativeUrl }`","tags":["asset analytics"],"parameters":[{"name":"asset","in":"path","required":true,"description":"Path parameter — asset id (hl-BTC, pm-{slug}, kalshi-{ticker}, hl-pred-{slug-or-outcomeId}).","schema":{"type":"string"}},{"name":"activity","in":"query","required":false,"description":"Query — set to 'true' to include freshness.historyLastEverMs (last write on disk). Costs ~1-2ms.","schema":{"type":"boolean"}},{"name":"venueNativeUrl","in":"query","required":false,"description":"Query — set to 'false' to drop venueNativeUrl (~50-100 bytes) for byte-saving scans. Default true.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=2, stale-while-revalidate=4`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/health":{"get":{"operationId":"api_health","summary":"Apex liveness — quick yes/no whether the process is up + SSE fan-out telemetry","description":"Minimal endpoint for liveness checks: returns ok + uptime + asset-pulse subscriber counts. Lighter than /api/feed/health (which goes deeper into per-upstream state). Use this for top-level uptime monitoring; /api/feed/health for the data-pipeline verdict.\n\nResponse shape: `{ ok, uptimeMs, pulse:{assetsPolling, totalSubscribers, perAsset} }`","tags":["health"],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=0, stale-while-revalidate=0`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/account/usage":{"get":{"operationId":"api_account_usage","summary":"Your API key's usage, quota, tier, and per-venue breakdown (this UTC month)","description":"Self-service usage report for the caller's own API key. Authenticate with the key being reported on (Authorization: Bearer <key> or X-API-Key). Returns the tier, its limits (monthly quota, per-key rate, features, venue entitlement), and the current-month usage including the per-venue split that underpins per-venue billing.\n\nResponse shape: `{ ok, tier, owner, venues, month, limits:{monthlyQuota, ratePerMin, maxConcurrentStreams, features, venues}, usage:{total, quotaRemaining, byVenue, byEndpoint, firstAt, lastAt}, key:{hash, createdAt, expiresAt} }`","tags":["account"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=0, stale-while-revalidate=0`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing / invalid / revoked / expired API key (when gating is enabled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/account/keys":{"post":{"operationId":"api_account_keys","summary":"Admin key management — issue (POST), list (GET), revoke (DELETE)","description":"Admin-only API-key lifecycle, gated by the HYPO_ADMIN_TOKEN bearer (fail-closed in production). POST {tier, owner, label?, venues?, expiresAt?} issues a key and returns the raw key ONCE (only its SHA-256 hash is stored). GET lists key metadata (hashes only). DELETE {keyHash} revokes.\n\nResponse shape: `POST → { ok, rawKey, record }; GET → { ok, keys:[record] }; DELETE → { ok, revoked }`","tags":["account"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"tier","in":"query","required":true,"description":"free | pro | enterprise (POST body).","schema":{"type":"string"}},{"name":"owner","in":"query","required":true,"description":"Account id / email (POST body).","schema":{"type":"string"}},{"name":"venues","in":"query","required":false,"description":"Per-key venue allow-list, or \"all\" (POST body).","schema":{"type":"string","description":"comma-separated values"}},{"name":"expiresAt","in":"query","required":false,"description":"Expiry epoch-ms, or null (POST body).","schema":{"type":"integer"}},{"name":"keyHash","in":"query","required":false,"description":"Key hash to revoke (DELETE body).","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=0, stale-while-revalidate=0`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing / invalid / revoked / expired API key (when gating is enabled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Admin endpoint unavailable — HYPO_ADMIN_TOKEN not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/account/balance":{"get":{"operationId":"api_account_balance","summary":"Your prepaid USDC/USDT balance, recent ledger, and the price list","description":"Self-service prepaid balance for the caller's API key (µUSD + USD). Includes the recent credit/debit ledger and the per-call price list. Top up by depositing USDC/USDT (see /api/account/wallets).\n\nResponse shape: `{ ok, owner, balance:{microUsd, usd}, prepaidEnabled, priceListMicroUsd, ledger:[{ts, type, microUsd, balanceAfter, ref}] }`","tags":["account"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=0, stale-while-revalidate=0`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing / invalid / revoked / expired API key (when gating is enabled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/account/wallets":{"post":{"operationId":"api_account_wallets","summary":"Register the wallet(s) you pay FROM + discover deposit addresses","description":"GET returns your registered pay-from wallets, the per-chain deposit addresses (USDC/USDT across the top-10 chains), and the price list. POST {address} registers an EVM/Tron/Solana wallet — a deposit from it credits your prepaid balance automatically.\n\nResponse shape: `GET → { ok, registeredWallets, depositTargets:[{chain, asset, depositAddress}], priceListMicroUsd }; POST → { ok, registeredWallets }`","tags":["account"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"address","in":"query","required":false,"description":"Pay-from wallet (0x… EVM / T… Tron / base58 Solana) — POST body.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=0, stale-while-revalidate=0`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing / invalid / revoked / expired API key (when gating is enabled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/account/deposit-address":{"get":{"operationId":"api_account_deposit_address","summary":"Your unique per-chain deposit address (CREATE2 forwarders)","description":"Returns a deposit address UNIQUE to your account on each configured chain, derived deterministically (CREATE2) so it is byte-identical to where funds are swept. Send USDC/USDT on the matching chain to your address; confirmed deposits credit your prepaid balance automatically — no pay-from registration needed. Dormant (503) until per-customer deposits are configured.\n\nResponse shape: `{ ok, owner, addresses:[{chain, address, assets}] }`","tags":["account"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"chain","in":"query","required":false,"description":"Limit to one chain (e.g. 'base'); default returns every configured chain.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=0, stale-while-revalidate=0`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing / invalid / revoked / expired API key (when gating is enabled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/account/credit":{"post":{"operationId":"api_account_credit","summary":"Admin manual credit (fiat/Stripe bridge, adjustments)","description":"Admin-only off-chain top-up gated by HYPO_ADMIN_TOKEN, idempotent by `ref`. On-chain USDC/USDT deposits are credited automatically by the watcher; this is the manual/fiat path.\n\nResponse shape: `{ ok, owner, applied, microUsd, balanceMicroUsd }`","tags":["account"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"owner","in":"query","required":true,"description":"Account owner to credit (POST body).","schema":{"type":"string"}},{"name":"usd","in":"query","required":false,"description":"Amount in USD (or microUsd) — POST body.","schema":{"type":"number"}},{"name":"ref","in":"query","required":true,"description":"Idempotency key, e.g. stripe_pi_… (POST body).","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=0, stale-while-revalidate=0`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing / invalid / revoked / expired API key (when gating is enabled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Admin endpoint unavailable — HYPO_ADMIN_TOKEN not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/account/webhook/{provider}":{"post":{"operationId":"api_account_webhook_provider","summary":"PSP deposit webhook (Coinbase Commerce / Stripe / generic HMAC)","description":"Signed deposit webhook sink. provider ∈ coinbase | stripe | hmac. The signature is verified over the RAW body (fail-closed if the provider secret is unset), then the deposit is credited to the account in metadata.hypo_owner — idempotent by the PSP event id. This is the no-custody path (the PSP attributes the payment + handles per-customer addresses + fiat); the on-chain watcher is the self-custody path. Both credit the same ledger.\n\nResponse shape: `{ ok, provider, owner, applied, microUsd, balanceMicroUsd }  (200); ignored events → { ok, ignored }; bad signature → 401; secret unset → 503`","tags":["account"],"security":[{"PspSignature":[]}],"parameters":[{"name":"provider","in":"path","required":true,"description":"Path param: coinbase | stripe | hmac.","schema":{"type":"enum"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=0, stale-while-revalidate=0`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Invalid PSP webhook signature","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"PSP secret not configured (fail-closed)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/account/vouchers":{"post":{"operationId":"api_account_vouchers","summary":"Admin — issue prepaid voucher codes (one of the parallel funding paths)","description":"Issue redeemable voucher codes worth a fixed amount, gated by HYPO_ADMIN_TOKEN. POST {usd|microUsd, count?, expiresAt?, label?} returns the raw codes ONCE (only SHA-256 hashes are stored). GET lists voucher metadata. Customers redeem at /api/account/redeem; the credit lands in the same prepaid balance as on-chain / PSP / admin top-ups.\n\nResponse shape: `POST → { ok, count, microUsd, codes:[\"HYPO-…\"] }; GET → { ok, vouchers:[record] }`","tags":["account"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"usd","in":"query","required":false,"description":"Voucher value in USD (or microUsd) — POST body.","schema":{"type":"number"}},{"name":"count","in":"query","required":false,"description":"How many to issue (default 1, max 1000) — POST body.","schema":{"type":"integer"}},{"name":"expiresAt","in":"query","required":false,"description":"Expiry epoch-ms, or null — POST body.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=0, stale-while-revalidate=0`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing / invalid / revoked / expired API key (when gating is enabled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Admin endpoint unavailable — HYPO_ADMIN_TOKEN not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/account/redeem":{"post":{"operationId":"api_account_redeem","summary":"Redeem a prepaid voucher code into your balance","description":"Redeem a voucher (POST {code}) — single-use + idempotent — crediting the caller's prepaid balance. Authenticated by your API key. Works alongside on-chain deposits, PSP webhooks, and admin credits: all converge on one balance per account.\n\nResponse shape: `{ ok, owner, microUsd, balanceMicroUsd } (200); invalid OR already-redeemed → 404 invalid_code (indistinguishable, anti-enumeration); expired → 410`","tags":["account"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"code","in":"query","required":true,"description":"The voucher code, e.g. HYPO-ABCDE-FGHJK-LMNPQ (POST body).","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=0, stale-while-revalidate=0`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing / invalid / revoked / expired API key (when gating is enabled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/account/treasury":{"get":{"operationId":"api_account_treasury","summary":"Admin — prepaid float/treasury report (yield visibility)","description":"Total prepaid liability held across all customers — the float you custody until usage consumes it, available for yield (T-bills / on-chain). Gated by HYPO_ADMIN_TOKEN.\n\nResponse shape: `{ ok, float:{ totalMicroUsd, totalUsd, accounts, nonZeroAccounts } }`","tags":["account"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=0, stale-while-revalidate=0`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing / invalid / revoked / expired API key (when gating is enabled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Admin endpoint unavailable — HYPO_ADMIN_TOKEN not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/bot/status":{"get":{"operationId":"api_bot_status","summary":"HYPO Bot — system + per-account status (free)","description":"Status for the autonomous trading bot (bot.hypo.markets). Unauthenticated → public system status (execMode mock|live, global kill, engine-loop flag). Authenticated (API key) → adds the caller's config, kill state, prepaid balance, recent orders, and access-fee totals. NON-CUSTODIAL: the bot trades on the USER's OWN venue account; HYPO never holds trading funds — only the per-trade access fee billed from prepaid credit. Execution is DORMANT (execMode='mock') until live adapters + the credential vault ship.\n\nResponse shape: `{ ok, authenticated, system:{execMode:'mock'|'live', globalKill, engineLoop, vaultEnabled}, killed?, balanceMicroUsd?, config?:{enabled, venues[], assets[], minConfidence, tradeSizeUsd, feeMicroUsd, maxStaleMs}, connectedVenues?:[{venue, kind, addedAt}], activity?:{orderRecords, filled, fills, accessFeesMicroUsd}, recentOrders?:[{t, clientOrderId, venue, venueAsset, side, sizeUsd, status, feeAccessMicroUsd}] }`","tags":["bot"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=0, stale-while-revalidate=0`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing / invalid / revoked / expired API key (when gating is enabled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/bot/control":{"post":{"operationId":"api_bot_control","summary":"HYPO Bot — configure the caller's bot + kill-switch","description":"Update the caller's single bot. Body (all optional): enabled, venues (subset of hyperliquid|polymarket|kalshi|limitless), assets[], minConfidence (0..1 signal floor), tradeSizeUsd (per-trade notional — NO cap), maxStaleMs (stale-signal guard), kill (boolean — instant halt override that wins over enabled). feeMicroUsd is NOT user-settable (operator revenue). 1 bot per key; open more by provisioning more keys.\n\nResponse shape: `{ ok, config:{enabled, venues[], assets[], minConfidence, tradeSizeUsd, feeMicroUsd, maxStaleMs, updatedAt}, killed }`","tags":["bot"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=0, stale-while-revalidate=0`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing / invalid / revoked / expired API key (when gating is enabled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/bot/trade":{"post":{"operationId":"api_bot_trade","summary":"HYPO Bot — place one order on the caller's own venue account (billed per trade)","description":"Place ONE explicit order non-custodially on the caller's OWN venue account, billing the flat per-trade access fee from prepaid credit ONLY on a real fill. Body: { venue (hyperliquid|polymarket|kalshi|limitless), asset, side (buy|sell), sizeUsd>0, markPrice>0 (execution reference), clientOrderId? (idempotency) }. Idempotent on clientOrderId — a replay never double-places or double-bills. Live execution is double-gated (HYPO_BOT_EXEC_MODE=live AND the per-user credential vault); until then it returns 503 in live mode and executes against the deterministic MockVenue in mock mode. 402 on insufficient prepaid credit; 423 when the kill-switch is set.\n\nResponse shape: `{ ok, execMode:'mock'|'live', outcome:{status:'filled'|'resting'|'rejected'|'duplicate'|'killed'|'insufficient_balance', clientOrderId, chargedMicroUsd, order?, reason?} }`","tags":["bot"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=0, stale-while-revalidate=0`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing / invalid / revoked / expired API key (when gating is enabled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/bot/credentials":{"post":{"operationId":"api_bot_credentials","summary":"HYPO Bot — connect/list/disconnect your OWN venue credential (non-custodial)","description":"Manage the credential the bot uses to place orders on YOUR OWN venue account — the non-custodial key. Secrets are AES-256-GCM encrypted at rest and NEVER returned or logged. POST {venue, kind:'apiKey'|'wallet'|'oauth', secret:{...}} to connect; DELETE {venue} to disconnect; GET to list connected venues (metadata only). 503 when the vault is not configured (HYPO_BOT_VAULT_KEY unset). STRONGLY recommend supplying trade-only / withdraw-disabled keys — even a vault breach can then only trade, never withdraw (HYPO never holds funds or withdrawal authority).\n\nResponse shape: `{ ok, vaultEnabled?, connected:[{venue, kind, addedAt}] }  (secrets never returned)`","tags":["bot"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=0, stale-while-revalidate=0`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing / invalid / revoked / expired API key (when gating is enabled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/admin/flags":{"post":{"operationId":"api_admin_flags","summary":"Admin — live operational flag state + global kill toggle","description":"The operator control surface (powers the /ops console). GET returns the LIVE state of every operational flag across bot (execMode, engineLoop, vaultEnabled, globalKill, tradeFee), monetization (paidGating, prepaidBilling), deposits (EVM, Solana), and security (trustProxy). POST {globalKill:true|false} toggles the runtime bot kill-switch (no restart). Env-driven flags are read-only (flip in the engine's env file + restart). Gated by HYPO_ADMIN_TOKEN (Bearer).\n\nResponse shape: `{ ok, flags:{ bot:{execMode,engineLoop,vaultEnabled,globalKill,tradeFeeMicroUsd}, monetization:{paidGating,prepaidBilling}, deposits:{evm,solana}, security:{trustProxy}, dataVenues:[{venue,shipped,tier}], process:{venue,nodeEnv} }, engines:[{venue,port,verdict}] }`","tags":["account"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=0, stale-while-revalidate=0`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing / invalid / revoked / expired API key (when gating is enabled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Admin endpoint unavailable — HYPO_ADMIN_TOKEN not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/bot/leaderboard":{"get":{"operationId":"api_bot_leaderboard","summary":"HYPO Bot — public anonymous leaderboard (realized P&L)","description":"Public ranking of all bots by realized P&L (descending). FREE, no auth, no PII — each bot is a stable pseudonymous handle derived from its hashed ledger id; no owner, balance, or credential is ever exposed. The traction surface. ?limit (1-200, default 50).\n\nResponse shape: `{ ok, leaderboard:[{handle, realizedPnlUsd, winRate, tradeCount, totalVolumeUsd, openPositions}] }`","tags":["bot"],"parameters":[{"name":"limit","in":"query","required":false,"description":"Max rows, 1-200, default 50.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=0, stale-while-revalidate=0`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/bot/tape":{"get":{"operationId":"api_bot_tape","summary":"HYPO Bot — public live-tape SSE (anonymous fills)","description":"Server-Sent Events stream of recent fills across ALL bots, anonymised (pseudonymous handle, no PII). A 'snapshot' frame on connect, then 'fills' frames as new trades land (polls the durable ledger). ': heartbeat' every 25s. FREE, no auth. The live 'show parade'.\n\nResponse shape: `text/event-stream: data:{type:'snapshot'|'fills', fills:[{t, handle, venue, venueAsset, side, filledUsd, avgPrice}]}; ': connected' + ': heartbeat' comments`","tags":["bot"],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"`public, s-maxage=0, stale-while-revalidate=0`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Validation error","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Your API key. Premium endpoints require it when HYPO_PAID_GATING is on; free endpoints ignore it."},"BearerAuth":{"type":"http","scheme":"bearer","description":"Same key as a Bearer token (Authorization: Bearer <key>). For /api/account/keys + /api/account/credit, supply the HYPO_ADMIN_TOKEN instead."},"PspSignature":{"type":"apiKey","in":"header","name":"X-CC-Webhook-Signature","description":"PSP webhook signature over the RAW body — provider-specific header: X-CC-Webhook-Signature (Coinbase Commerce), Stripe-Signature (Stripe), or X-HYPO-Signature (generic HMAC). Verified server-side; fail-closed."}},"schemas":{"Success":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean","enum":[true]},"apiVersion":{"type":"string","description":"Present ONLY on /api/m2m/* responses (currently \"v1\"). Absent on all other routes, which are versioned by deployment via info.version. See info.x-api-versioning."}},"additionalProperties":true,"description":"Successful response envelope. `ok: true` is always present; `apiVersion` is present only on /api/m2m/* responses; the rest of the body shape is endpoint-specific (see `description`)."},"Error":{"type":"object","required":["ok","error"],"properties":{"ok":{"type":"boolean","enum":[false]},"error":{"type":"string"}},"additionalProperties":false,"description":"Error envelope returned with 4xx/5xx + Cache-Control: no-store."}}}}