- 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
Prerequisites
- A Zerion API key (get one here)
- A wallet address to query
Steps
Start by fetching the wallet’s NFT portfolio to get total NFT value broken down by chain.
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).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.Get the wallet’s NFT collections grouped by collection, sorted by total floor price.
collection_info.name — the collection namenfts_count — how many NFTs from this collection the wallet holdstotal_floor_price — combined floor value of the wallet’s positions in this collectionmin_changed_at / max_changed_at — acquisition timestampsDrill into the specific NFT positions held by the wallet, sorted by floor price.
nft_info.name — the NFT’s name or token IDcollection_info.name — the collection it belongs tovalue — estimated USD value based on the collection’s floor priceTo 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.curl -u "YOUR_API_KEY:" \
"https://api.zerion.io/v1/wallets/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045/nft-positions/?currency=usd&filter[collection_ids]=bc31571c-d2a8-45e4-8012-37dbbf8b7038"
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");
Next steps
- Use pagination to handle wallets with large NFT collections
- Include the
nft_collectionsrelationship in theincludeparameter to get full collection metadata in one request - Combine with the transactions endpoint to track NFT purchase and sale history