API Reference
PrediX exposes 3 layers for developers:
| Layer | Purpose | When to use |
|---|---|---|
| Indexer API (Ponder + PostgreSQL + Hono) | Raw on-chain data: market, position, trade, OHLC | Bots, analytics, raw data |
| Backend API (NestJS v2) | View model for FE/app: cache + metadata + i18n + auth + comments | FE / mobile app user-facing |
| Smart contract events | Canonical source of truth on-chain | Custom subgraph, monitor, listener |
When to use BE vs Indexer:
| Need | Indexer | Backend |
|---|---|---|
| Raw on-chain data | Yes | wraps Indexer |
| Display metadata (title, category, icon) | No | Yes (MongoDB) |
| Localized i18n | No | Yes |
| Auth session (SIWE) | No | Yes |
| Cache (2s hot / 60s warm) | No | Yes |
| Notifications / comments | No | Yes |
Bot/analytics raw data -> Indexer. FE/app user-facing -> Backend.
Base URL
Section titled “Base URL”| Env | Indexer | Backend |
|---|---|---|
| Beta (live) | TBA - see Beta info | TBA - see Beta info |
| Production (TBA) | TBA | TBA |
Schema is identical across both environments - switching beta to production only requires changing the base URL.
Authentication
Section titled “Authentication”- Indexer: public read-only.
- Backend: public + auth via SIWE cookie session (details in the SIWE auth flow section below).
Response envelope
Section titled “Response envelope”Indexer success:
{ "data": <payload>, "meta": { "limit": 20, "offset": 0, "count": 150 }}Backend success:
{ "data": <payload>, "meta": { "timestamp": 1740100000, "version": "v2", "reqId": "uuid-optional" }}Single item: "meta": null (Indexer) or meta.list fields omitted (BE).
Error (both):
{ "error": "MarketNotFound", "message": "Market 0xabc... not found"}BE adds details: [{path, message}] for validation errors + a code enum (see Error codes section).
Indexer endpoints
Section titled “Indexer endpoints”Markets
Section titled “Markets”GET /api/markets list (status, limit, offset, sort, category, featured)GET /api/markets/:id single (id = decimal string)GET /api/markets/:id/trades Union Router trades + CLOB taker fillsGET /api/markets/:id/prices price + open-interest time seriesGET /api/markets/:id/positions per-user positions (filter by address)GET /api/markets/:id/orderbook current snapshot grouped by tickGET /api/markets/:id/liquidity AMM liquidity bookMarkets list response:
{ "data": [{ "id": "1", "marketIdHex": "0x000...01", "question": "BTC > $100k before 2027?", "endTime": 1798752000, "yesPrice": "0.62", "totalCollateral": "125000.000000", "volume": "523000.000000", "tradeCount": 2341, "isResolved": false, "outcome": null, "creator": "0xfad5...", "oracle": "0x7887...", "createdAt": 1740000000, "createdBlock": 49800000 }], "meta": { "limit": 20, "offset": 0, "count": 150 }}Pricing
Section titled “Pricing”GET /api/markets/:id/amm-state Uniswap v4 pool stateGET /api/markets/:id/price-delta?window=24 price change (1-720h window)GET /api/markets/:id/volume-window?window=24 volume windowGET /api/markets/:id/candles?timeframe=1h&from=&to= OHLCTimeframes: 1m, 5m, 15m, 1h, 4h, 1d, 1w.
Events (multi-outcome)
Section titled “Events (multi-outcome)”GET /api/eventsGET /api/events/:id detail with marketIds + winningIndexPortfolio
Section titled “Portfolio”GET /api/portfolio/:address portfolio snapshot (positions, P&L)GET /api/portfolio/:address/history historical portfolio seriesGET /api/portfolio/:address/pnl aggregated PnLGET /api/user-stats leaderboard (sort=pnl|volume|accuracy)GET /api/user-stats/:address per-user aggregate statsGET /api/users/:address/accuracy resolved-position accuracy scoreGET /api/users/:address/resolved-positions resolved positionsGET /api/users/:address/maker-fills maker fills (CLOB)Exchange (CLOB)
Section titled “Exchange (CLOB)”GET /api/exchange/orders list (marketId, owner, status filters)GET /api/exchange/orders/:orderId single orderGET /api/exchange/matches match recordsGET /api/trades/:txHash/breakdown per-tx CLOB vs AMM breakdownLiquidity (AMM)
Section titled “Liquidity (AMM)”GET /api/liquidity/positions/:address LP positions by providerGET /api/liquidity/events add/remove liquidity eventsOracles + admin
Section titled “Oracles + admin”GET /api/oracles/approvedGET /api/oracles/manual/:oracleContract/reportsGET /api/oracles/chainlink/:oracleContract/marketsGET /api/admin/role-changesGET /api/admin/pausesGET /api/admin/diamond-cutsGET /api/admin/fee-changesGET /api/admin/hook-proxy-upgradesProtocol stats
Section titled “Protocol stats”GET /api/stats totalMarkets, totalVolume, totalTrades, totalUsers, etcSystem
Section titled “System”GET /api/health latestIndexedBlock, lagBlocksBackend endpoints
Section titled “Backend endpoints”Primitives - strict wire format
Section titled “Primitives - strict wire format”| Type | Format | Example |
|---|---|---|
| Address | lowercase 0x[a-f0-9]{40} |
"0xfad5..." |
| MarketId | lowercase 0x[a-f0-9]{64} |
"0xabc...64hex" |
| Price | decimal string | "0.524" |
| Money | object | {decimal:"10.5", raw:"10500000", decimals:6, unit:"USDC"} |
| Timestamp | unix seconds int | 1740100000 |
| User string | object | {key:"market.0xabc.title", fallback:"Will BTC..."} |
Market discriminator (Stripe pattern)
Section titled “Market discriminator (Stripe pattern)”type Market = | { kind: 'binary', binary: BinaryData, /* ... */ } | { kind: 'scalar', scalar: ScalarData, /* ... */ } | { kind: 'multi', multi: MultiData, /* ... */ } | { kind: 'sports', sports: SportsData, /* ... */ } | { kind: 'grouped', grouped: GroupedData, /* ... */ };FE: market[market.kind] - exhaustive switch.
Markets & events
Section titled “Markets & events”GET /api/markets list with filters + paginationGET /api/markets/resolve resolve idOrSlug -> canonical idGET /api/markets/hot curated hot listGET /api/markets/:idOrSlug single (lookup by id or slug)GET /api/markets/:idOrSlug/outcomes/:outcomeSlugGET /api/markets/:idOrSlug/orderbookGET /api/markets/:idOrSlug/tradesGET /api/markets/:idOrSlug/pricesGET /api/markets/:idOrSlug/candlesGET /api/markets/:idOrSlug/commentsPOST /api/markets/:idOrSlug/comments [auth]PATCH /api/markets/:idOrSlug/comments/:commentId [auth]Pricing
Section titled “Pricing”GET /api/markets/:id/pricing/view combined CLOB + AMM viewPOST /api/markets/:id/pricing/quote quote before swapPOST /api/markets-batch/price-views batch up to 50POST /api/markets-batch/candles batch OHLCTrading & portfolio
Section titled “Trading & portfolio”GET /api/users/:address/profileGET /api/trading/:address/ordersGET /api/trading/:address/portfolioGET /api/trading/:address/positions/:marketIdGET /api/trading/:address/tradesGET /api/trading/:address/pnlGET /api/trading/:address/redemption-quote/:marketIdGET /api/trades/:txHash/breakdownGET /api/orders/:orderId/fillsAuth (SIWE)
Section titled “Auth (SIWE)”GET /api/auth/challenge?address=0x...POST /api/auth/verifyGET /api/auth/me [auth required]GET /api/auth/me/settings [auth required]POST /api/auth/logoutAccount abstraction
Section titled “Account abstraction”POST /api/aa/auth/passkey/register/challengePOST /api/aa/auth/passkey/register/verifyPOST /api/aa/auth/passkey/loginPOST /api/aa/auth/ecdsa/... ECDSA fallback flowPOST /api/aa/bundler Pimlico bundler proxyPOST /api/aa/paymaster/sponsor sponsor UserOpFaucet
Section titled “Faucet”POST /api/faucet [auth] empty body; destination = session userGET /api/faucet/status?address=0x... per-address claim statusNotifications
Section titled “Notifications”GET /api/notifications?unread=trueGET /api/notifications/unread-countPATCH /api/notifications/:id/readPOST /api/notifications/read-allRewards & gamification
Section titled “Rewards & gamification”GET /api/rewardsGET /api/leaderboardGET /api/tradersGET /api/pointGET /api/referralGET /api/tagsGovernance
Section titled “Governance”Planned: Governance endpoints for vePRX voting will be available in Phase 2.
System
Section titled “System”GET /health mongo + indexer probeGET /ready readiness probeGET /version build version + git shaSIWE auth flow
Section titled “SIWE auth flow”
SIWE auth: GET /auth/challenge -> server returns nonce -> user signMessage -> POST /auth/verify -> BE verifies ECDSA -> set HTTPOnly cookie 7 days
// 1. Challengeconst { message } = await fetch(`${API}/api/auth/challenge?address=${addr}`).then(r => r.json());
// 2. Signconst signature = await walletClient.signMessage({ message });
// 3. Verifyawait fetch(`${API}/api/auth/verify`, { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ address: addr, signature }),});Cache headers
Section titled “Cache headers”BE has 2 tiers: hot 2s (markets list/detail, orderbook, trades) - warm 60s (user profile, category, stats).
Response: X-Cache: HIT | MISS, X-Cache-Tier: hot | warm.
Error codes (closed set)
Section titled “Error codes (closed set)”| Code | HTTP | Meaning |
|---|---|---|
MARKET_NOT_FOUND |
404 | Market id does not exist |
MARKET_PAUSED |
400 | Market is currently paused |
INDEXER_UNAVAILABLE |
503 | Indexer circuit breaker tripped |
INVALID_ADDRESS |
400 | Address malformed |
INVALID_MARKET_ID |
400 | MarketId malformed |
AUTH_REQUIRED |
401 | Endpoint requires a session |
AUTH_INVALID |
401 | Session expired or invalid |
FORBIDDEN |
403 | Insufficient role |
VALIDATION_FAILED |
400 | Request body failed validation |
RATE_LIMIT_EXCEEDED |
429 | Rate limit exceeded |
INSUFFICIENT_BALANCE |
400 | Wallet has insufficient balance |
SLIPPAGE_EXCEEDED |
400 | On-chain revert due to slippage |
OpenAPI typed client
Section titled “OpenAPI typed client”BE publishes OpenAPI 3.1. FE generates types automatically:
npm run sync:schemasnpm run check:schemas-syncimport type { paths } from '@predix/api-types';import createClient from 'openapi-fetch';
const api = createClient<paths>({ baseUrl: BACKEND_BASE_URL });const { data } = await api.GET('/markets/{id}', { params: { path: { id: '0x0001...' } },});Smart contract events
Section titled “Smart contract events”Source of truth on-chain - bot listeners, custom subgraphs, and monitoring services should consume from here.

