Persist and refresh the access token daily

Kite access tokens expire daily (~around market open, roughly 6 AM IST invalidation of the previous day's token). A long-running bot needs to detect expiry and re-auth without crashing silently.

Persist the token

# auth/token_store.py
import json, pathlib

TOKEN_FILE = pathlib.Path("tokens/session.json")

def save_token(access_token: str):
    TOKEN_FILE.parent.mkdir(exist_ok=True)
    TOKEN_FILE.write_text(json.dumps({"access_token": access_token}))

def load_token() -> str | None:
    if not TOKEN_FILE.exists():
        return None
    return json.loads(TOKEN_FILE.read_text()).get("access_token")

Validate on startup instead of blindly trusting a saved token

# auth/session.py (continued)
from kiteconnect import KiteConnect
from kiteconnect.exceptions import TokenException
from config import API_KEY
from auth.token_store import load_token, save_token

def get_authenticated_kite() -> KiteConnect:
    kite = KiteConnect(api_key=API_KEY)
    token = load_token()
    if token:
        kite.set_access_token(token)
        try:
            kite.profile()          # cheap call to verify token is live
            return kite
        except TokenException:
            pass                    # fall through to fresh login
    raise RuntimeError(
        "No valid session — run auth/login.py to log in manually."
    )

Automating the daily human step

Zerodha's 2FA requirement means full headless login needs a TOTP seed (saved when you enabled 2FA) fed programmatically:

import pyotp
totp = pyotp.TOTP(YOUR_TOTP_SECRET)
current_code = totp.now()   # use this in place of manually reading your app

Combine this with a browser-automation login (Selenium/Playwright hitting Zerodha's login form) to fully script the chapter 7-9 flow. This is common in production bots but treat the TOTP seed with the same care as api_secret — it's effectively a master key to your 2FA.

For learning, a once-daily manual login (30 seconds) is the simpler and safer choice. Automate it once you're confident in everything downstream.

Checkpoint

You now have durable, verifiable auth. Every later chapter assumes a working kite = get_authenticated_kite().

Next: 011 — Get user profile