How Azeroth Auction Assassin Monitors Auction Houses: A Technical Deep Dive

Azeroth Auction Assassin continuously monitors World of Warcraft auction houses by polling the Blizzard Auction House API during calculated scan windows, using Last-Modified headers to skip unchanged data and parallel threads to process multiple realms simultaneously.

The ff14-advanced-market-search/azerothauctionassassin repository provides an open-source Python solution for tracking underpriced items across World of Warcraft's auction houses. To monitor auction houses efficiently across hundreds of connected realms without exhausting API quotas, the tool implements a time-windowed polling architecture centered on the MegaData configuration class and the Alerts worker thread. This system coordinates OAuth-authenticated requests, intelligent data deduplication, and Discord webhook notifications into a continuous monitoring loop.

Configuration and Upload Timer Initialization

Monitoring begins with the MegaData class defined in utils/mega_data_setup.py. During initialization (lines 33-43), the class reads AzerothAuctionAssassinData/mega_data.json to load region settings, faction preferences, desired items/pets, and scan-window parameters. The configuration includes the Saddlebag upload-timers backup URL, which provides the exact minute when each realm's auction house data refreshes.

At startup, MegaData optionally calls get_update_timers_backup from utils/api_requests.py (lines 101-115) to populate self.upload_timers. This dictionary maps each dataSetID (representing a connected realm or commodity group) to its lastUploadMinute, enabling the system to predict when Blizzard publishes new auction snapshots (typically once per hour).

Scan Window Detection and Scheduling

Every minute, the Alerts thread in mega_alerts.py determines which realms require scanning. The is_in_scan_window function (lines 227-249) compares the current minute against each realm's known lastUploadMinute, respecting user-defined SCAN_TIME_MIN and SCAN_TIME_MAX offsets. This creates a dynamic window—typically starting 2 minutes before and extending 5 minutes after the upload time—during which the tool actively polls for new data.

Realms outside their designated windows are skipped, minimizing unnecessary API calls. When the current minute falls within a realm's window, the system flags the corresponding dataSetID for immediate processing.

Parallel API Request Execution

For each realm entering its scan window, the main loop in mega_alerts.py (lines 260-285) spawns a ThreadPoolExecutor with a configurable number of workers (mega_data.THREADS). The executor submits pull_single_realm_data tasks for every matching connectedRealmId, enabling concurrent monitoring of multiple auction houses.

The pull_single_realm_data function invokes mega_data.get_listings_single (lines 595-639 in utils/mega_data_setup.py), which constructs the appropriate Blizzard API endpoint. For standard realms, it builds a region-specific URL; for commodity items (cross-realm trading), it uses special IDs -1 or -2 to hit the commodity endpoints.


# utils/mega_data_setup.py – decide which API to call

def get_listings_single(self, connectedRealmId: int):
    if connectedRealmId in [-1, -2]:          # commodity endpoint

        auction_info = self.make_commodity_ah_api_request()
    else:
        url = self.construct_api_url(connectedRealmId, "")
        auction_info = self.make_ah_api_request(url, connectedRealmId)
    # “skipped” means the Last‑Modified header didn’t change

    if auction_info.get("skipped"):
        return None
    return auction_info["auctions"]

Intelligent Change Detection

Before parsing auction data, the system checks for actual updates using HTTP header inspection. Both make_ah_api_request and make_commodity_ah_api_request in utils/mega_data_setup.py (lines 663-698 and 726-757) capture the Last-Modified header from Blizzard's response.

If the timestamp matches the cached value in self.upload_timers for that dataSetID, the function immediately returns {"auctions": [], "skipped": True}, causing the realm to be bypassed. When the timestamp differs, indicating fresh data, update_local_timers refreshes the cache and the JSON payload proceeds to parsing. This mechanism prevents processing stale data and respects API rate limits.


# utils/mega_data_setup.py – skip unchanged data

def make_ah_api_request(self, url, connectedRealmId):
    req = requests.get(url, headers={"Authorization": f"Bearer {self.check_access_token()}"})
    if "Last-Modified" in req.headers:
        lastUploadTimeRaw = req.headers["Last-Modified"]
        if self.upload_timers.get(connectedRealmId, {}).get("lastUploadTimeRaw") == lastUploadTimeRaw:
            return {"auctions": [], "skipped": True}
        self.update_local_timers(connectedRealmId, lastUploadTimeRaw)
    return req.json()