Event source: Router.Trade = canonical (volume + trades count), Hook_MarketTraded = analytics only (priceSnapshot, NOT volume), PositionSplit/Merge/Redeem/Refund = audit rows always land
- Canonical trade:
Router.Trade-protocolStats.totalVolume / totalTradesonly increments from this event. - AMM swap analytics:
Hook_MarketTraded- priceSnapshot only, does not count volume (avoids double-counting). - Audit rows always land:
PositionSplit,PositionMerged,TokensRedeemed,MarketRefunded- recorded regardless of recipient.
Router events
Section titled “Router events”event Trade(uint256 indexed marketId, address indexed trader, address indexed recipient, TradeType tradeType, uint256 amountIn, uint256 amountOut, uint256 clobFilled, uint256 ammFilled);event DustRefunded(address indexed recipient, address indexed token, uint256 amount);event ClobSkipped(uint256 indexed marketId, address indexed recipient, bytes4 reason);Trade -> tables: trade, position (if recipient is not a protocol contract), market.volume, market.tradeCount, priceSnapshot (source=“router”), protocolStats, user, userStats.
Exchange events
Section titled “Exchange events”event OrderPlaced(bytes32 indexed orderId, uint256 indexed marketId, address indexed owner, Side side, uint256 price, uint256 amount, bytes32 builder);event OrderMatched(bytes32 indexed makerOrderId, bytes32 indexed takerOrderId, uint256 indexed marketId, MatchType matchType, uint256 amount, uint256 price, bytes32 makerBuilder, bytes32 takerBuilder);event OrderCancelled(bytes32 indexed orderId);event TakerFilled(uint256 indexed marketId, address indexed taker, address indexed recipient, Side takerSide, uint256 totalFilled, uint256 totalCost, uint256 matchCount);event FeeCollected(uint256 indexed marketId, uint256 amount);Tables: exchangeOrder, orderMatch, takerFill, position (non-protocol).
MarketFacet events
Section titled “MarketFacet events”event MarketCreated(uint256 indexed marketId, address indexed creator, address indexed oracle, address yesToken, address noToken, uint256 endTime, string question);event PositionSplit(uint256 indexed marketId, address indexed user, uint256 amount);event PositionMerged(uint256 indexed marketId, address indexed user, uint256 amount);event MarketResolved(uint256 indexed marketId, bool outcome, address indexed resolver);event MarketEmergencyResolved(uint256 indexed marketId, bool outcome, address indexed resolver);event TokensRedeemed(uint256 indexed marketId, address indexed user, uint256 winningBurned, uint256 losingBurned, uint256 fee, uint256 payout);event RefundModeEnabled(uint256 indexed marketId, address indexed enabler);event MarketRefunded(uint256 indexed marketId, address indexed user, uint256 yesBurned, uint256 noBurned, uint256 payout);Tables: market, outcomeToken, positionSplit, positionMerge, redemption, refundClaim, feeConfigChange.
EventFacet events
Section titled “EventFacet events”event EventCreated(uint256 indexed eventId, address indexed creator, uint256 endTime, string name, uint256[] marketIds, address oracle);event EventResolved(uint256 indexed eventId, address indexed resolver);event EventRefundModeEnabled(uint256 indexed eventId);Table: eventGroup.
Hook events
Section titled “Hook events”event Hook_PoolRegistered(uint256 indexed marketId, PoolId indexed poolId, address yesToken, address quoteToken, bool yesIsCurrency0);event Hook_MarketTraded(uint256 indexed marketId, address indexed trader, bool isBuy, uint256 usdcVolume, uint256 yesVolume, uint256 yesPrice, uint256 noPrice);Hook_PoolRegistered -> hookPoolBinding (essential lookup for filtering PoolManager events). Hook_MarketTraded -> priceSnapshot (source=“hook_amm”), market.lastTradeAt. Does NOT count volume.
Oracle events
Section titled “Oracle events”// ManualOracleevent OutcomeReported(uint256 indexed marketId, bool outcome, address indexed reporter);event OutcomeRevoked(uint256 indexed marketId, address indexed admin);event ReportReopened(uint256 indexed marketId, address indexed admin);event ChallengeDelayUpdated(uint256 previous, uint256 current);
// ManualOracle - multi-outcome event variantsevent EventOutcomeReported(uint256 indexed eventId, uint256 winningIndex, address indexed reporter);event EventOutcomeRevoked(uint256 indexed eventId, address indexed admin);event EventReportReopened(uint256 indexed eventId, address indexed admin);
// ChainlinkOracleevent MarketRegistered(uint256 indexed marketId, address indexed feed, int256 threshold, bool gte, uint64 snapshotAt);event MarketResolved(uint256 indexed marketId, int256 price, bool outcome);event MarketUnregistered(uint256 indexed marketId);Diamond admin events
Section titled “Diamond admin events”event RoleGranted(bytes32 indexed role, address indexed account, address sender);event RoleRevoked(bytes32 indexed role, address indexed account, address sender);event DiamondCut(FacetCut[] cuts, address init, bytes data);event GlobalPaused(address account);event ModulePaused(bytes32 module, address account);OutcomeToken (ERC-20)
Section titled “OutcomeToken (ERC-20)”event Transfer(address indexed from, address indexed to, uint256 value);event Approval(address indexed owner, address indexed spender, uint256 value);Each market has 1 YES + 1 NO token. Table: holder - canonical balance view.
PoolManager (Uniswap v4)
Section titled “PoolManager (Uniswap v4)”Filter by hookPoolBinding membership first:
Initialize-> insertammPoolStateSwap-> updatesqrtPriceX96,liquidity,tickModifyLiquidity-> updateliquidity += delta
WebSocket
Section titled “WebSocket”For real-time data, see WebSocket.
Common patterns
Section titled “Common patterns”Fetch top 10 markets by volume
Section titled “Fetch top 10 markets by volume”const res = await fetch(`${INDEXER_BASE_URL}/api/markets?sort=volume&limit=10`);const { data: markets } = await res.json();markets.forEach(m => console.log(`${m.question} - $${m.volume}`));Listen to Trade events in realtime
Section titled “Listen to Trade events in realtime”import { createPublicClient, http } from 'viem';import { unichain } from 'viem/chains';
const client = createPublicClient({ chain: unichain, transport: http() });
client.watchContractEvent({ address: ROUTER, abi: routerAbi, eventName: 'Trade', args: { marketId }, onLogs: (logs) => logs.forEach(log => console.log(log.args)),});Limits
Section titled “Limits”| Tier | Public | Auth | Quota |
|---|---|---|---|
| Free | 60 req/min/IP | 300 req/min/user | 10,000 req/day |
Auth endpoint (challenge/verify): 5/min.
BigInt serialization
Section titled “BigInt serialization”All on-chain amounts are uint256 -> serialized as decimal strings at the boundary ("125000.000000" instead of a number, to avoid precision loss). Parse client-side: BigInt(stripDecimal(str)).
Indexer lag
Section titled “Indexer lag”- L2 finality: ~12-15 min on Unichain mainnet (chain
130). - Indexer lag from head: typically < 60s (
/api/health). - Just traded but don’t see it -> wait 10-30s, retry.
Ponder handles reorgs automatically - chain reorg -> indexer reverts + replays. Clients need no custom logic.