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

IntervalServed from
1mthe base one-minute series
5m · 15m · 30m · 1haggregated server-side from the 1m series, open/high/low/close rolled correctly
1dthe 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

The response echoes 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?

NeedUse
Research, one file, nothing to maintainCSV export
Dashboards, alerts, anything recurringThe API
Intraday workThe 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

StatusCodeCause
400invalid_intervalInterval outside the six above
400invalid_rangefrom later than to, or an unparseable date
403symbol_not_entitledSymbol outside the plan; the body names the covered set
404symbol_not_foundSymbol is not in the instrument table
429rate_limitedDaily 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:

Pull your first bars in a minute.

Three Saudi stocks plus the index, free, no card.