Download the instrument master

Every tradable instrument across every exchange Zerodha supports is listed in one big CSV/dataframe, refreshed daily. You need this to resolve human-readable symbols into the instrument_token the API actually trades on.

instruments = kite.instruments()          # all exchanges, ~90k+ rows
nse_instruments = kite.instruments("NSE") # just NSE equities
nfo_instruments = kite.instruments("NFO") # just NSE F&O

Each row:

{
  "instrument_token": 408065,
  "exchange_token": 1594,
  "tradingsymbol": "INFY",
  "name": "INFOSYS LIMITED",
  "last_price": 0.0,
  "expiry": "",
  "strike": 0.0,
  "tick_size": 0.05,
  "lot_size": 1,
  "instrument_type": "EQ",
  "segment": "NSE",
  "exchange": "NSE",
}

Cache it — don't fetch on every startup

This call returns tens of thousands of rows and the data changes at most once daily (new listings, expiries rolling over). Fetch once per day and cache locally:

import pandas as pd
from pathlib import Path
from datetime import date

CACHE_PATH = Path("data/instruments_cache.parquet")

def get_instruments(kite, exchange: str | None = None) -> pd.DataFrame:
    today = date.today().isoformat()
    if CACHE_PATH.exists():
        df = pd.read_parquet(CACHE_PATH)
        if df.attrs.get("fetched_date") == today:
            return df[df.exchange == exchange] if exchange else df
    raw = kite.instruments()
    df = pd.DataFrame(raw)
    df.attrs["fetched_date"] = today
    df.to_parquet(CACHE_PATH)
    return df[df.exchange == exchange] if exchange else df

Note: df.attrs doesn't survive a parquet round-trip reliably across all pandas versions — for production, store the fetch date in a sidecar file or a separate metadata table instead of relying on attrs.

Next: 022 — Search an instrument by symbol