# How to Pull Portfolio Data From a Crypto API

> A practical walkthrough of fetching multi-chain wallet balances, protocol positions and transaction history from a crypto portfolio API, with worked examples.

- **URL:** https://octav.fi/blog/crypto-portfolio-api-guide
- **Published:** 2026-05-11
- **Author:** Octav Team — Portfolio Intelligence for Digital Assets
- **Topic:** API & Developers
- **Tags:** api, developers, integration
- **Source:** Octav, Practical guides on crypto NAV reporting, multi-chain portfolio management and digital asset APIs, from the team behind Octav.

---
Most teams that end up buying portfolio data start by trying to build it. The
build usually stalls at the same place: enumerating positions is easy on one
chain and miserable on fifteen.

## What a portfolio API actually has to return

A wallet address is not a portfolio. A useful response resolves an address into
every position it controls, priced and categorised:

| Field | Why it matters |
| --- | --- |
| `chain` | Positions must be attributable per network |
| `protocol` | An LP token is meaningless without its protocol context |
| `balance` | Raw units, so you can re-derive value yourself |
| `value` | Priced at a stated timestamp, not "now" |
| `category` | Wallet, lending, staking, LP, vesting |

The last two are what separate a portfolio API from a block explorer. A balance
without a timestamped price is not something you can put in a report.

## Fetching a portfolio

A single request should resolve an address across every supported chain:

```bash
curl -s https://api.octav.fi/v1/portfolio \
  -H "Authorization: Bearer $OCTAV_API_KEY" \
  -G --data-urlencode "addresses=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
```

Handling the response in TypeScript:

```ts
type Position = {
  chain: string;
  protocol: string | null;
  symbol: string;
  balance: string;
  value: number;
  category: "wallet" | "lending" | "staking" | "lp" | "vesting";
};

const res = await fetch(url, {
  headers: { Authorization: `Bearer ${process.env.OCTAV_API_KEY}` },
});

if (!res.ok) {
  throw new Error(`Portfolio request failed: ${res.status}`);
}

const { positions } = (await res.json()) as { positions: Position[] };

// Exposure by chain — the aggregate most teams want first.
const byChain = positions.reduce<Record<string, number>>((acc, p) => {
  acc[p.chain] = (acc[p.chain] ?? 0) + p.value;
  return acc;
}, {});
```

## Three mistakes that show up in production

- **Treating the response as live state.** Prices move. Store the timestamp
  alongside the value, or your reports will not reconcile with each other.
- **Summing LP tokens at face value.** An LP position's value is a function of
  the pool's composition, not of the token's own price.
- **Paginating naively.** Addresses with long histories return large result
  sets; handle the cursor rather than assuming one page.

## Where to go next

The [API documentation](https://docs.octav.fi/docs/quickstart) covers
authentication, rate limits and the full response schema.
