What time is it in your data?
Three endpoints in this API represent time in three different ways, and a wrong assumption about any of them produces a series that is subtly, plausibly, invisibly shifted.
EOD has no time at all
An EOD row carries "date": "2026-07-24" and nothing else. There is no hour, so there is no timezone question — but there is a trading-day question. The date belongs to the venue's calendar, not to yours. A Tokyo close and a New York close stamped with the same date happened roughly fourteen hours apart, and joining them on date silently pairs events that were never simultaneous.
Intraday carries three time fields at once
An intraday row carries timestamp, gmtoffset and datetime together. The spec's own example:
{"timestamp": 1628876100, "gmtoffset": 0, "datetime": "2021-08-13 17:35:00",
"open": 148.929992, "high": 149.059997, "low": 148.929992,
"close": 149.035003, "volume": 416405}
timestamp is Unix seconds. gmtoffset here is 0, which tells you that datetime is UTC — not exchange-local. Assume it is local for a US listing and you are four or five hours out, which is precisely enough to move a bar from before the open to after it, or to put an overnight print inside the regular session.
The rule: gmtoffset is not decoration. It is the field that tells you what datetime means.
The parameters are timestamps, not dates
On /intraday/{ticker}, from and to are typed integer — Unix timestamps in UTC. The spec gives 1627896900 for 2021-08-02 09:35:00. On /eod/{ticker}, the parameters with the same two names are YYYY-MM-DD strings.
Same names, different types, different endpoints. Sending 2021-08-02 where an integer is expected produces a 422, not the 400 you might reach for, and the body is a validation envelope naming the field: {"errors":{"from":["The from must be a number."],"to":["The to must be a number."]}}. It is one of the few parameters this API does check — which is worth knowing, because on /eod the same mistake would have sailed through with a 200.
A bar's timestamp marks its start
This one catches everybody once. A 5-minute bar stamped 17:35:00 covers 17:35:00 through 17:39:59. It is labelled by its opening instant, not its close.
You can see the interval in the raw numbers. Two consecutive 5-minute bars in the spec's example are 1628876100 and 1628876400:
1628876400 − 1628876100 = 300 seconds = 5 minutes.
Get this backwards and every bar in your series is shifted by one period. The chart still looks completely normal, which is what makes it dangerous — the error has no visual signature.
Practical rules
- Store UTC. Convert on display. Every timezone bug that ever escapes to production comes from storing something else.
- Never compare a timestamp to a date without deciding whose calendar day you mean.
- When a series looks shifted by exactly one bar, or exactly N hours, suspect time before you suspect data. It is almost always time.
The timeframes lesson in Reading the Market covers why the same instrument looks different at different bar sizes; this lesson is about making sure the bars you have are the bars you think you have.
Try it now
- Here is one hour of 5-minute bars for
AAPL.US,from=1790343000&to=1790346600, reduced to its time fields. Checkgmtoffseton the rows shown, then convert eachtimestampto a UTC clock time yourself and confirm it matchesdatetime. Subtract the first two timestamps, and count how many 300-second steps separate the first bar from the last.
- Here is a whole day of 5-minute bars, 25 September 2026 from midnight to midnight UTC, reduced to its ends. Subtract the first timestamp from the second: that is the interval. Then read the caption on the gaps between neighbours. Where every gap equals the interval, the day had no halt; a gap that did not would be a session boundary or a halt.
- Take the first bar of that day and work out, from its timestamp alone, what time it was in New York and whether it opens the session or closes the previous one. Then check it against the venue's published hours below.