How to fetch complete list of NFT holders from OpenSea API with Python

I’m working on extracting ownership data from a specific NFT project called Aethir Wars through OpenSea’s API. My goal is to collect three pieces of info: the owner details, which token they own, and their wallet address.

The problem I’m running into is that my script only returns a partial list of holders. I think there might be an issue with how I’m handling pagination or API limits. I also noticed that some NFTs show null values in the “Last_Sale” field when they were transferred instead of sold, so I added some error handling for that.

import requests

api_endpoint = "https://api.opensea.io/api/v1/assets"
my_api_key = "your_key_here"
collection_name = 'warriors'

def fetch_holders(category, page_offset):
    params = {'order_by': 'token_id', 'order_direction': 'desc', 'offset': page_offset, 'limit': '50',
              'collection': collection_name, 'X-API-KEY': my_api_key}
    result = requests.get(api_endpoint, params=params)
    json_data = result.json()
    counter = 0
    for token in json_data['assets']:
        try:
            if category in json_data['assets'][0]['name']:
                if json_data["assets"][counter]["last_sale"] is None:
                    print(json_data["assets"][counter]["name"] + ': '
                          + json_data["assets"][counter]["sell_orders"][0]["maker"]["address"])
                else:
                    print(json_data["assets"][counter]["name"] + ': '
                          + json_data["assets"][counter]["last_sale"]["transaction"]["from_account"]["user"]["username"])
        except TypeError:
            continue
        counter += 1

offset_range = range(0, 500, 50)

for page in offset_range:
    try:
        fetch_holders(category='Warrior', offset=str(page))
    except KeyError:
        continue

    try:
        fetch_holders(category='Mage', offset=str(page))
    except KeyError:
        continue

    try:
        fetch_holders(category='Archer', offset=str(page))
    except KeyError:
        continue

Any ideas why I’m not getting the full ownership list?

Hey there! I’ve been wrestling with similar opensea api headaches lately and noticed something intresting in your code that might be causing the incomplete results.

One thing that caught my eye - you’re using a fixed range of 0-500 with 50 item chunks, but how do you actually know the collection has exactly that many items? What if there are way more NFTs in the collection than 500? The API won’t tell you upfront how many total assets exist, so you might be cutting off your data collection prematurely.

Also, I’m curious about your error handling approach - you’re using except KeyError: continue which essentially swallows any missing data errors. But what if those KeyErrors are actually telling you something important about the API response structure? Have you tried logging what specific keys are missing when those exceptions fire?

Another thought - are you hitting any rate limits? OpenSea can be pretty strict about request frequency, and if you’re making rapid sequential calls without delays, some of your requests might be getting throttled or rejected silently.

What happens if you add some debug output to see exactly how many assets each API call is returning? Like print(f"Page {page}: got {len(json_data.get('assets', []))} assets") - that might show you where the data starts drying up.

Have you considered checking the actual collection size first through opensea’s stats endpoint to get a better idea of how many items you should expect to find?

The main issue appears to be with your pagination logic and how you’re filtering the results. You’re only checking if the category exists in the first asset’s name (json_data['assets'][0]['name']) but then attempting to process all assets in the loop. This means you’re likely skipping entire pages of results when the first item doesn’t match your category filter. Another problem is that you’re hardcoding the offset range to 500, but you should continue fetching until the API returns an empty assets array or fewer results than your limit. The collection might have more than 500 items total. I’d suggest moving the category check inside the loop to if category in token['name']: and implementing proper pagination by checking the response length. Also consider using the next parameter from the API response if available, as it’s more reliable than manually calculating offsets. The current approach might work for smaller collections but will definitely miss holders in larger ones.