---
title: "Holdings and PnL on every chain"
method: GET
path: "/portfolio/{wallet}"
tags: ["Portfolio"]
---

# Holdings and PnL on every chain

`GET /portfolio/{wallet}`

## Request

- Base URL: `https://api.getchord.xyz/v1`
- URL: `https://api.getchord.xyz/v1/portfolio/{wallet}`
- Auth: none declared

What one wallet holds on Chord's eight EVM chains, and on Solana when you pass its Solana address, with its PnL over the last 24 hours, 7 days and 30 days. One rule for every chain, per token and window: the USD from its sales minus the USD of its buys inside the window, plus what is held now at today's price, minus what was held at the window's start at that hour's close. No cost basis is involved, and transfers are neutral: a token that arrived by transfer only adds its price move, and a quantity bought in the window that left with no sale on the tape (sent, bridged out) counts as sold at the window's average buy price, neither a gain nor a loss. Stablecoins are left out of PnL and counted in `cashUsd`.

**Use it for**: a wallet or portfolio page, "how did this wallet do this week", an agent sizing a trade against what the wallet already holds.

**Notes**
- **Coverage first.** A window is `complete` only when the chain's swap tape covers all of it. When it does not (the tape starts later, a hole in the history), `usd`, `pct` and `fromTs` are null: show a dash, never a partial number as a whole one. A total window is complete only when every chain in the answer is, chains that could not be read included. BNB Chain does not record swaps under $100: its windows still count as complete, and `coverage.ingestFloorUsd` says so.
- **Holdings** = the chain's token list plus every token the wallet swapped in the last 30 days, read on-chain (Solana: its 50 largest holdings). A token with no price Chord trusts (no measured liquidity, not on the chain's board) has `priceUsd: null`, comes last and is in no total. Priced holdings under $0.01 are folded into `dust`.
- **Each chain answers on its own deadline.** One that cannot be read is `status: "unavailable"` with a `reason` inside the 200 and is left out of the totals' value; `partial` = the balances were read, part of the rest was not.
- **Times are milliseconds** in this answer (`asOf`, `fromTs`, `tapeFromTs`), unlike most of this API.
- **Cache**: kept 45 seconds per wallet, Solana address and chain set (10 seconds when a chain could not be read); a copy up to 10 minutes old may be served once while a refresh runs (`asOf` says when it was computed). `fresh=1` recomputes, only for a request with an `X-API-Key` or an Ask session; otherwise it is ignored.
- **Without an API key (or an Ask session)**: a cached answer is served to anyone. One IP may start at most 10 new reads every 10 minutes (this route and `/activity` together), and every such caller together a limited number a minute; past either the answer is 429 `RATE_LIMITED` or 503 `BUSY` with `Retry-After`, and nothing is read. Send an `X-API-Key` for more.

## Path parameters

- `wallet` string, required: EVM address, hex, case-insensitive.

## Query parameters

- `sol` string: Solana address, base58, case-sensitive. Never lower-case it.
- `chains` string
- `fresh` '1'

## Response `200`

