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

# Migrate from Zerion

> A 1:1 mapping from the Zerion API to the Octav API

A drop-in mapping from the **Zerion API** to Octav. The biggest wins:

* **A flat, already-priced object.** Zerion returns JSON:API (`data[].attributes`, related entities via `relationships`) that you traverse and stitch together. Octav returns a flat object where every asset is already USD-priced from a single [`GET /v1/portfolio`](/api/endpoints/portfolio).
* **Compact responses.** Zerion payloads can grow very large. Octav responses stay compact.
* **Decode once, no double-counting.** On LST and vault wallets Zerion can surface a receipt token as a `wallet` position *and* again as its decoded `deposit`/`staked` position, so a naive sum counts it twice. Octav decodes each asset once.
* **Deeper protocol decoding.** Perps, options, and complex positions come back fully decoded, and **P\&L and cost basis** ship in the same response.

<Info>
  **Get your API key** at [data.octav.fi](https://data.octav.fi/). Base URL: `https://api.octav.fi/v1`.
</Info>

## Endpoint mapping

| Use case                   | Zerion                                                            | Octav                                                                       |
| -------------------------- | ----------------------------------------------------------------- | --------------------------------------------------------------------------- |
| Wallet tokens (all chains) | `GET /wallets/{address}/positions?filter[positions]=only_simple`  | [`GET /v1/portfolio`](/api/endpoints/portfolio) → `assetByProtocols.wallet` |
| DeFi positions             | `GET /wallets/{address}/positions?filter[positions]=only_complex` | same `GET /v1/portfolio` → `assetByProtocols.<protocol>`                    |
| Net worth                  | `GET /wallets/{address}/portfolio`                                | same response → `networth` + `chains`                                       |
| Transaction history        | `GET /wallets/{address}/transactions`                             | [`GET /v1/transactions`](/api/endpoints/transactions)                       |
| Token metadata / price     | `fungible_info` on each position                                  | inline on every asset (`price`, `value`, `decimal`)                         |

<Tip>
  Everything except history comes from a **single** `GET /v1/portfolio` call. There is no `only_simple` / `only_complex` filter to run twice and merge.
</Tip>

## Authentication

Zerion uses HTTP Basic auth: your API key followed by a colon, base64-encoded, in an `Authorization: Basic` header. Octav uses a standard `Authorization: Bearer` token.

<CodeGroup>
  ```bash Zerion theme={null}
  # Basic auth: base64("YOUR_ZERION_KEY:") — note the trailing colon
  curl "https://api.zerion.io/v1/wallets/0x6426af179aabebe47666f345d69fd9079673f6cd/positions?filter[positions]=only_simple&sort=-value" \
    -H "Authorization: Basic $(printf 'YOUR_ZERION_KEY:' | base64)"
  ```

  ```bash Octav theme={null}
  curl "https://api.octav.fi/v1/portfolio?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd" \
    -H "Authorization: Bearer YOUR_OCTAV_KEY"
  ```
</CodeGroup>

## Wallet token balances

Zerion's `only_simple` positions return a JSON:API array where each token lives under `data[].attributes`. In Octav, wallet tokens live under the `wallet` protocol, grouped by chain:

```
assetByProtocols.wallet.chains.<chain>.protocolPositions.WALLET.assets[]
```

<CodeGroup>
  ```javascript JavaScript theme={null}
  const OCTAV_KEY = process.env.OCTAV_API_KEY;
  const address = "0x6426af179aabebe47666f345d69fd9079673f6cd";

  const res = await fetch(
    `https://api.octav.fi/v1/portfolio?addresses=${address}&includeImages=true`,
    { headers: { Authorization: `Bearer ${OCTAV_KEY}` } }
  );
  const [portfolio] = await res.json(); // /v1/portfolio returns one entry per address

  // Flatten wallet tokens across every chain
  const wallet = portfolio.assetByProtocols.wallet;
  const tokens = Object.values(wallet.chains).flatMap((chain) =>
    Object.values(chain.protocolPositions).flatMap((pos) => pos.assets)
  );

  tokens.forEach((t) =>
    console.log(`${t.symbol} on ${t.chainKey}: ${t.balance} ($${t.value})`)
  );
  ```

  ```python Python theme={null}
  import os
  import requests

  OCTAV_KEY = os.environ["OCTAV_API_KEY"]
  address = "0x6426af179aabebe47666f345d69fd9079673f6cd"
  res = requests.get(
      "https://api.octav.fi/v1/portfolio",
      params={"addresses": address, "includeImages": True},
      headers={"Authorization": f"Bearer {OCTAV_KEY}"},
  )
  portfolio = res.json()[0]  # one entry per address

  wallet = portfolio["assetByProtocols"]["wallet"]
  for chain in wallet["chains"].values():
      for pos in chain["protocolPositions"].values():
          for t in pos["assets"]:
              print(f"{t['symbol']} on {t['chainKey']}: {t['balance']} (${t['value']})")
  ```

  ```bash cURL theme={null}
  curl "https://api.octav.fi/v1/portfolio?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd&includeImages=true" \
    -H "Authorization: Bearer YOUR_OCTAV_KEY"
  ```
</CodeGroup>

**Field mapping**

| Zerion (`only_simple` position)                       | Octav (`…assets[]`) | Notes                                                                          |
| ----------------------------------------------------- | ------------------- | ------------------------------------------------------------------------------ |
| `attributes.fungible_info.symbol`                     | `symbol`            |                                                                                |
| `attributes.fungible_info.name`                       | `name`              |                                                                                |
| `attributes.quantity.float`                           | `balance`           | Human-readable in both.                                                        |
| `attributes.price`                                    | `price`             | USD, precomputed.                                                              |
| `attributes.value`                                    | `value`             | Octav's `value` is always populated; Zerion's can be `null`.                   |
| `attributes.fungible_info.implementations[].address`  | `contract`          | Token address. Native tokens use the zero address in Octav.                    |
| `attributes.fungible_info.implementations[].decimals` | `decimal`           |                                                                                |
| `relationships.chain.data.id`                         | `chainKey`          | e.g. `ethereum`. See [supported chains](/api/reference/supported-chains).      |
| `attributes.flags.displayable`                        | —                   | Octav applies its own spam filtering; there is no `displayable` flag to check. |

## Net worth and per-chain breakdown

Zerion's `/portfolio` endpoint maps directly onto the top-level `networth` and `chains` fields returned by the same portfolio call.

| Zerion (`/portfolio`)                                     | Octav                            | Notes                                                                                   |
| --------------------------------------------------------- | -------------------------------- | --------------------------------------------------------------------------------------- |
| `data.attributes.total.positions`                         | `networth`                       | Total net worth in USD.                                                                 |
| `data.attributes.positions_distribution_by_chain.<chain>` | `chains.<chain>.value`           | Per-chain total.                                                                        |
| —                                                         | `chains.<chain>.key` / `chainId` | Chain identifier + numeric ID (Octav adds this).                                        |
| —                                                         | `chains.<chain>.valuePercentile` | Share of net worth per chain (Octav adds this).                                         |
| `data.attributes.changes.absolute_1d` (24h change)        | —                                | No direct equivalent; Octav returns `openPnl` / `closedPnl` / `totalCostBasis` instead. |

```javascript JavaScript theme={null}
console.log(`Net worth: $${portfolio.networth}`);
Object.values(portfolio.chains).forEach((c) =>
  console.log(`${c.name}: $${c.value} (${c.valuePercentile}%)`)
);
```

## DeFi positions

Zerion returns decoded positions under `only_complex`, each with a `position_type` (`deposit`, `loan`, `staked`, `reward`, `locked`). Octav returns the same money under `assetByProtocols`, keyed by protocol, then chain, then position type (`LENDING`, `LIQUIDITYPOOL`, `STAKED`, `FARMING`, …).

<CodeGroup>
  ```javascript JavaScript theme={null}
  // Every protocol except the "wallet" bucket is a DeFi position
  const defi = Object.entries(portfolio.assetByProtocols).filter(
    ([key]) => key !== "wallet"
  );

  defi.forEach(([key, protocol]) => {
    console.log(`${protocol.name}: $${protocol.value}`);
    Object.values(protocol.chains).forEach((chain) => {
      Object.entries(chain.protocolPositions).forEach(([type, pos]) => {
        console.log(`  ${type}: $${pos.totalValue}`);
        pos.assets.forEach((a) => console.log(`    ${a.symbol}: $${a.value}`));
      });
    });
  });
  ```

  ```python Python theme={null}
  for key, protocol in portfolio["assetByProtocols"].items():
      if key == "wallet":
          continue
      print(f"{protocol['name']}: ${protocol['value']}")
      for chain in protocol["chains"].values():
          for ptype, pos in chain["protocolPositions"].items():
              print(f"  {ptype}: ${pos['totalValue']}")
  ```
</CodeGroup>

**Field mapping**

| Zerion (`only_complex` position)       | Octav                                   | Notes                                  |
| -------------------------------------- | --------------------------------------- | -------------------------------------- |
| `attributes.protocol`                  | `assetByProtocols.<key>.name`           | Keyed by protocol.                     |
| `attributes.position_type` = `deposit` | `assets[]` (supplied)                   |                                        |
| `attributes.position_type` = `loan`    | `assets[]` (borrowed)                   | Debt is decoded, not netted away.      |
| `attributes.position_type` = `staked`  | `assets[]` (staked)                     |                                        |
| `attributes.position_type` = `reward`  | `assets[]` (rewards)                    |                                        |
| `attributes.value`                     | `protocolPositions.<TYPE>.totalValue`   | Contributes to the net position value. |
| `relationships.chain.data.id`          | `assetByProtocols.<key>.chains.<chain>` |                                        |

<Tip>
  On LST and vault wallets, Zerion can list a receipt token twice — once as a `wallet` position and again as its decoded `deposit`/`staked` position. Octav decodes each asset once, so summing `assetByProtocols` never double-counts.
</Tip>

## Transaction history

Swap `GET /wallets/{address}/transactions` for [`GET /v1/transactions`](/api/endpoints/transactions).

<CodeGroup>
  ```bash Zerion theme={null}
  curl "https://api.zerion.io/v1/wallets/0x6426af179aabebe47666f345d69fd9079673f6cd/transactions" \
    -H "Authorization: Basic $(printf 'YOUR_ZERION_KEY:' | base64)"
  ```

  ```bash Octav theme={null}
  curl "https://api.octav.fi/v1/transactions?addresses=0x6426af179aabebe47666f345d69fd9079673f6cd" \
    -H "Authorization: Bearer YOUR_OCTAV_KEY"
  ```
</CodeGroup>

Octav categorizes each transaction (swap, deposit, stake, bridge, …) and prices transfers in USD. See the [transaction types reference](/api/reference/transaction-types).

## Key differences

* **Response shape.** Zerion returns JSON:API — you walk `data[].attributes` and resolve related entities through `relationships`. Octav returns a flat object; no JSON:API traversal.
* **Payload size.** Zerion responses can be very large. Octav responses stay compact.
* **Values.** Zerion's `attributes.value` can be `null`; Octav returns a populated `value` (and `price`) on every asset.
* **Decode once.** Zerion may surface an LST/vault receipt token as both a `wallet` and a `deposit`/`staked` position. Octav decodes each asset once, so naive sums don't double-count.
* **Grouping.** Zerion splits `only_simple` vs `only_complex` and groups by `position_type`. Octav groups `assetByProtocols` → `chains` → `protocolPositions` → `assets[]`, with a dedicated `wallet` bucket for loose tokens.
* **Chains.** Both cover EVM **and** Solana. Octav serves both from one flat response and shape.
* **Billing.** Octav charges **1 credit per call** (see [pricing](/api/pricing)).
* **P\&L.** Octav adds `openPnl`, `closedPnl`, and `totalCostBasis` at no extra call.
* **NFTs.** Octav's `/portfolio` focuses on fungible assets and DeFi positions; it does not enumerate NFTs the way Zerion's `/nft-positions` does.

## Need help migrating?

<CardGroup cols={2}>
  <Card title="Join our Discord" icon="discord" href="https://discord.com/invite/qvcknAa73A">
    Share your Zerion response shape and we'll map it.
  </Card>

  <Card title="Portfolio endpoint" icon="chart-pie" href="/api/endpoints/portfolio">
    Full reference for the endpoint you'll be calling.
  </Card>
</CardGroup>
