GUIDE · DATA
TASI historical data
One endpoint serves every bar the platform holds: GET /api/v1/history/{symbol}. This is what it returns, how to export it to CSV, and the two details that quietly break intraday studies — the request window and the timestamp offset.
Published 23 October 2026 · verified against the live API.
What is available
| Interval | Served from |
|---|---|
1m | the base one-minute series |
5m · 15m · 30m · 1h | aggregated server-side from the 1m series, open/high/low/close rolled correctly |
1d | the daily series, with quality: final or provisional |
Those six are the whole list. 10m, 60m, 1w and 1mo are not served and answer 400 invalid_interval — build weekly bars from 1d if that is what you need, and you control the week boundary.
How far back the series goes is a property of the data, not a plan limit: the response carries from, count and the bars themselves, so probe one symbol once before you size a job.
The request window
fromandtoaccept a plain date (2026-10-22, read as Riyadh time) or an RFC3339 timestamp.- Omit them and the window defaults to 30 days for
1dand 7 days for every intraday interval. The defaults are small on purpose — two intraday defaults at1mwould be an accidental full download. limitdefaults to 500 bars and caps at 5000. The newest bars win when the window holds more than the limit.to=2026-10-22is read as the end of that Riyadh day, so the session is included.
symbol, interval, from, to, limit and count. Compare them with what you asked for; that is the difference between "the API returned nothing" and "I asked for a window it did not serve".One-off export to CSV
import os, requests, pandas as pd
BASE = "https://api.tasi-api.com/api/v1"
H = {"X-API-Key": os.environ["TASI_API_KEY"]}
FREE = ["2222", "1120", "7010"] # the free-tier symbols
frames = []
for sym in FREE:
r = requests.get(f"{BASE}/history/{sym}",
params={"interval": "1d", "from": "2021-10-01",
"to": "2026-10-22", "limit": 2000},
headers=H, timeout=20)
r.raise_for_status()
body = r.json()
print(sym, body["count"], body["from"], body["to"])
frames.append(pd.DataFrame(body["data"]).assign(symbol=sym))
pd.concat(frames).to_csv("tasi_daily.csv", index=False)
Three symbols is three requests — the daily cap counts requests, not bars.
API or CSV?
| Need | Use |
|---|---|
| Research, one file, nothing to maintain | CSV export |
| Dashboards, alerts, anything recurring | The API |
| Intraday work | The API — a CSV of 1m bars is stale before you finish writing it |
Timestamps and the session
Every timestamp is RFC3339 with an explicit offset. Parse it as an aware datetime and convert when you need session-local hours — never as a naive string, because a three-hour shift misaligns every intraday study and looks like a strategy result:
df["t"] = pd.to_datetime(df["t"], utc=True)
df["riyadh"] = df["t"].dt.tz_convert("Asia/Riyadh")
The Tadawul session runs Sunday to Thursday, 10:00–15:00 Asia/Riyadh (07:00–12:00 UTC). The Kingdom has no daylight saving, so the offset is a constant +03:00 — which is exactly why a naive parse does not fail loudly.
A daily bar's timestamp marks its session date, not the closing print. Do not read it as "the price at 15:00".
Rate limits
Free is 500 requests per day, resetting at 00:00 Asia/Riyadh. There is no per-minute throttle: the cap is daily, and each response on Free carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Over the cap you get 429 rate_limited with a Retry-After in seconds.
A backfill of three symbols across six intervals is eighteen requests. Polling a watchlist every second is not a Free workload — that is what Pro is for.
Errors
| Status | Code | Cause |
|---|---|---|
| 400 | invalid_interval | Interval outside the six above |
| 400 | invalid_range | from later than to, or an unparseable date |
| 403 | symbol_not_entitled | Symbol outside the plan; the body names the covered set |
| 404 | symbol_not_found | Symbol is not in the instrument table |
| 429 | rate_limited | Daily cap reached |
Full contract: the history endpoint in the docs. Building something on top of this? Start with the backtest guide, and see what each plan covers.
Next
History is one of four surfaces, and the other three answer different questions:
- Current price —
GET /quotes?symbols=2222,1120returns one row per symbol instead of a series. See quotes. - A price that pushes — the WebSocket at
wss://api.tasi-api.com/api/v1/wssends a frame when a quote changes, so nothing polls. See streaming. - Acting on a signal — your first trading bot takes the same bars and places a simulated order.
Pull your first bars in a minute.
Three Saudi stocks plus the index, free, no card.