- Portfolio: One wallet on every chain in scope.
  - `wallet` string: EVM address, lower-case.
  - `sol` string, nullable: The Solana address as given, or null.
  - `asOf` integer: Unix time, milliseconds. When this answer was computed. A cached answer keeps its own `asOf`.
  - `totals` object
    - `valueUsd` number: Sum of `valueUsd` over the chains that were read.
    - `cashUsd` number
    - `pnl` object: Complete only when every chain in the answer is complete for that window, chains that could not be read included. `pct` is the sum of the chains' USD over the sum of their bases.
      - `24h` PortfolioWindow, required: PnL over one window by the window rule (see `GET /portfolio/{wallet}`).
        - `usd` number, nullable, required: PnL over the window, USD. Null when the window is not complete.
        - `pct` number, nullable, required: Percent (1.5 = +1.5 %) of the base: the value held at the window's start plus the USD bought in it. Null when that base is under one cent, or when the window is not complete.
        - `complete` boolean, required: The swap tape covers the whole window with no known hole. False = `usd`, `pct` and `fromTs` are null: show a dash, never a number.
        - `fromTs` integer, nullable, required: Unix time, milliseconds. Where the number starts: the window's start, aligned down to the hour. On `totals`: the latest start of the chains.
      - `7d` PortfolioWindow, required: PnL over one window by the window rule (see `GET /portfolio/{wallet}`).
        - `usd` number, nullable, required: PnL over the window, USD. Null when the window is not complete.
        - `pct` number, nullable, required: Percent (1.5 = +1.5 %) of the base: the value held at the window's start plus the USD bought in it. Null when that base is under one cent, or when the window is not complete.
        - `complete` boolean, required: The swap tape covers the whole window with no known hole. False = `usd`, `pct` and `fromTs` are null: show a dash, never a number.
        - `fromTs` integer, nullable, required: Unix time, milliseconds. Where the number starts: the window's start, aligned down to the hour. On `totals`: the latest start of the chains.
      - `30d` PortfolioWindow, required: PnL over one window by the window rule (see `GET /portfolio/{wallet}`).
        - `usd` number, nullable, required: PnL over the window, USD. Null when the window is not complete.
        - `pct` number, nullable, required: Percent (1.5 = +1.5 %) of the base: the value held at the window's start plus the USD bought in it. Null when that base is under one cent, or when the window is not complete.
        - `complete` boolean, required: The swap tape covers the whole window with no known hole. False = `usd`, `pct` and `fromTs` are null: show a dash, never a number.
        - `fromTs` integer, nullable, required: Unix time, milliseconds. Where the number starts: the window's start, aligned down to the hour. On `totals`: the latest start of the chains.
  - `chains` PortfolioChain[]: In registry order, Solana last.
    - `chain` union: EVM chain id, or `solana`.
      - integer
      - string
    - `key` string: Registry key (`monad`, `base`, …) or `solana`.
    - `name` string
    - `status` 'ok' | 'partial' | 'unavailable': `ok` = read. `partial` = balances read, part of the rest (prices, the tape, some tokens) not, and `reason` says which. `unavailable` = not read at all: in no total, every window without a number.
    - `reason` string: Present unless `ok`. One plain sentence for a person.
    - `valueUsd` number: Sum of the priced holdings, small balances included.
    - `cashUsd` number: The part of `valueUsd` held in stablecoins.
    - `pnl` object: The chain's PnL per window.
      - `24h` PortfolioWindow, required: PnL over one window by the window rule (see `GET /portfolio/{wallet}`).
        - `usd` number, nullable, required: PnL over the window, USD. Null when the window is not complete.
        - `pct` number, nullable, required: Percent (1.5 = +1.5 %) of the base: the value held at the window's start plus the USD bought in it. Null when that base is under one cent, or when the window is not complete.
        - `complete` boolean, required: The swap tape covers the whole window with no known hole. False = `usd`, `pct` and `fromTs` are null: show a dash, never a number.
        - `fromTs` integer, nullable, required: Unix time, milliseconds. Where the number starts: the window's start, aligned down to the hour. On `totals`: the latest start of the chains.
      - `7d` PortfolioWindow, required: PnL over one window by the window rule (see `GET /portfolio/{wallet}`).
        - `usd` number, nullable, required: PnL over the window, USD. Null when the window is not complete.
        - `pct` number, nullable, required: Percent (1.5 = +1.5 %) of the base: the value held at the window's start plus the USD bought in it. Null when that base is under one cent, or when the window is not complete.
        - `complete` boolean, required: The swap tape covers the whole window with no known hole. False = `usd`, `pct` and `fromTs` are null: show a dash, never a number.
        - `fromTs` integer, nullable, required: Unix time, milliseconds. Where the number starts: the window's start, aligned down to the hour. On `totals`: the latest start of the chains.
      - `30d` PortfolioWindow, required: PnL over one window by the window rule (see `GET /portfolio/{wallet}`).
        - `usd` number, nullable, required: PnL over the window, USD. Null when the window is not complete.
        - `pct` number, nullable, required: Percent (1.5 = +1.5 %) of the base: the value held at the window's start plus the USD bought in it. Null when that base is under one cent, or when the window is not complete.
        - `complete` boolean, required: The swap tape covers the whole window with no known hole. False = `usd`, `pct` and `fromTs` are null: show a dash, never a number.
        - `fromTs` integer, nullable, required: Unix time, milliseconds. Where the number starts: the window's start, aligned down to the hour. On `totals`: the latest start of the chains.
    - `coverage` object
      - `tapeFromTs` integer, nullable: Unix time, milliseconds. First moment the chain's swap tape covers without a hole for this wallet. Null = no tape here.
      - `source` 'tape' | 'tape+lake' | 'box': `tape` = Chord's raw tape of the chain; `tape+lake` = older days joined from Chord's history lake; `box` = Chord's Solana index.
      - `ingestFloorUsd` number, nullable: Present on a chain whose tape does not record swaps under this many USD (BNB Chain: 100). Disclosed, not a hole: that chain's windows still count as complete, and a swap under the floor reads only as a price move or a transfer. Say so next to the numbers.
    - `holdings` PortfolioHolding[]: Priced holdings worth at least $0.01, largest first, then the unpriced ones.
      - `address` string, nullable: EVM token, lower-case. Null for the chain's native coin and on a Solana row (which carries `mint`).
      - `mint` string: Solana rows only: the mint, case kept.
      - `symbol` string
      - `name` string, nullable
      - `decimals` integer: From the chain's list or the chain itself, never assumed.
      - `logo` string, nullable
      - `native` boolean: The chain's own coin (ETH, MON, AVAX, BNB, USDC on Arc, SOL).
      - `stable` boolean: A dollar stablecoin: counted in `cashUsd`, left out of PnL.
      - `amount` string: Balance in whole tokens, as a decimal string.
      - `priceUsd` number, nullable: Null = no price Chord trusts (no measured liquidity, not on the chain's board): listed last, in no total.
      - `valueUsd` number, nullable: amount × priceUsd.
      - `change24hPct` number, nullable: Percent.
      - `pnl` object: This token's part of each window's PnL, USD. Null = left out (a stablecoin, no price, no start price) or the chain's window is not complete.
        - `24h` number, nullable, required
        - `7d` number, nullable, required
        - `30d` number, nullable, required
    - `dust` object: Priced holdings under $0.01, counted here and not listed.
      - `count` integer
      - `valueUsd` number
    - `unpriced` integer: Holdings with `priceUsd: null` (listed at the end of `holdings`).

## Other responses

- `400`: `INVALID_ADDRESS` (the wallet), `INVALID_SOL_ADDRESS`, `BAD_CHAINS`, or `SOL_REQUIRED` (`chains` names `solana` without `sol`).
- `429`: `RATE_LIMITED`: without an API key, this IP started its 10 new portfolio reads of the last 10 minutes (`Retry-After`); a portfolio read in the last 10 minutes still answers. Or `RATE_LIMIT_EXCEEDED`, the per-minute limit of every route.
- `503`: `BUSY`: too many new portfolio reads are running for callers without a key right now (`Retry-After`). `UNAVAILABLE`: the answer itself could not be built. A chain that cannot be read is not a 503: it comes back `status: "unavailable"` inside a 200.

---

[API](https://skmtc.dev/getchord/apis/chord-api.md) · [All operations](https://skmtc.dev/getchord/apis/chord-api/llms.txt) · [OpenAPI document](https://skmtc.dev/getchord/apis/chord-api/revisions/d3bc4640fea0?raw)
