Skip to main content
There is no bulk fundamentals endpoint — a factor batch is your own loop, one symbol per request. Use stock_universe to build the symbol set, fetch the overview for covered rows, and deepen only the shortlist. The rate you run at is set by your plan’s per-minute limit.

Resolve coverage before you spend the batch

stock_universe {"coverage":"has_fundamentals"} returns the symbols eligible for a fundamentals fan-out without one resolution call per ticker. Page with next_offset; use the shape filter when a factor only applies to a statement family. Use stock_search only for user-entered names/tickers, and stock_reference {"catalog":"coverage"} for measured aggregate rates.

Concurrency follows the minute limit

Pick your in-flight count from the plan’s per-minute limit and leave headroom for retries. Also mind the quota: Free is 100 requests per day, which a single 30-symbol watchlist refresh can exhaust once you add a resolve call and an overview call each. Read X-Credits-Remaining after each authenticated tool call when it is present, and stop immediately on a quota_exhausted 429. See Plans and limits.

A bounded worker pool

Both versions cap in-flight requests and back off on a 429.
batch.py
The typed reason separates a minute 429 from a quota 429; do not infer it from timing. For the full policy (attempt caps, total-wait budget), wrap each request with the loop in Errors and retries.

Watching for new data

Schema 2 successful 200 responses include a composite ETag. Store the response and send the same request body later with If-None-Match. A 304 Not Modified has an empty body and means your cached response is still current for that exact tool, schema, params, fundamentals build, policy version, and market cursor. For fundamentals, watch meta.as_of.sec, meta.published_at, meta.build.fundamentals, and meta.last_verified_at when present. For price-backed responses, also watch meta.as_of.market and the response freshness fields such as sessions_behind. Use this polling shape for watchlists:
  1. Resolve the symbol once with stock_search.
  2. Fetch schema 2 data and persist the body plus ETag.
  3. Re-poll with the same body and If-None-Match.
  4. On 304, reuse your cached body. On 200, replace it and store the new ETag.
Schema 2 304s still pass authentication and rate limiting, but the quota credit is refunded at the API edge. Do not expect a response body on 304.

Keep SEC-backed tools out of a tight loop

stock_filings, stock_insider, and stock_ownership read SEC data and can be slow cold, so pull them only for the shortlist a user actually opens — in a background job, cached hard.

Where batches go wrong

  • Fanning out stock_overview over symbols you never checked with stock_search, spending quota on ingestion_pending tickers.
  • Continuing to send after a 429 instead of waiting.
  • Looping on quota_exhausted instead of stopping until the window resets.
  • Rendering a refused field (unavailable is present) as 0. Carry the refusal token through.

Production patterns

Caching, key safety, and SEC loading states around the loop.