‹ API Foundations Lesson 11 of 16
Contents Lesson 11 of 16

4 min read · practitioner

Why did one request use 100 calls?

"One request equals one call" is a convenient assumption and it is wrong. Endpoints are priced by how much work they represent, and the spread is two orders of magnitude. Here is the documented cost per request, by family:

Endpoint Documented cost
/eod/{ticker} 1
/real-time/{ticker} 1 per symbol returned; a ten-symbol s= batch spent 10 (measured on /user, 2026-09-07)
/div/{ticker}, /splits/{ticker} 1
/calendar/earnings, /calendar/ipos, /calendar/dividends, /calendar/splits, /calendar/trends 1
/exchanges-list, /exchange-symbol-list/{code} 1
/search/{query} 1
/economic-events 1
/intraday/{ticker} 5
/technical/{ticker} 5
/screener 5
/exchange-details/{code}, /symbol-change-history 5
/news, /sentiments 5, plus 5 per ticker queried
/ticks 10
/fundamentals/{ticker} 10
/insider-transactions 10
/historical-market-cap/{ticker} 10
/macro-indicator/{country} 10
/eod-bulk-last-day/{exchange} 100 for a whole exchange; 1 per ticker when filtered by symbol
/bulk-fundamentals/{EXCHANGE} 100 per request, maximum 500 symbols

The arithmetic that changes how you build

Say you need yesterday's close for 3,000 US tickers.

The loop: 3,000 requests to /eod/{ticker} at 1 call each = 3,000 calls.

The bulk endpoint: one request to /eod-bulk-last-day/US = 100 calls.

Thirty times cheaper, one round trip instead of three thousand, and it returns instruments you had forgotten were in your universe. That is not a micro-optimisation; it is the difference between a job that finishes in a second and one that spends twenty minutes politely queueing.

Now fundamentals for the same 3,000 tickers, one at a time: 3,000 × 10 = 30,000 calls. That is 30% of a 100,000-a-day quota for a single refresh. Do it twice a day and you have spent 60% of your budget before anything else runs. /bulk-fundamentals/{EXCHANGE} exists for exactly this, at 100 calls per request with a maximum of 500 symbols.

Cache by what the field is, not by endpoint. A closed session's date, open, high, low, close and volume are settled once the venue's revision window has passed; store them once and never fetch them again. adjusted_close is valid only until the instrument's next ex-date, so cache it with an expiry on that date, or store raw closes and derive it. Symbol lists, exchange details and holiday calendars change on a schedule of days; refresh them daily. A delayed quote is worth its delay and no longer. /user costs nothing and can be read freely. A pipeline that respects those five lifetimes spends its quota on new rows only.

The expensive failure

A bulk request that fails still costs its 100 calls. Wrap one in a naive retry loop and each attempt burns another 100 for nothing. Five retries on a genuinely broken request is 500 calls spent on zero rows — and if the reason it failed was a rate limit, the retries are also feeding the problem that caused it.

This is the single most expensive mistake in the course, and it is one line of code: retry the cheap things freely, retry the expensive things once, and log the difference.

Two more things worth knowing

Cost is per request, not per row. /eod/AAPL.US with no from or to returns decades of daily rows for the same 1 call as a five-day slice. So there is no quota argument for paginating a history by hand — ask for the whole range you need in one request. (There is a bandwidth argument for narrowing the range, which is a different budget.)

The WebSocket feed does not consume API calls. Streaming real-time data is documented as separate from the REST call budget. A live price display and a nightly historical backfill therefore draw on two different budgets, and the one you optimise is the one you are actually spending.

Try it now

  1. Price your own daily refresh in calls before you write it: list every endpoint it hits, multiply by its cost, sum. Compare that to your daily quota.
  2. Find one loop over tickers in your code that a bulk endpoint could replace, and compute the saving in calls.
  3. Check what your retry logic does after a failed request to a 100-call endpoint. If the answer is "the same as for a 1-call endpoint", that is today's fix.