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

# Build an NFT portfolio viewer

> Fetch a wallet's NFT holdings across chains, group them by collection, and display floor prices, total value, and per-chain breakdowns with Zerion API.

**What you'll build:**

* Fetch a wallet's total NFT value broken down by chain
* List NFT collections sorted by floor value
* Drill into individual NFT positions with metadata
* Filter by specific collections

```
NFT value by chain:
  ethereum: $1,820.50
  polygon:  $37.56

Collections:
  Bored Ape Yacht Club:  1 NFT(s) - floor value $1,802.40
  Art Blocks Curated:    2 NFT(s) - floor value $55.11
  Polygon Apes:          3 NFT(s) - floor value $37.56

Individual NFTs:
  Bored Ape #7495 (Bored Ape Yacht Club)  - $1,802.40
  Chromie Squiggle #4821 (Art Blocks)     - $37.55
  Fidenza #479 (Art Blocks)               - $17.56
  Polygon Ape #102 (Polygon Apes)         - $12.52
```

**Time:** \~10 minutes

## Prerequisites

* A Zerion API key ([get one here](https://dashboard.zerion.io))
* A wallet address to query

## Steps

<Steps>
  <Step title="Get the NFT portfolio overview">
    Start by fetching the wallet's [NFT portfolio](/api-reference/wallets/get-wallet-nft-portfolio) to get total NFT value broken down by chain.

    <CodeGroup>
      ```javascript JavaScript theme={null}
      const API_KEY = process.env.ZERION_API_KEY;
      const address = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";
      const headers = {
        accept: "application/json",
        authorization: `Basic ${btoa(API_KEY + ":")}`,
      };

      const response = await fetch(
        `https://api.zerion.io/v1/wallets/${address}/nft-portfolio?currency=usd`,
        { headers }
      );

      const { data } = await response.json();
      const byChain = data.attributes.positions_distribution_by_chain;
      console.log("NFT value by chain:", byChain);
      ```

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

      api_key = os.environ["ZERION_API_KEY"]
      address = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"

      response = requests.get(
          f"https://api.zerion.io/v1/wallets/{address}/nft-portfolio",
          params={"currency": "usd"},
          auth=(api_key, ""),
      )

      data = response.json()["data"]
      by_chain = data["attributes"]["positions_distribution_by_chain"]
      print("NFT value by chain:", by_chain)
      ```

      ```bash cURL theme={null}
      curl -u "YOUR_API_KEY:" \
        "https://api.zerion.io/v1/wallets/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045/nft-portfolio?currency=usd"
      ```
    </CodeGroup>

    The response includes `positions_distribution_by_chain`, a map of chain IDs to their total NFT floor value (e.g., `ethereum: 1820.50`, `polygon: 37.56`).

    <Note>
      The API may return a `202` status if the wallet's NFT data is still being indexed. Poll the endpoint until you receive a `200` response.
    </Note>
  </Step>

  <Step title="Fetch NFT collections">
    Get the wallet's [NFT collections](/api-reference/wallets/get-wallet-nft-collections) grouped by collection, sorted by total floor price.

    <CodeGroup>
      ```javascript JavaScript theme={null}
      const collectionsRes = await fetch(
        `https://api.zerion.io/v1/wallets/${address}/nft-collections/?currency=usd&sort=-total_floor_price`,
        { headers }
      );

      const collections = await collectionsRes.json();

      for (const collection of collections.data) {
        const { collection_info, nfts_count, total_floor_price } = collection.attributes;
        console.log(
          `${collection_info.name}: ${nfts_count} NFT(s) - floor value ${total_floor_price != null ? `$${total_floor_price.toFixed(2)}` : "N/A"}`
        );
      }
      ```

      ```python Python theme={null}
      collections_response = requests.get(
          f"https://api.zerion.io/v1/wallets/{address}/nft-collections/",
          params={"currency": "usd", "sort": "-total_floor_price"},
          auth=(api_key, ""),
      )

      for collection in collections_response.json()["data"]:
          attrs = collection["attributes"]
          name = attrs.get("collection_info", {}).get("name", "Unknown")
          count = attrs["nfts_count"]
          floor = attrs.get("total_floor_price")
          print(f"{name}: {count} NFT(s) - floor value {f'${floor:.2f}' if floor is not None else 'N/A'}")
      ```

      ```bash cURL theme={null}
      curl -u "YOUR_API_KEY:" \
        "https://api.zerion.io/v1/wallets/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045/nft-collections/?currency=usd&sort=-total_floor_price"
      ```
    </CodeGroup>

    Each collection entry includes:

    * `collection_info.name` - the collection name
    * `nfts_count` - how many NFTs from this collection the wallet holds
    * `total_floor_price` - combined floor value of the wallet's positions in this collection
    * `min_changed_at` / `max_changed_at` - acquisition timestamps
  </Step>

  <Step title="Fetch individual NFT positions">
    Drill into the specific [NFT positions](/api-reference/wallets/get-wallet-nft-positions) held by the wallet, sorted by floor price.

    <CodeGroup>
      ```javascript JavaScript theme={null}
      const positionsRes = await fetch(
        `https://api.zerion.io/v1/wallets/${address}/nft-positions/?currency=usd&sort=-floor_price`,
        { headers }
      );

      const positions = await positionsRes.json();

      for (const position of positions.data) {
        const { nft_info, collection_info, value } = position.attributes;
        console.log(
          `${nft_info?.name} (${collection_info?.name}) - ${value != null ? `$${value.toFixed(2)}` : "N/A"}`
        );
      }
      ```

      ```python Python theme={null}
      positions_response = requests.get(
          f"https://api.zerion.io/v1/wallets/{address}/nft-positions/",
          params={"currency": "usd", "sort": "-floor_price"},
          auth=(api_key, ""),
      )

      for position in positions_response.json()["data"]:
          attrs = position["attributes"]
          nft_name = attrs.get("nft_info", {}).get("name", "Unknown")
          collection_name = attrs.get("collection_info", {}).get("name", "Unknown")
          value = attrs.get("value")
          print(f"{nft_name} ({collection_name}) - {f'${value:.2f}' if value is not None else 'N/A'}")
      ```

      ```bash cURL theme={null}
      curl -u "YOUR_API_KEY:" \
        "https://api.zerion.io/v1/wallets/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045/nft-positions/?currency=usd&sort=-floor_price"
      ```
    </CodeGroup>

    Using `sort=-floor_price` orders NFTs by their floor price (highest first). Each position includes:

    * `nft_info.name` - the NFT's name or token ID
    * `collection_info.name` - the collection it belongs to
    * `value` - estimated USD value based on the collection's floor price
  </Step>

  <Step title="Filter by collection (optional)">
    To show NFTs from a specific collection, use the `filter[collection_ids]` parameter. Collection IDs are UUIDs found in the `relationships.collection.data.id` field of each NFT position returned by the endpoints above.

    ```bash theme={null}
    curl -g -u "YOUR_API_KEY:" \
      "https://api.zerion.io/v1/wallets/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045/nft-positions/?currency=usd&filter[collection_ids]=bc31571c-d2a8-45e4-8012-37dbbf8b7038"
    ```
  </Step>

  <Step title="Full working example">
    Save as `nft-portfolio.mjs` and run with `node nft-portfolio.mjs`:

    ```javascript theme={null}
    const API_KEY = process.env.ZERION_API_KEY;
    const BASE_URL = "https://api.zerion.io/v1";
    const headers = {
      accept: "application/json",
      authorization: `Basic ${btoa(API_KEY + ":")}`,
    };

    async function fetchWithRetry(url, maxRetries = 10) {
      for (let i = 0; i < maxRetries; i++) {
        const res = await fetch(url, { headers });
        if (res.status === 202) {
          const retryAfter = res.headers.get("Retry-After") || "3";
          console.log(`NFT data still indexing, retrying in ${retryAfter}s... (${i + 1}/${maxRetries})`);
          await new Promise((r) => setTimeout(r, parseInt(retryAfter) * 1000));
          continue;
        }
        if (!res.ok) throw new Error(`API error: ${res.status}`);
        return res.json();
      }
      throw new Error("Timed out waiting for NFT data after " + maxRetries + " retries");
    }

    async function displayNFTPortfolio(address) {
      // 1. Portfolio overview
      const portfolio = await fetchWithRetry(
        `${BASE_URL}/wallets/${address}/nft-portfolio?currency=usd`
      );
      const byChain = portfolio.data.attributes.positions_distribution_by_chain;

      console.log("=== NFT VALUE BY CHAIN ===");
      for (const [chain, value] of Object.entries(byChain).sort((a, b) => b[1] - a[1])) {
        console.log(`  ${chain}: $${value.toFixed(2)}`);
      }

      // 2. Collections
      const collections = await fetchWithRetry(
        `${BASE_URL}/wallets/${address}/nft-collections/?currency=usd&sort=-total_floor_price`
      );

      console.log("\n=== COLLECTIONS ===");
      for (const c of collections.data) {
        const { collection_info, nfts_count, total_floor_price } = c.attributes;
        console.log(`  ${collection_info.name}: ${nfts_count} NFT(s) - ${total_floor_price != null ? `$${total_floor_price.toFixed(2)}` : "N/A"}`);
      }

      // 3. Individual NFTs
      const positions = await fetchWithRetry(
        `${BASE_URL}/wallets/${address}/nft-positions/?currency=usd&sort=-floor_price`
      );

      console.log("\n=== INDIVIDUAL NFTs ===");
      for (const pos of positions.data) {
        const { nft_info, collection_info, value } = pos.attributes;
        console.log(`  ${nft_info?.name} (${collection_info?.name}) - ${value != null ? `$${value.toFixed(2)}` : "N/A"}`);
      }
    }

    displayNFTPortfolio("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
    ```
  </Step>
</Steps>

## Next steps

* Use [pagination](/pagination-and-filtering) to handle wallets with large NFT collections
* Include the `nft_collections` relationship in the `include` parameter to get full collection metadata in one request
* Combine with the [transactions endpoint](/api-reference/wallets/get-wallet-transactions) to track NFT purchase and sale history


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