Why is FOREX in the exchange list when it isn't an exchange?
Pull /exchanges-list and most rows look exactly as you would expect: a real venue with a real ISO MIC. LSE with XLON, XETRA with XETR, TO with XTSE.
Then you reach these, returned live on 2026-07-28:
{"Name": "Money Market Virtual Exchange", "Code": "MONEY", "OperatingMIC": null,
"Country": "Unknown", "Currency": "Unknown"}
{"Name": "Europe Fund Virtual Exchange", "Code": "EUFUND", "OperatingMIC": null,
"Country": "Unknown", "Currency": "EUR"}
{"Name": "Government Bonds", "Code": "GBOND", "OperatingMIC": null,
"Country": "Unknown", "Currency": "Unknown"}
{"Name": "Cryptocurrencies", "Code": "CC", "OperatingMIC": "CRYP", "Country": "Unknown"}
{"Name": "FOREX", "Code": "FOREX", "OperatingMIC": "CDSL", "Country": "Unknown"}
Two of them say "Virtual Exchange" in the name. All five say Country: "Unknown". Three have no MIC at all.
Resolve two of them through /exchange-details/{EXCHANGE_CODE} and every field a real venue would answer comes back, mostly saying nothing:
They are namespaces, not venues
The API addresses everything through one grammar: SYMBOL.EXCHANGE. But plenty of financial series do not trade on an exchange. A currency pair trades over the counter across a decentralised network. A benchmark rate is published, not traded. A government bond yield is a computed number, not a transaction.
Rather than invent a second addressing scheme for those, the API gives them a fictional venue so the existing grammar keeps working. FOREX is not a place. It is a namespace that lets EURUSD.FOREX be a valid address.
Real examples, all of which go to the ordinary /eod/{ticker} endpoint:
EURUSD.FOREX,EUR.FOREX— currency pairsBTC-USD.CC— cryptoUS10Y.GBOND— the 10-year US Treasury yieldEURIBOR3M.MONEY,SOFR.MONEY,ECBRATES.MONEY— reference and policy rates. Pull the exchange's symbol list before you guess a code here: the plausible-looking ones are often not there, and a wrong code on a virtual exchange returns404 Ticker Not Foundrather than an empty series.GSPC.INDX— the S&P 500 index
What comes back is still shaped like a stock
Call /eod/US10Y.GBOND for 20 to 24 July 2026 and you get this (live, 2026-07-28):
{"date": "2026-07-24", "open": 4.7, "high": 4.716, "low": 4.655,
"close": 4.687, "adjusted_close": 4.687, "volume": 0}
The seven OHLCV fields are all there. But close here is 4.687 percent, not 4.687 dollars — it is a yield wearing a price schema. volume is 0 for every row, because a computed yield has no shares changing hands. And adjusted_close equals close exactly, because there are no splits or dividends to adjust for.
Code that assumes every /eod response is a tradable price in currency units will happily compute a nonsense market value from that. The schema is generic; the meaning is not.
The gap in the list
Now the trap. INDX is a working exchange code — GSPC.INDX resolves, and /exchange-symbol-list/INDX returns the index universe, roughly 1,668 symbols. But INDX does not appear in the 70 rows that /exchanges-list returns.
If your code builds its universe by iterating /exchanges-list, it will never see a single index and will never tell you why. The general form of this is worth carrying: a list endpoint returns what that endpoint lists, which is not the same as everything that exists. Verify the codes you depend on by calling them, not by assuming they were enumerated.
Try it now
/exchanges-listreturned 70 rows on 28 September 2026. Its last seven are below, and no row above them hasCountryset to"Unknown". Count the rows whereCountryis"Unknown", then use the JSON block at the top of this lesson to count those whereOperatingMICis null. Say what your parser currently does with each of the two fields.
- Here are
/eod/US10Y.GBONDand/eod/AAPL.USfor the same week, the one ending Friday 25 September 2026. Same seven columns; write down whatcloseandvolumemean in each.
- Below is the head of
/exchange-symbol-list/INDX: a working exchange code, 1,674 symbols long on 28 September 2026. Now look back at the 70-row list: no row hasCodeINDX. That gap is the lesson.