A watchlist needs an outcome for every instrument, including symbols that fail. A successful batch envelope does not mean all quotes succeeded. Build the refresh process around individual rows so one unavailable symbol cannot silently erase the rest of the list.
Split a watchlist into requests of ten
POST /api/quote/batch accepts up to ten symbols per request. It requires batch access on the active plan. The example below removes duplicate inputs and sends chunks sequentially; it makes no assumption that batching changes quota accounting.
Save it as watchlist.py, install requests, and set the same server-side environment variables as the Python quote guide. Replace the sample symbols with instruments you have verified.
import math
import os
import requests
symbols = list(dict.fromkeys(["NASDAQ:AAPL", "NASDAQ:MSFT"]))
base = os.environ["NAVQUOTE_API_BASE"].rstrip("/")
rows = {symbol: {"status": "unavailable"} for symbol in symbols}
with requests.Session() as client:
client.headers["Authorization"] = f"Bearer {os.environ['API_KEY']}"
for offset in range(0, len(symbols), 10):
chunk = symbols[offset:offset + 10]
response = client.post(
f"{base}/api/quote/batch",
json={"symbols": chunk, "fields": "all"},
timeout=30,
)
response.raise_for_status()
result = response.json()
if result.get("success") is not True:
raise RuntimeError("Batch request failed")
for item in result["data"]["data"]:
symbol = item["symbol"]
if symbol not in chunk:
continue
quote = item.get("data") or {}
price = quote.get("lp")
stamp = quote.get("lp_time")
usable = (
item.get("success") is True
and type(price) in (int, float) and math.isfinite(price)
and type(stamp) in (int, float) and math.isfinite(stamp)
and stamp > 0
)
rows[symbol] = {
"status": "available" if usable else "unavailable",
"price": price if usable else None,
"market_time": stamp if usable else None,
"degraded": bool(result.get("degraded") or item.get("degraded")),
}
print(rows)
Read the nested batch result
The batch envelope contains data.total, data.successful, data.failed and a data.data array. Each item identifies its own symbol and success state. A successful item’s data holds the quote fields directly; do not reuse the single-quote parser without accounting for that different wrapper.
Pre-populating rows as unavailable means an omitted result cannot accidentally count as a successful refresh. The sample matches explicit exchange-prefixed symbols. If your application accepts aliases or lowercase input, normalize and resolve them before building the watchlist, then preserve a mapping back to the user’s label.
Merge results into an existing screen
The script reports one refresh; it does not hold previous data. In a stateful application, update successful rows and retain the last known value for failed rows with a visible unavailable or stale status. Never turn a missing price into zero: that would create a false market move.
Sort or filter using only values that meet your application’s freshness requirements. A degraded response should remain distinguishable from a fresh observation even if its numeric fields look complete. Avoid refreshing an individual row’s market timestamp just because another symbol in the same batch updated.
Recover without amplifying failures
A request-level failure stops this command-line example with an error. A background worker can record a failed chunk and continue processing other chunks, but should keep a bounded deadline and concurrency limit. Retry temporary failures selectively instead of repeatedly fetching successful symbols.
If the watchlist stays open for long periods, consider an initial batch snapshot followed by a stream. That combination still needs timestamp reconciliation: the snapshot and stream are not documented as an atomic handoff. The WebSocket guide explains acknowledgements, updates and recovery boundaries.