> ## 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.

# Portfolio

> Retrieve portfolio holdings across wallets and DeFi protocols

Get comprehensive portfolio data for a blockchain address including assets, protocol positions, and net worth across multiple chains.

<Note>
  **Interactive Playground:** Test this endpoint in the [API Playground](/api-reference/portfolio). Get your API key at [data.octav.fi](https://data.octav.fi/)
</Note>

<Info>
  **Cost:** 1 credit per call
</Info>

***

## Endpoint

<CodeGroup>
  ```bash Request theme={null}
  GET https://api.octav.fi/v1/portfolio
  ```

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

***

## Parameters

<ParamField query="addresses" type="string" required>
  EVM or SOL wallet address to retrieve portfolio data for

  ```
  addresses=0x6426af179aabebe47666f345d69fd9079673f6cd
  ```
</ParamField>

<ParamField query="includeImages" type="boolean" default="false">
  Include image URLs for chains, assets, and protocols

  Useful for displaying logos in your application UI
</ParamField>

<ParamField query="includeExplorerUrls" type="boolean" default="false">
  Include blockchain explorer URLs for assets and transactions

  Links to Etherscan, Arbiscan, etc. for easy navigation
</ParamField>

<ParamField query="waitForSync" type="boolean" default="false">
  Wait for fresh data if cache is stale

  * `false`: Return cached data immediately (recommended)
  * `true`: Wait for sync if data is older than 1 minute
</ParamField>

***

## Response

### Portfolio Object

<ResponseField name="address" type="string">
  The wallet address
</ResponseField>

<ResponseField name="networth" type="string">
  Total portfolio net worth in USD
</ResponseField>

<ResponseField name="cashBalance" type="string">
  Available cash balance
</ResponseField>

<ResponseField name="dailyIncome" type="string">
  Income generated today
</ResponseField>

<ResponseField name="dailyExpense" type="string">
  Expenses incurred today
</ResponseField>

<ResponseField name="fees" type="string">
  Total fees in native asset
</ResponseField>

<ResponseField name="feesFiat" type="string">
  Total fees in USD
</ResponseField>

<ResponseField name="lastUpdated" type="string">
  Last sync timestamp (milliseconds since epoch)
</ResponseField>

<ResponseField name="openPnl" type="string">
  Unrealized profit/loss (if available, otherwise "N/A")
</ResponseField>

<ResponseField name="closedPnl" type="string">
  Realized profit/loss (if available, otherwise "N/A")
</ResponseField>

<ResponseField name="totalCostBasis" type="string">
  Total cost basis of holdings (if available, otherwise "N/A")
</ResponseField>

<ResponseField name="assetByProtocols" type="object">
  Assets organized by protocol (wallet, lending, staking, etc.), keyed by protocol identifier.

  Each protocol contains:

  * `key`: Protocol identifier
  * `name`: Protocol display name
  * `value`: Total value in USD
  * `chains`: Object keyed by chain identifier. Each chain holds a `protocolPositions` object whose entries contain the `assets[]` array.

  Asset path: `assetByProtocols.<protocol>.chains.<chain>.protocolPositions.<POSITION>.assets[]`

  Each asset contains `symbol`, `name`, `balance`, `price`, `value`, `decimal`, `contract`, `chainKey`, and `chainContract`. The on-chain token address is `contract` (native assets use the zero address); `chainContract` is the `<chain>:<contract>` composite key.
</ResponseField>

<ResponseField name="chains" type="object">
  Per-chain totals, keyed by chain identifier.

  Each chain contains:

  * `key`: Chain identifier (e.g., "ethereum", "arbitrum")
  * `name`: Chain display name
  * `chainId`: Numeric chain ID
  * `value`: Total value on this chain
  * `valuePercentile`: Share of total net worth on this chain
  * `totalCostBasis`, `totalClosedPnl`, `totalOpenPnl`: Cost basis and P\&L (or "N/A")
</ResponseField>

***

## Example Request

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

  ```javascript JavaScript theme={null}
  const address = '0x6426af179aabebe47666f345d69fd9079673f6cd';

  const response = await fetch(
    `https://api.octav.fi/v1/portfolio?addresses=${address}&includeImages=true`,
    {
      headers: {
        'Authorization': `Bearer ${apiKey}`
      }
    }
  );

  const portfolio = await response.json();
  console.log(`Net Worth: $${portfolio.networth}`);
  ```

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

  address = '0x6426af179aabebe47666f345d69fd9079673f6cd'

  response = requests.get(
      'https://api.octav.fi/v1/portfolio',
      params={
          'addresses': address,
          'includeImages': True
      },
      headers={'Authorization': f'Bearer {api_key}'}
  )

  portfolio = response.json()
  print(f"Net Worth: ${portfolio['networth']}")
  ```

  ```typescript TypeScript theme={null}
  interface Asset {
    balance: string;
    symbol: string;
    name: string;
    price: string;
    value: string;
    decimal: string;
    contract: string;
    chainKey: string;
    chainContract: string;
  }

  interface ProtocolPosition {
    name: string;
    assets: Asset[];
    totalValue: string;
  }

  interface ProtocolChain {
    key: string;
    name: string;
    value: string;
    protocolPositions: Record<string, ProtocolPosition>;
  }

  interface Protocol {
    key: string;
    name: string;
    value: string;
    chains: Record<string, ProtocolChain>;
  }

  interface Portfolio {
    address: string;
    networth: string;
    cashBalance: string;
    lastUpdated: string;
    assetByProtocols: Record<string, Protocol>;
    chains: Record<string, any>;
  }

  const address = '0x6426af179aabebe47666f345d69fd9079673f6cd';

  const response = await fetch(
    `https://api.octav.fi/v1/portfolio?addresses=${address}&includeImages=true`,
    {
      headers: {
        'Authorization': `Bearer ${apiKey}`
      }
    }
  );

  const portfolio: Portfolio = await response.json();
  console.log(`Net Worth: $${portfolio.networth}`);
  ```
</CodeGroup>

***

## Example Response

<Accordion title="View Full Response" icon="code">
  ```json theme={null}
  {
    "address": "0x6426af179aabebe47666f345d69fd9079673f6cd",
    "cashBalance": "0",
    "lastUpdated": "1715173392020",
    "networth": "25123.45",
    "assetByProtocols": {
      "wallet": {
        "name": "Wallet",
        "key": "wallet",
        "value": "25123.45",
        "chains": {
          "ethereum": {
            "name": "Ethereum",
            "key": "ethereum",
            "value": "24962.30",
            "protocolPositions": {
              "WALLET": {
                "name": "wallet",
                "assets": [
                  {
                    "balance": "5.5",
                    "symbol": "eth",
                    "name": "ethereum",
                    "price": "3200.50",
                    "value": "17602.75",
                    "decimal": "18",
                    "contract": "0x0000000000000000000000000000000000000000",
                    "chainKey": "ethereum",
                    "chainContract": "ethereum:0x0000000000000000000000000000000000000000"
                  },
                  {
                    "balance": "7359.55",
                    "symbol": "usdc",
                    "name": "usd coin",
                    "price": "1.00",
                    "value": "7359.55",
                    "decimal": "6",
                    "contract": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
                    "chainKey": "ethereum",
                    "chainContract": "ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
                  }
                ],
                "protocolPositions": [],
                "totalValue": "24962.30",
                "unlockAt": "0"
              }
            }
          },
          "arbitrum": {
            "name": "Arbitrum",
            "key": "arbitrum",
            "value": "161.15",
            "protocolPositions": {
              "WALLET": {
                "name": "wallet",
                "assets": [
                  {
                    "balance": "161.15",
                    "symbol": "usdc",
                    "name": "usd coin",
                    "price": "1.00",
                    "value": "161.15",
                    "decimal": "6",
                    "contract": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
                    "chainKey": "arbitrum",
                    "chainContract": "arbitrum:0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
                  }
                ],
                "protocolPositions": [],
                "totalValue": "161.15",
                "unlockAt": "0"
              }
            }
          }
        }
      }
    },
    "chains": {
      "ethereum": {
        "name": "Ethereum",
        "key": "ethereum",
        "chainId": "1",
        "value": "24962.30",
        "valuePercentile": "99.36",
        "totalCostBasis": "N/A",
        "totalClosedPnl": "N/A",
        "totalOpenPnl": "N/A"
      },
      "arbitrum": {
        "name": "Arbitrum",
        "key": "arbitrum",
        "chainId": "42161",
        "value": "161.15",
        "valuePercentile": "0.64",
        "totalCostBasis": "N/A",
        "totalClosedPnl": "N/A",
        "totalOpenPnl": "N/A"
      }
    }
  }
  ```
</Accordion>

***

## Data Freshness

The Portfolio endpoint uses intelligent caching to balance data freshness with performance:

<AccordionGroup>
  <Accordion title="How Caching Works" icon="clock">
    **Cache Duration:** 1 minute

    **When data is less than 1 minute old:**

    * Cached data returned immediately
    * Response time: under 100ms

    **When data is more than 1 minute old:**

    * Cached data returned immediately
    * Background sync initiated for next request
    * Next request gets fresh data

    **With waitForSync=true:**

    * Waits for sync if data is stale
    * Returns data less than 1 minute old
    * Response time: Variable (1-10 seconds)
  </Accordion>

  <Accordion title="Best Practices" icon="lightbulb">
    **For most use cases:**

    * Use default `waitForSync=false`
    * Data fresher than 1 minute is sufficient
    * Fast response times

    **For real-time tracking:**

    * Set `waitForSync=true` when you need the absolute latest data
    * Accept longer response times
    * Consider rate limits

    **For background updates:**

    * Call endpoint periodically to keep cache warm
    * Background sync ensures next request is fresh
  </Accordion>
</AccordionGroup>

***

## Use Cases

<Tabs>
  <Tab title="Portfolio Dashboard" icon="chart-line">
    Display portfolio overview with net worth and asset breakdown:

    ```javascript theme={null}
    const portfolio = await fetchPortfolio(address);

    // Display summary
    console.log(`Net Worth: $${portfolio.networth}`);
    console.log(`Daily P&L: $${portfolio.dailyIncome - portfolio.dailyExpense}`);

    // List protocols
    Object.values(portfolio.assetByProtocols).forEach(protocol => {
      console.log(`${protocol.name}: $${protocol.value}`);
    });

    // Chain distribution
    Object.values(portfolio.chains).forEach(chain => {
      console.log(`${chain.name}: $${chain.value}`);
    });
    ```
  </Tab>

  <Tab title="Asset Tracking" icon="coins">
    Track specific assets across all protocols:

    ```javascript theme={null}
    function getAssetTotal(portfolio, symbol) {
      let total = 0;
      let balance = 0;

      Object.values(portfolio.assetByProtocols).forEach(protocol => {
        Object.values(protocol.chains).forEach(chain => {
          Object.values(chain.protocolPositions).forEach(position => {
            position.assets
              .filter(asset => asset.symbol === symbol)
              .forEach(asset => {
                total += parseFloat(asset.value);
                balance += parseFloat(asset.balance);
              });
          });
        });
      });

      return { symbol, balance, value: total };
    }

    const ethTotal = getAssetTotal(portfolio, 'eth');
    console.log(`Total ETH: ${ethTotal.balance} ($${ethTotal.value})`);
    ```
  </Tab>

  <Tab title="Protocol Analysis" icon="vault">
    Analyze DeFi protocol exposure:

    ```javascript theme={null}
    function getProtocolExposure(portfolio) {
      const protocols = Object.values(portfolio.assetByProtocols);
      const totalValue = parseFloat(portfolio.networth);

      return protocols.map(protocol => ({
        name: protocol.name,
        value: parseFloat(protocol.value),
        percentage: (parseFloat(protocol.value) / totalValue * 100).toFixed(2)
      })).sort((a, b) => b.value - a.value);
    }

    const exposure = getProtocolExposure(portfolio);
    exposure.forEach(p => {
      console.log(`${p.name}: $${p.value} (${p.percentage}%)`);
    });
    ```
  </Tab>
</Tabs>

***

## Error Responses

<AccordionGroup>
  <Accordion title="400 Bad Request" icon="circle-exclamation">
    Invalid parameters provided.

    ```json theme={null}
    {
      "error": "Bad Request",
      "message": "addresses parameter is required"
    }
    ```

    **Common causes:**

    * Missing `addresses` parameter
    * Invalid address format
  </Accordion>

  <Accordion title="401 Unauthorized" icon="lock">
    Authentication failed.

    ```json theme={null}
    {
      "error": "Unauthorized",
      "message": "Invalid API key"
    }
    ```

    **Solution:** Check your API key in the Authorization header
  </Accordion>

  <Accordion title="429 Too Many Requests" icon="gauge-high">
    Rate limit exceeded.

    ```json theme={null}
    {
      "error": "Rate limit exceeded",
      "message": "You have exceeded your rate limit",
      "retry_after": 60
    }
    ```

    **Solution:** Wait for the specified time or implement retry logic
  </Accordion>

  <Accordion title="402 Payment Required" icon="credit-card">
    Insufficient credits.

    ```json theme={null}
    {
      "error": "Insufficient credits",
      "message": "Please purchase more credits to continue"
    }
    ```

    **Solution:** Purchase more credits at [data.octav.fi](https://data.octav.fi/)
  </Accordion>
</AccordionGroup>

***

## Related Endpoints

<CardGroup cols={2}>
  <Card title="Transactions" icon="receipt" href="/api/endpoints/transactions">
    View transaction history for this address
  </Card>

  <Card title="Token Overview" icon="coins" href="/api/endpoints/token-overview">
    Get detailed token breakdown by protocol
  </Card>

  <Card title="Historical Portfolio" icon="clock" href="/api/endpoints/historical-portfolio">
    View portfolio value at specific dates
  </Card>

  <Card title="Status" icon="signal" href="/api/endpoints/status">
    Check when portfolio was last synced
  </Card>
</CardGroup>
