Skip to content

GET /v1/portfolio/pnl/:walletAddress

Return a wallet's portfolio value change over the last 24 hours — the USD value of its holdings now versus 24 hours ago, plus a per-asset breakdown. Useful for portfolio dashboards and "you're up/down today" tiles.

This is a value change, not trading PnL: deposits and withdrawals move the number too (a deposit reads as an increase, a withdrawal as a decrease). It answers "is my wallet worth more than yesterday?", not "how much did I make trading?". Cost-basis / realized-vs-unrealized accounting is not included.

Holdings are valued with live DEX prices (the same source as /v1/prices); the 24h-ago value reconstructs each asset's balance from the wallet's transaction activity in the window and prices it with the 24h-ago price. Only tracked mints (those returned by /v1/balances) are valued.

Caching is standard HTTP: requests authenticated with ?apiKey= in the URL return Cache-Control: public, max-age=15, s-maxage=15 (max-age=5 on the value variant), so browsers and the edge serve warm repeats without re-paying the scan — cache entries are keyed by the full URL, i.e. per key and per wallet. Requests authenticated with the x-api-key header always get no-store: server-side integrations see fully fresh data on every call. Concurrent in-flight requests for the same wallet are coalesced server-side into one computation.

Request

http
GET /v1/portfolio/pnl/:walletAddress
x-api-key: YOUR_API_KEY

Path parameters

FieldTypeDescription
walletAddressstringBase58 wallet pubkey (32-44 chars)

Example

bash
curl https://api.venum.dev/v1/portfolio/pnl/Fgz6qLgeibJPNkNdALqvrYQ9FauLanTKs15miEsAiHqh \
  -H 'x-api-key: YOUR_API_KEY'

Batch: GET /v1/portfolio/pnl?wallets=a,b,c

Fetch up to 20 wallets in one request. The server fans the scans out concurrently, so a multi-wallet page loads in roughly the time of its slowest wallet instead of the sum of all of them.

bash
curl "https://api.venum.dev/v1/portfolio/pnl?wallets=WALLET_A,WALLET_B" \
  -H 'x-api-key: YOUR_API_KEY'

Response: { "results": { "<wallet>": <pnl object | { "status": "unavailable" }> }, "count": N, "fetchedAt": ... }. A wallet that temporarily fails does not fail the batch — it comes back as { "status": "unavailable" } and can be retried individually.

Rate accounting: a batch of N wallets consumes N portfolio requests from your per-minute quota (see below) — batching saves round-trips, not quota.

Streaming batch: GET /v1/stream/portfolio/pnl?wallets=a,b,c

The SSE variant of the batch — identical computation, quota accounting, and 20-wallet cap, but each wallet's result is pushed the moment it completes, so a wallet-list UI renders rows progressively instead of waiting for the slowest wallet.

bash
curl -N "https://api.venum.dev/v1/stream/portfolio/pnl?wallets=WALLET_A,WALLET_B" \
  -H 'x-api-key: YOUR_API_KEY'
Eventdata payload
ready{"ts": <ms>, "wallets": N}
pnlone full pnl object (same shape as the REST endpoints), per wallet, in completion order
wallet-error{"wallet": "...", "status": "unavailable"} — one failing wallet never ends the stream
done{"ts": <ms>, "ok": N, "failed": M} — then the stream closes

Counts against the SSE streams connection limit while open (the connection is short-lived — it closes after done).

Lean variant: GET /v1/portfolio/value/:walletAddress

Current total value only — no 24h reconstruction, so it responds in a fraction of the pnl latency. Use this for list views that only need a total.

json
{ "wallet": "...", "valueNowUsd": 12500.5, "stats": { "elapsedMs": 140 }, "fetchedAt": 1733587200000 }

Response

200 OK

json
{
  "wallet": "Fgz6qLgeibJPNkNdALqvrYQ9FauLanTKs15miEsAiHqh",
  "valueNowUsd": 12500.5,
  "value24hAgoUsd": 12000.25,
  "changeUsd": 500.25,
  "changePct": 4.17,
  "assets": [
    {
      "mint": "So11111111111111111111111111111111111111112",
      "symbol": "SOL",
      "decimals": 9,
      "balanceNow": 100.5,
      "balance24hAgo": 100.0,
      "priceNow": 80.31,
      "price24hAgo": 78.5,
      "valueNowUsd": 8071.31,
      "value24hAgoUsd": 7850.0,
      "changeUsd": 221.31
    }
  ],
  "stats": {
    "windowHours": 24,
    "rpcPosts": 12,
    "signaturesScanned": 42,
    "txHydrated": 42,
    "truncated": false,
    "elapsedMs": 3250
  },
  "fetchedAt": 1733587200000
}

Top-level fields

FieldTypeDescription
walletstringThe queried wallet
valueNowUsdnumberTotal USD value of current holdings
value24hAgoUsdnumberTotal USD value of holdings as they stood 24 h ago
changeUsdnumbervalueNowUsd − value24hAgoUsd (signed)
changePctnumber | nullPercent change vs 24 h ago; null when the 24h-ago value was zero
assetsarrayPer-asset breakdown, sorted by valueNowUsd descending
fetchedAtnumberUNIX millisecond timestamp the computation started

Asset object

FieldTypeDescription
mintstringBase58 mint address
symbolstringTracked symbol (e.g. "SOL", "USDC")
decimalsnumberToken decimals
balanceNownumberCurrent balance (human-readable)
balance24hAgonumberReconstructed balance 24 h ago (balanceNow − net flow)
priceNownumber | nullCurrent USD price; null if the asset can't be priced
price24hAgonumber | nullUSD price 24 h ago; falls back to priceNow when no 24h % data
valueNowUsdnumberbalanceNow × priceNow
value24hAgoUsdnumberbalance24hAgo × price24hAgo
changeUsdnumbervalueNowUsd − value24hAgoUsd

Stats object

FieldTypeDescription
windowHoursnumberThe lookback window (24)
rpcPostsnumberRPC requests issued for the transaction scan
signaturesScannednumberSignatures examined inside the window
txHydratednumberTransactions fully fetched to compute balance deltas
truncatedbooleantrue if the wallet had more window activity than the scan cap — the 24h-ago figure is then a partial reconstruction
elapsedMsnumberServer compute time

Notes:

  • An asset held 24 h ago but fully sold during the window (zero balance now) still appears, contributing to value24hAgoUsd.
  • Assets with no priceable market are included with priceNow: null and contribute 0 to the USD totals.
  • When an asset has no 24h price-change data, its price24hAgo falls back to the current price, so its contribution reflects only the change in quantity, not a fabricated price move.
  • A truncated: true result means a very active wallet exceeded the scan cap; treat value24hAgoUsd / changeUsd as approximate.

Errors

StatusDescription
400Invalid wallet address (not a valid pubkey)
401Missing or invalid API key
429Rate limit exceeded
503Temporarily unavailable (upstream RPC); retry after the Retry-After header

Auth

Requires API key.

Rate Limit

Dedicated limits (per minute) — lower than /v1/balances because the 24h transaction-history scan makes this endpoint significantly heavier:

TierLimit
anonymous0
free5
starter30
growth75
pro150