Data Processing and Discord Alerts

New auction data passes through clean_listing_data in mega_alerts.py (lines 250-340), which filters listings against user criteria including maximum price, item level, and specific item IDs. Matching auctions are formatted as Discord embeds or plain messages and dispatched via the configured webhook URL.

After processing all active realms, the loop sleeps for 5 seconds if work was performed or 20 seconds if all realms were skipped, then resumes at the next minute mark. This creates a continuous monitoring cycle that reacts immediately to Blizzard's hourly data refreshes.


# mega_alerts.py – main monitoring loop

while self.running:
    current_min = int(datetime.now().minute)
    matching_realms = [realm["dataSetID"]
        for realm in mega_data.get_upload_time_list()
        if is_in_scan_window(current_min,
                              realm["lastUploadMinute"],
                              mega_data.SCAN_TIME_MIN,
                              mega_data.SCAN_TIME_MAX)]
    if matching_realms:
        pool = ThreadPoolExecutor(max_workers=mega_data.THREADS)
        for cid in matching_realms:
            pool.submit(pull_single_realm_data, cid)
        pool.shutdown(wait=True)

Practical Code Examples

Running a One-Shot Debug Scan

To execute a single scan across all configured realms without starting the continuous loop:

from utils.mega_data_setup import MegaData
from mega_alerts import Alerts

# Initialise MegaData with default JSON files

mega = MegaData()                 # reads AzerothAuctionAssassinData/mega_data.json

# Run a single fast scan across all realms

alerts = Alerts()
alerts.path_to_data_files = None  # use defaults

alerts.run()                      # will invoke main_fast() → pull_single_realm_data for every realm

Fetching Listings for a Specific Realm

To manually retrieve auctions for a single connected realm and check for new data:

from utils.mega_data_setup import MegaData

mega = MegaData()
connected_realm_id = 1234                 # ID from AzerothAuctionAssassinData/*-wow-connected-realm-ids.json

auctions = mega.get_listings_single(connected_realm_id)

if auctions:
    print(f"Found {len(auctions)} listings in realm {connected_realm_id}")
else:
    print("No new data – Last‑Modified unchanged")

Testing Scan Window Logic

To verify whether a specific minute falls within a realm's monitoring window:

from mega_alerts import is_in_scan_window

current = 3          # current minute of the hour

last_upload = 58     # realm reported upload at minute 58

min_before = -2      # start scanning 2 minutes before upload

max_after = 5        # stop scanning 5 minutes after upload

print(is_in_scan_window(current, last_upload, min_before, max_after))

# → True (window wraps around the hour boundary)

Summary

  • Azeroth Auction Assassin monitors auction houses by polling the Blizzard API only during calculated scan windows around each realm's known upload time.
  • The Last-Modified header check in make_ah_api_request eliminates redundant processing of unchanged data.
  • ThreadPoolExecutor enables parallel scanning of multiple realms while respecting rate limits.
  • The MegaData class centralizes configuration, OAuth token management, and timer caching.
  • Detected deals are filtered through clean_listing_data and immediately posted to Discord via webhooks.

Frequently Asked Questions

How often does Azeroth Auction Assassin check the auction house?

The main loop evaluates realms every minute, but only initiates API requests during the configured scan window (typically 2-5 minutes before and after the realm's hourly data upload). Outside these windows, realms are skipped entirely to conserve API quota.

What prevents the tool from hitting Blizzard API rate limits?

Two mechanisms protect against rate limiting: the Last-Modified header comparison that skips unchanged data, and the scan window logic that concentrates requests only when new data is expected. Additionally, the ThreadPoolExecutor uses a configurable worker count (THREADS in mega_data.json) to limit concurrent connections.

How does AAA know when a realm's auction data updates?

The system references the Saddlebag upload-timers API or local upload_timers cache containing each dataSetID's lastUploadMinute. Blizzard typically refreshes auction house data once per hour at consistent minutes (e.g., minute 58), and AAA polls aggressively only during the expected refresh window.

Can the tool monitor commodity items separately from specific realms?

Yes. In utils/mega_data_setup.py, the get_listings_single method treats IDs -1 and -2 as commodity endpoints, routing them to make_commodity_ah_api_request instead of the standard realm-specific make_ah_api_request. This allows monitoring cross-realm commodity markets independently from individual server auction houses.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →