Skip to main content
Per-wallet performance: PnL history, performance summary, open/closed positions, trade history, and public profile lookup. Full parameter reference and a live tester: API Reference.

Base URL

Authentication

The five /trader-stats/* routes (pnl-history, positions, summary, analysis, trades) are fully public and take no credentials at all — sending auth headers to them does nothing, and they never return 401 or 403. GET /search-traders is the only authenticated endpoint on this page (x-kairos-auth: api-key). Send the API-key triple (X-Client-Id, X-Api-Key, X-Api-Secret), a first-party session JWT, or an X-Admin-Secret. No API-key scope is required. Session-JWT callers additionally pass the invite gate; API-key and admin-secret callers bypass it. It shares the heavy rate-limit group (10 requests/minute) with /trader-stats/* and /top-holders. The /search-traders example below reads credentials from KAIROS_CLIENT_ID, KAIROS_API_KEY, and KAIROS_API_SECRET in your shell.

Address handling

wallet_address is validated as either an Ethereum address (0x..., 42 chars) or a Solana base58 address, normalized (EVM addresses lower-cased) before lookup — a value matching neither format returns 400. When omitted, positions and trades choose the provider with the most ClickHouse fills for the wallet, then fall back to address format (0x → Polymarket, Solana → Kalshi). PnL history defaults to Polymarket. Pass provider for deterministic results, especially for multi-venue EVM wallets.
Gotcha: provider auto-detection is not the same on every route. Positions, trades, and summary sniff the wallet’s fills; PnL history never does and always falls back to Polymarket; /search-traders resolves every 0x address to Polymarket. Two routes can therefore answer about two different venues for the same wallet unless you pass provider.

PnL history

Cumulative realized-PnL series for one wallet over a fixed time window, bucketed for charting. Public — no authentication required. Uses the heavy rate-limit group (10 requests/minute per trusted client IP).

Request

Gotcha: ALL is not all-time. The window map is 1D → last 24h in 1-minute buckets, 1W → last 7 days in 5-minute buckets, 1M and ALL → last 30 days in 1-hour buckets. ALL and 1M return identical data.

Response

Gotcha: data_points[].timestamp carries no timezone suffix — it is a ClickHouse-local UTC timestamp such as 2026-07-15T00:00:00. Parsers that assume local time will shift your chart. Treat it as UTC explicitly.
Gotcha: all PnL values are decimal strings, not floats — parse with a decimal library to avoid precision loss.
Responses are cached publicly for 30 seconds (stale-while-revalidate=60); the service also caches the computed history in Redis for 120 seconds. Providers not yet covered by the ClickHouse FIFO pipeline (Kalshi and other non-EVM venues) return an all-zero payload with data_points: []. Unlike positions and trades, this route does not auto-detect the provider from the wallet’s fills — omitting provider always queries Polymarket.

Positions

A page of the wallet’s open and closed positions, plus a performance summary computed over its full inventory. Public — no authentication required. Uses the heavy rate-limit group (10 requests/minute per trusted client IP).

Request

Response

Gotcha: two performance fields do not mean what their names suggest. win_rate is a fraction in 0–1, not a percentage — multiply by 100 before rendering. positions_value is the cost basis of open positions, not their mark-to-market value.
Market names/icons are resolved only for the returned page, so wallets holding tens of thousands of positions can’t blow past the backend’s max query size. Responses are cached publicly for 15 seconds (stale-while-revalidate=30); the service also caches per (provider, include_redeemable, status, limit, offset) in Redis for 30 seconds. The Kalshi adapter reads the configured authenticated Kalshi account and does not use wallet_address to select an arbitrary public account. Pass provider=kalshi only when that account-level view is intended.

Summary

A wallet’s performance stats alone — the same performance figures as /positions — without the position list, so the profile header and stats panel can render before the positions page. Public — no authentication required. Uses the heavy rate-limit group (10 requests/minute per trusted client IP).

Request

Response

performance carries the same shape as on /positions, and the two always agree. It is null for a venue whose stats come only from the full positions build — read them from /positions there; passing provider for such a venue returns performance: null rather than an error.
Responses are cached publicly for 15 seconds (stale-while-revalidate=30); the service also caches per (provider, include_redeemable) in Redis for 30 seconds.

Analysis

The trader profile’s analysis panel: realized PnL and buy count across four windows, lifetime win/loss, the distribution of closed positions by ROI, and a daily PnL calendar. Public — no authentication required. Uses the heavy rate-limit group (10 requests/minute per trusted client IP).

Request

Venue coverage: only venues whose trades flow through the lot allocator are supported — currently Predict.fun. For any other venue, supported is false and no figures are returned: windows and daily come back empty, and win_loss and roi_distribution are null. This is not an error — check supported before rendering the panel.

Response

Gotcha: the realized_pnl values here are JSON numbers, unlike the decimal strings on /pnl-history and /positions — parse them accordingly, and don’t reuse a string-parsing PnL helper across both shapes.
Gotcha: roi_distribution can be null while supported is true — that means the ROI read failed and the rest of the panel was served without it. Treat a null distribution as “unavailable this call”, not “no closed positions”, and retry rather than rendering an empty chart.
Windows, daily buckets, and win/loss are computed over whole UTC days ending today, so 1D covers today only and a partially elapsed day is still one full bucket. The panel is cached per wallet for up to a minute.

Trade history

A page of the wallet’s individual fills. Public — no authentication required. Uses the heavy rate-limit group (10 requests/minute per trusted client IP).

Request

Response

This endpoint skips the positions/inventory scan the full profile build does, so it stays fast even for high-frequency wallets. Responses are cached publicly for 15 seconds (stale-while-revalidate=30); the service also caches per (provider, trade_limit, trade_offset) in Redis for 30 seconds.
Look up a trader’s public profile by address. The only authenticated endpoint on this page. Auth: API key, JWT, or X-Admin-Secret (no scope). Rate limit: heavy group, 10 requests/minute (keyed by authenticated user id when a JWT session is present, otherwise by trusted client IP). Live upstream profile fetch — not served from a local cache.

Request

Every 0x address auto-detects as Polymarket — pass provider explicitly for other EVM venues (Opinion, predict.fun). Solana addresses auto-detect as Kalshi. Non-Polymarket venues currently return a synthetic, service-generated profile rather than a live upstream fetch.

Response

An address with an unrecognized format returns 200 with profile: null and an explanatory error. A blank address still returns 400.

Migration from removed profile routes

The former /trader-stats/profile/{wallet_address}, /trader-stats/category-breakdown/{wallet_address}, and /trader-stats/refresh/{wallet_address} routes are no longer registered. Replace a profile request with parallel calls to the current positions, trades, and PnL-history endpoints. There is no public force-refresh operation; use the cache headers above.

Errors

Errors are returned as { "detail": "<message>" }, except the rate limiter’s 429, which uses { "error": "Rate limit exceeded: <limit>" } plus X-RateLimit-* and Retry-After headers. The five /trader-stats/* routes take no credentials at all, so they never return 401 or 403. Only GET /search-traders is authenticated. The Code column holds the exact detail string where this page documents one, otherwise —.