JSON or CSV, and why the default can bite you
Most endpoints here accept fmt=json or fmt=csv. They are not two qualities of the same answer — they are two shapes, each right for a different job.
The same five rows, twice
As JSON, each row is an object that names its own fields:
[{"date": "2026-07-23", "open": 321.73, "high": 323.3, "low": 319.35,
"close": 321.66, "adjusted_close": 321.66, "volume": 40840800},
{"date": "2026-07-24", "open": 321.79, "high": 334.37, "low": 321.62,
"close": 333.02, "adjusted_close": 333.02, "volume": 47443900}]
As CSV, the field names are written once, at the top, and every row after that is bare values:
Date,Open,High,Low,Close,Adjusted_close,Volume
2026-07-23,321.73,323.3,319.35,321.66,321.66,40840800
2026-07-24,321.79,334.37,321.62,333.02,333.02,47443900
Same seven fields, same numbers — both blocks are the response as it stood on 2026-07-28, so if you run them today the adjusted_close column will read slightly lower in each. The difference the lesson is about is that JSON repeats the field names on every single row and CSV does not. Over a five-row request that is trivia. Over twenty years of daily history — roughly 5,000 rows — those repeated labels are most of the payload.
Choosing between them
JSON when a program consumes it. It is self-describing, it survives fields being added, and it can express nesting. Some responses cannot be flattened at all: /fundamentals/{ticker} returns a deeply nested document with sections for company information, financial statements, holders and earnings. There is no sensible single CSV table for that, and the API does not pretend otherwise.
CSV when a human or a bulk loader consumes it. It opens in a spreadsheet, it streams into a database load command, and it is smaller on the wire. For a straight rectangular series — EOD prices, dividends, splits — it is the efficient choice.
The gotcha: the default is not consistent
You might assume that leaving fmt off gives you the same thing everywhere. It does not — and the situation is worse than inconsistent documentation, because the documentation and the server do not agree either.
Here is what the spec says next to what actually arrives, every one of these called with no fmt at all:
| Endpoint | Documented default | What the server sends |
|---|---|---|
/exchanges-list |
JSON | JSON |
/symbol-change-history |
JSON | JSON |
/real-time/{ticker} |
JSON | CSV |
/div/{ticker} |
JSON | CSV |
/splits/{ticker} |
JSON | CSV |
/intraday/{ticker} |
CSV | CSV |
/calendar/earnings |
CSV | CSV |
/calendar/dividends |
JSON | 422 — it refuses without a filter; JSON once it has one |
Three endpoints document JSON and send CSV. One sends no data at all until you give it the filter it demands, and then it does send the JSON it documents — so /calendar/dividends and /calendar/earnings, siblings on one documentation page, hand you opposite formats. And /eod/{ticker}, which documents no default, sends CSV under a Content-Type of text/html, so sniffing the content type will not save you either.
A parser written against one of these and pointed at another receives a comma-separated string where it expected a list, and throws a type error that looks like a bug in your code.
There is one rule that makes the whole problem disappear: always send fmt explicitly. It costs seven characters and removes an entire class of failure. Relying on a default means relying on a behaviour you did not ask for and would not notice changing.
Try it now
- Here is the same
/eod/AAPL.USquery in both formats, measured on 28 September 2026. Divide the JSON size by the CSV size for each window, and say how the gap moves with row count.
| window | rows | fmt=json |
fmt=csv |
|---|---|---|---|
| 21 to 25 September 2026 | 5 | 598 bytes | 319 bytes |
| 27 September 2021 to 25 September 2026 | five years | 152,088 bytes | 70,559 bytes |
- Here is
/div/AAPL.US?from=2025-09-01with nofmtat all, called on 28 September 2026: status 200,Content-Type: application/csv, and this whole body. The spec says JSON; count the commas. One call, and you have caught a documented default that is not the served one. The table below is the same call withfmt=json: the same four payouts, as rows.
Date,Dividends
2025-11-10,0.26
2026-02-09,0.26
2026-05-11,0.27
2026-08-10,0.27
- Search your own code for a request that omits
fmt. Add it.