https://app.seer.pm
Functions are available at: https://app.seer.pm/.netlify/functions/<function-name>
get-market
Fetches a single market by ID or URL/slug. Merges real-time subgraph data with cached database data (including verification status) for maximum accuracy.Body (JSON)
Response 200
Serialized market object: allbigint fields are returned as string for JSON compatibility.
Errors
- 400 –
Missing request bodyorMissing required parameters: chainId and (id or url). - 404 –
Market not found. - 500 – Internal server error.
markets-search
Search and list markets with filters, pagination, and sorting. Queries one or multiple chains; whenVITE_TESTNET_WEBSITE=1 only Sepolia is used.
Body (JSON) – Search parameters
All optional. Defaults in the handler:limit=1000, page=1, chains = all supported (or Sepolia only on testnet).
Response 200
Errors
- 400 –
Missing request body. - 500 –
Internal server error.
get-token-transactions
Returns top holders and recent transactions for one or more tokens (per chain).Query params
Response 200
recentTransactions items use the following type (value is serialized as string in JSON):
Cache-Control: public, max-age=300 (5 minutes).
Errors
- 400 – Missing
tokenIdsorchainId, invalidchainId, or unsupported chain. - 500 – Internal server error.
get-transactions
Transaction history for an account: swaps, liquidity (LP add/remove), and CTF events (split/merge/redeem). Always computed across all production chains and cached; optional filters apply in memory on the cached payload. Covers the account and the TradeExecutor contracts it owns. Someone trading through an executor holds the outcome tokens inside that contract and the DEX indexes the swap with the executor asrecipient, so querying the address alone returned only what the EOA did directly. Rows carry sourceWallet, which differs from the requested account when an executor produced them, and are deduped across the wallets by eventId. Executor discovery is best-effort: if it fails the response is the account’s own rows, as before.
Query params
Response 200
Array ofTransactionData, sorted by timestamp descending (then blockNumber). Each row includes chainId.
Errors
- 400 – Missing
account, invalidchainId/eventType, or non-numericstartTime/endTime. - 500 – Internal server error.
get-portfolio
Positions for an account: outcome token balances per market, with market data, outcome, collateral, and redeemed prices. Always computed across all production chains and cached; optionalchainId filters the cached payload.
Query params
Response 200
Array ofPortfolioPosition:
Errors
- 400 – Missing
account, or invalidchainId. - 500 – Internal server error.
get-portfolio-value
Current portfolio value and ~24h mark-to-market change (outcome positions only; same collateral filter asget-portfolio). Totals are USD (primary collateral × spot USD via DexScreener / on-chain sDAI·sUSDS, same source as the PnL leaderboard).
Query params
Response 200
currentPortfolioValue, historyPortfolioValue, historyTimestamp, delta, deltaPercent, unit: "USD".
get-portfolio-pl
Period P/L for an account (outcome MTM, including what the account holds in LP positions, + router collateral legs − swap/LP cashflow). Adding or removing liquidity on its own does not move P/L; price moves and fees compounded into the position do. Fees accrued but not collected are not counted. Generic markets only — Futarchy is excluded so non-primary notional (PNK/GNO, …) is not mixed into the profile’s primary units.get-portfolio still lists Futarchy positions.
- Global (no
marketId/marketIds): reads materialized rows from Supabasepnl_leaderboard(app_id = all). No live compute; miss → zeros. Values are USD (pnl=pnl_usd; other fields converted withcollateral_price_usd).chainId=allsums USD across chains. Response includesunit: "USD". - Market-scoped (
marketIdormarketIds): computes live viacomputePortfolioPlAllPeriodsin native collateral units. Futarchy ids → 404.chainId=allis not allowed.
Query params
@seer-pm/sdk fetchPortfolioPnL / fetchPortfolioValue send collateralProfile from getActiveCollateralProfileName() after configureCollateral — see Collateral profiles. fetchPortfolioPnL / fetchPortfolioValue accept chainId: number | "all".
get-pnl-leaderboard
Ranked wallet P/L for markets configured under Seer apps (web/src/lib/apps.ts). Data is materialized by scheduled-refresh-pnl-leaderboard / refresh-pnl-leaderboard-background into Supabase pnl_leaderboard (apply web/supabase/sql/pnl_leaderboard.sql first). The refresh considers every wallet with analytics activity (not a top-N by tx count) and rotates a stale/missing batch each run.
Query params
All rankings use USD (
pnl_usd from materialization: native collateral PnL × spot collateral USD). Per-chain filters still return USD so ranks stay comparable; chainId=all sums pnl_usd across chains. App filter all is independent of SEER_APPS market lists.
Each row includes an optional username when the wallet has chosen one. Usernames are opt-in, so its absence means only that the wallet never set one.
Trader score
Each row carriesscore (0-100, one decimal) and tier (Elite ≥85, Great ≥70, Good ≥55, Average ≥40, Weak below). It is a weighted mean of five clamped-linear components, shrunk toward 50 afterwards:
Every ratio is taken over the same market set: those carrying more than $1 of capital at risk.
scoredCapitalUsd is the capital in exactly that set, which is why returns can differ slightly from the row’s roi (whole-wallet).
hitRate scores the wallet against its own break-even rate — avgLoss / (avgWin + avgLoss), from the realised per-market payoffs — rather than a fixed band, because a prediction market pays out inversely to the price paid and a fixed band marks down every long-shot strategy regardless of profitability. scoreBreakdown.inputs carries hitRate, breakEvenHitRate and their difference hitEdge; the component scores hitEdge.
scoreBreakdown.sampleShrink is { scoredMarketCount, factor, rawScore }, where factor is n / (n + 10) and score = 50 + (rawScore − 50) × factor. The weighted component points sum to rawScore, not to score — a client rendering the breakdown has to show the shrink or it will look like an arithmetic error.
score is null, never 0, when the wallet has fewer than 3 markets above 100 of capital across those markets, or more than 25% of its pnl_usd outside them. When it is null the row also carries scoreUnavailable — { reason: "markets" | "capital" | "coverage", scoredMarketCount, minScoredMarkets, minCapitalUsd, dustUsd, unscoredPnlFraction, maxUnscoredPnlFraction } — so a client can say which gate was missed. Note that marketCount counts every traded market while the gate counts only those above $1 of capital at risk, so a wallet can show a large marketCount and still be unscored, or (for a pure LP wallet, which never swaps) a smaller one. Two priors keep small books honest on top of the shrink: the hit rate carries 5 pseudo-markets at 50%, and the profit factor carries symmetric prior mass of 2% of the book’s own gross flow on both sides, so a book with no losing market reads a finite ratio rather than infinity.
reason: "coverage" means the scored markets do not account for the wallet’s P/L. Positions that move value without putting primary collateral at risk — conditional markets, whose pool trades one outcome token against another, and outcome tokens received by transfer — carry P/L into pnl_usd against no measured capital, so they never enter the scored set. pnl and roi on the row still include them.
Read score as a summary of the wallet’s cross-market distribution. It is not a time-series metric: lossBurn is a terminal-state loss measure — negative terminal market P/L over capital — and says nothing about the path taken to get there (it cannot see a position that was deep underwater mid-window and recovered), breadth measures concentration across markets rather than the smoothness of an equity curve, and returns has a capital denominator but no risk denominator. scoreBreakdown.method is "markets" and identifies which implementation produced the number.
The score is computed at read time from additive statistics stored on pnl_leaderboard, so it is correct after the executor→owner, app-scope and cross-chain merges. It is not stored per row, and it is not meaningful to average two scores.
Those statistics describe an owner’s book, not an address’s. A wallet that trades through a TradeExecutor contract holds its capital on one address and, after the contract sweeps the outcome tokens out, its value on another; each market would fall below the $1 gate on both. So the per-market books of an owner and its executors are combined at materialization, before the gate, and the resulting statistics are stored on the owner’s row while the executors’ rows carry zeros. market_count follows them: it counts distinct markets, so summing each member’s own count double-counted every market an owner and its executor both traded. The additive totals — pnl_usd, volume_usd, capital_deployed — do stay per address and still sum across the merge.
get-market-pnl-leaderboard
Ranked wallet P/L within one market, including its conditional children. Readspnl_market_leaderboard (apply web/supabase/sql/pnl_market_leaderboard.sql first) — same materialization pass as the global board, so freshness is identical.
Query params
Response 200
{ marketId, marketIds, chainId, period, total, rows }, where each row is { rank, address, pnl, volume, roi, unit: "USD", capitalUsd, score, tier, updatedAt, mergedWallets? }.
score and tier are the trader’s protocol-wide score (the app_id = 'all' board for the same chain and period), not a score for this market: a single market can never clear the score’s 3-market eligibility gate, so a market-scoped score would always be null. It is looked up for the current page only, after pagination — which is why this endpoint cannot rank by it. A failed lookup yields score: null rather than failing the board.
markets-charts
Returns chart data (hourly price/volume) for markets. If IDs are provided, only those markets; if omitted, all cached chart data.Query params
Response 200
Object keyed bymarketId with each market’s chart data (array of series):
Errors
- 400 –
idswas provided but no valid address:No valid market IDs provided. - 500 – Supabase or internal error.
Quick reference for AIs
All responses are JSON. Errors return
{ "error": "message" } with the appropriate HTTP status code.