---
title: "Get merged PnL data for a user across one or more wallets/providers"
method: GET
path: "/pnl/{user_id}"
tags: ["PnL"]
---

# Get merged PnL data for a user across one or more wallets/providers

`GET /pnl/{user_id}`

## Request

- Base URL: `https://data.kairos.trade`
- URL: `https://data.kairos.trade/pnl/{user_id}`
- Auth: API key in header `X-Client-Id` and API key in header `X-Api-Key` and API key in header `X-Api-Secret`

Fetches and merges PnL records/summary across the wallets supplied for this user via the general-purpose PnL path — not the real-time pipeline used by `/pnl/hover` and `/pnl/wallet-totals`. At least one of `polymarket_wallet`, `kalshi_wallet`, or `hyperliquid_wallet` is required — the request is rejected with 400 if none are supplied. `polymarket_wallet` and `hyperliquid_wallet` must be valid Ethereum addresses (normalized/lower-cased); `kalshi_wallet` is treated as an opaque user-id string (Kalshi is a CEX with internal, non-address identifiers). `provider` (repeatable query param, up to 16 items) filters the merged result down to specific provider(s); when omitted, all providers implied by the supplied wallets are used. Kalshi and Hyperliquid PnL are served only through this aggregate endpoint; neither is available from `/pnl/hover` or `/pnl/wallet-totals`. Hyperliquid open-position and unrealized PnL use HIP-4 balances and mark prices, while realized PnL and fee fields are currently zero. The caller-supplied `{user_id}` MUST match the authenticated principal's own id (JWT `sub`, or `user_id` for API-key auth) — mismatches are rejected with 403; this endpoint can only ever return the caller's own PnL. `limit`/`offset` paginate the merged record list; internally up to `min(limit + offset, 1000)` records per provider are fetched, so `total`/`has_more` in the response describe the page relative to that ceiling, not an authoritative full-history count. For API-key callers, each requested provider must additionally have its API access enabled; JWT/admin callers are exempt.

## Path parameters

- `user_id` string, required

## Query parameters

- `polymarket_wallet` string
- `kalshi_wallet` string
- `hyperliquid_wallet` string
- `provider` string[]
- `limit` integer
- `offset` integer

## Response `200`

Merged PnL summary and records for the user.

- PnlUserPnlResponse: Full merged PnL response for a user, across the requested wallets/providers.
  - `user_id` string, required
  - `summary` PnlSummary, required: Merged PnL summary across all providers/wallets supplied for a user.
    - `user_id` string, required
    - `total_realized_pnl` number, double, required
    - `total_fees` number, double, required
    - `total_cost_basis` number, double, required
    - `total_winning_positions` integer, required
    - `total_losing_positions` integer, required
    - `by_exchange` object, required: Keyed by provider name.
  - `records` PnlRecord[], required
    - `provider` string, required
    - `market_id` string, required
    - `timestamp` string, required: ISO-8601 timestamp string (empty string if the underlying record has no timestamp).
    - `realized_pnl` number, double, required
    - `fees` number, double, required
  - `filters_applied` object, required: Echoes the effective filters used to build this response (wallets, providers, etc.).
  - `total` integer: Count of merged records the aggregator returned before pagination slicing, bounded by the per-provider fetch ceiling (min(limit+offset, 1000)) — not an authoritative full-history count.
  - `has_more` boolean: True only when the aggregator definitively saw more records than this page returned.

## Other responses

- `400`: Missing/oversized `user_id`, an invalid wallet address, an invalid `provider` filter, or no wallet address supplied at all.
- `401`: No valid credential presented — missing/invalid API-key headers, or an invalid/expired session token.
- `403`: The `user_id` does not match the authenticated caller, the API key is missing the `position:read` scope, or platform API access is disabled.
- `422`: Request failed FastAPI parameter validation.
- `429`: Rate limit exceeded for this route's sliding window, keyed on the session `sub` when authenticated and on the trusted client IP otherwise. Honor `Retry-After`. A per-API-key data ceiling (set per credential) rejects with `{"detail": "API key data rate limit exceeded"}` instead of the `error` envelope below.
- `503`: Per-venue API-access flag could not be read or validated.

---

[API](https://skmtc.dev/kairos/apis/kairos-data-api.md) - [All operations](https://skmtc.dev/kairos/apis/kairos-data-api/llms.txt) - [OpenAPI document](https://skmtc.dev/kairos/apis/kairos-data-api/revisions/cf6b76079e38?raw)
