> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lumeus.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Wallet portfolio

> List every asset a wallet holds, with balances and what it can spend.

Build a wallet's portfolio view from one Assets API request. Holdings returns
XLM, every trustline, liquidity pool shares and recognized contract tokens, each
with its balance, spendable amount and token metadata.

Set these once in your terminal. The sample values are real Testnet data, so
the read examples return real results.

```bash theme={null}
# Your Testnet key from app.lumeus.dev (starts with lum_test_)
export LUMEUS_API_KEY='paste-your-lum_test_-key'

export ACTIVITY_URL='https://api-testnet.lumeus.dev'
export ASSETS_URL='https://assets-testnet.lumeus.dev'
export WEBHOOKS_URL='https://webhooks-testnet.lumeus.dev'
export RPC_URL='https://rpc-testnet.lumeus.dev'

# Sample Testnet values
export ACCOUNT='GAIH3ULLFQ4DGSECF2AR555KZ4KNDGEKN4AFI4SU2M7B43MGK3QJZNSR'
export ASSET='BTC:GAGVZMODPYFTULIVSY3RGFADIC5LF4LLSN37N4MFWGNZ57TYBXL2LQQX'
export CONTRACT='CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC'
export TX_HASH='1ae3b48681d8b27f31ee8f1893281e9a56fd7c45cf9909cf6a0599949079c2ce'
```

This recipe uses a Testnet wallet that holds several assets:

```bash theme={null}
export WALLET='GADH5BQZDLFX5WY5HL5X2C3WVH2HWTMC6IIP22VRFGUQZMVQSBFYD4VK'
```

## Fetch the portfolio

```bash curl theme={null}
curl --fail-with-body -G "$ASSETS_URL/accounts/$WALLET/holdings" \
  -H "Authorization: Bearer $LUMEUS_API_KEY" \
  --data-urlencode 'limit=200' \
  | jq -r '._embedded.records[]
      | [.symbol // .code, .balance, .spendable, .asset,
         (.flags | map(select(. == "impersonation_risk" or . == "likely_spam")) | join(","))] | @tsv'
```

```javascript JavaScript theme={null}
// Node 18+: save as portfolio.mjs and run `node portfolio.mjs`
const { ASSETS_URL, WALLET, LUMEUS_API_KEY } = process.env;
const headers = { Authorization: `Bearer ${LUMEUS_API_KEY}` };

const holdings = [];
let url = new URL(`/accounts/${WALLET}/holdings?limit=200`, ASSETS_URL);
while (url) {
  const response = await fetch(url, { headers });
  const page = await response.json();
  if (!response.ok) throw new Error(`${response.status} ${page.code}: ${page.error}`);
  holdings.push(...page._embedded.records);
  url = page._links.next ? new URL(page._links.next.href, ASSETS_URL) : null;
}

// Never show a flagged token as if it were the real one.
const RISKY = ['impersonation_risk', 'likely_spam'];
for (const h of holdings) {
  const warning = h.flags.some((f) => RISKY.includes(f)) ? `  warning: ${h.flags.join(',')}` : '';
  console.log((h.symbol ?? h.code ?? h.asset).padEnd(8), h.balance.padStart(20), h.spendable ?? '', warning);
}
```

```python Python theme={null}
# pip install requests; save as portfolio.py and run `python portfolio.py`
import os

import requests

base = os.environ["ASSETS_URL"]
headers = {"Authorization": f"Bearer {os.environ['LUMEUS_API_KEY']}"}

holdings = []
url = f"{base}/accounts/{os.environ['WALLET']}/holdings?limit=200"
while url:
    response = requests.get(url, headers=headers, timeout=60)
    page = response.json()
    if not response.ok:
        raise SystemExit(f"{response.status_code} {page['code']}: {page['error']}")
    holdings += page["_embedded"]["records"]
    url = base + page["_links"]["next"]["href"] if "next" in page["_links"] else None

# Never show a flagged token as if it were the real one.
RISKY = {"impersonation_risk", "likely_spam"}
for h in holdings:
    warning = f"  warning: {','.join(h['flags'])}" if RISKY & set(h["flags"]) else ""
    print(f"{h.get('symbol') or h.get('code') or h['asset']:<8} {h['balance']:>20} {h.get('spendable') or ''}{warning}")
```

Output for the sample wallet (8 holdings, trimmed):

```text theme={null}
BTC                 1.9800000 1.9800000
ETH                 0.9900000 0.9900000
...
USDC                4.9499000 4.9499000
USDT                2.9700000 2.9700000
XLM              9431.1207026 9426.6207026
```

One holding record, trimmed:

```json theme={null}
{
  "asset": "native",
  "kind": "native",
  "code": "XLM",
  "symbol": "XLM",
  "decimals": 7,
  "balance": "9431.1207026",
  "balance_raw": "94311207026",
  "spendable": "9426.6207026",
  "source": "ledger_entry",
  "verified": true,
  "last_modified_ledger": 5136449
}
```

## Read the result

| Field | Use it for |
| - | - |
| `balance` | The total held, as a decimal string. `balance_raw` is the integer in the smallest unit |
| `spendable` | What the wallet can send now. XLM: the balance minus the minimum balance and selling liabilities. Trustlines: the balance minus selling liabilities, or `0` when the trustline is not authorized. `null` for pool shares and contract tokens |
| `symbol`, `code`, `name`, `decimals` | Display metadata from the token registry |
| `verified` | `true` when read from a ledger entry or recognized token storage |
| `flags` | Registry warnings. Hide or clearly mark tokens flagged `impersonation_risk` (copies the code or name of a known token) or `likely_spam`, as the code above does |
| `completeness.complete` (top level) | `true` when every holding was read exactly. Pass `include=unverified` to also list custom tokens with an unrecognized storage layout; their balances come from simulating the token's `balance()` call (`source: simulation`, `verified: false`) |
| `account` (top level) | XLM reserve details: `minimum_balance`, `subentries`, sponsorships. `null` for an address that does not exist yet (never funded), which then has no holdings |

Add `kind=trustline` (or `native`, `pool_share`, `contract`) to show one kind,
or `min_balance=0.01` to hide dust. Several classic assets can share a code such
as `USDC`; show the issuer or the token's home domain next to the code so users
can tell them apart.

## Credits

On Mainnet, holdings cost 1 credit per started 100 records, so a wallet with up
to 100 holdings costs 1 credit (plus 1 per token simulated with
`include=unverified`). On Testnet the request is free and counts toward the
daily Testnet cap of 10,000. See [Limits and credits](/guides/limits.md).

## Next

* [Holdings](/assets-api/holdings.md): every filter and field.
* [Last payments of an account](/recipes/last-payments.md): recent movements for the same wallet.
* [Alert on balance changes](/recipes/balance-alerts.md): keep the view current with webhooks.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.