How to write a basic Python script using kiteconnect

From WebNotes, a public knowledge base. Last updated . Reading time ~10 min. Level: Beginner.

The kiteconnect Python SDK is the official client library for the Kite Connect API from Zerodha. It wraps every REST endpoint and the WebSocket tick stream into a clean, Pythonic interface. This guide takes you from a blank directory to a working script that authenticates with Kite Connect, fetches your portfolio, queries live market prices, and places a paper-trade-style order. By the end you will have a reusable project structure suitable as the foundation for any algo trading strategy.

Project structure

A clean separation between authentication, configuration, and strategy logic makes scripts easier to maintain and test.

kite-project/
├── .env            # credentials, never commit this file
├── .gitignore
├── requirements.txt
├── auth.py         # daily token generation
├── main.py         # portfolio + market data queries
└── orders.py       # order placement helpers

Step-by-step procedure

Create a virtual environment and install dependencies

mkdir kite-project && cd kite-project
python -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate
pip install kiteconnect python-dotenv
pip freeze > requirements.txt

The virtual environment isolates project dependencies from your system Python. The python-dotenv package loads credentials from a .env file without exposing them in your code.

Store credentials securely

Create .env in the project root:

KITE_API_KEY=your_api_key_here
KITE_API_SECRET=your_api_secret_here

Create .gitignore:

.env
.venv/
__pycache__/
*.pyc
/tmp/
kite_token.json

Never commit .env to version control. If you use GitHub, add .env to .gitignore before the first commit.

Write the authentication module

Create auth.py:

"""auth.py, Kite Connect daily login flow."""

import os
import json
from pathlib import Path
from dotenv import load_dotenv
from kiteconnect import KiteConnect

load_dotenv()

API_KEY = os.environ["KITE_API_KEY"]
API_SECRET = os.environ["KITE_API_SECRET"]
TOKEN_FILE = Path("kite_token.json")


def get_kite_client() -> KiteConnect:
    """Return an authenticated KiteConnect instance for today's session."""
    kite = KiteConnect(api_key=API_KEY)

    if TOKEN_FILE.exists():
        data = json.loads(TOKEN_FILE.read_text())
        access_token = data.get("access_token")
        if access_token:
            kite.set_access_token(access_token)
            # Quick validation call; raises TokenException if stale
            try:
                kite.profile()
                return kite
            except Exception:
                pass  # Token stale; fall through to re-login

    # Interactive daily login
    print("Open this URL in a browser:")
    print(kite.login_url())
    request_token = input("Paste the request_token from the redirect URL: ").strip()

    session = kite.generate_session(request_token, api_secret=API_SECRET)
    kite.set_access_token(session["access_token"])
    TOKEN_FILE.write_text(json.dumps({"access_token": session["access_token"]}))
    print("Authenticated successfully.")
    return kite


if __name__ == "__main__":
    kite = get_kite_client()
    print("Logged in as:", kite.profile()["user_name"])

For a fully automated (headless) daily login that eliminates the manual browser step, see How to authenticate Kite Connect with TOTP automation.

Fetch portfolio and market data

Create main.py:

"""main.py, Portfolio, positions, and market data queries."""

from auth import get_kite_client

kite = get_kite_client()

# --- Profile ---
profile = kite.profile()
print(f"\nLogged in as: {profile['user_name']} ({profile['user_id']})")
print(f"Broker: {profile['broker']}")

# --- Account margins ---
margins = kite.margins()
print(f"\nEquity available margin: {margins['equity']['available']['live_balance']}")
print(f"Commodity available margin: {margins['commodity']['available']['live_balance']}")

# --- Holdings (delivery positions) ---
holdings = kite.holdings()
print(f"\nHoldings ({len(holdings)} positions):")
for h in holdings:
    print(f"  {h['tradingsymbol']:20s}  qty={h['quantity']:5d}  avg={h['average_price']:10.2f}  ltp={h['last_price']:10.2f}")

# --- Intraday positions ---
positions = kite.positions()
day = positions["day"]
net = positions["net"]
print(f"\nIntraday positions (day): {len(day)}")
for p in day:
    print(f"  {p['tradingsymbol']:20s}  qty={p['quantity']:5d}  pnl={p['pnl']:10.2f}")

# --- Live quotes ---
symbols = ["NSE:RELIANCE", "NSE:INFY", "NSE:HDFCBANK"]
quotes = kite.quote(symbols)
print("\nLive quotes:")
for sym, q in quotes.items():
    print(f"  {sym:25s}  ltp={q['last_price']:10.2f}  volume={q['volume']:10d}")

# --- Historical OHLC (last 5 days) ---
import datetime
instruments = kite.instruments("NSE")
reliance_token = next(i["instrument_token"] for i in instruments if i["tradingsymbol"] == "RELIANCE")

to_date = datetime.date.today()
from_date = to_date - datetime.timedelta(days=7)

candles = kite.historical_data(
    instrument_token=reliance_token,
    from_date=from_date,
    to_date=to_date,
    interval="day",
)
print(f"\nRELIANCE daily OHLC (last {len(candles)} sessions):")
for c in candles[-5:]:
    print(f"  {c['date'].date()}  O={c['open']:.2f}  H={c['high']:.2f}  L={c['low']:.2f}  C={c['close']:.2f}  V={c['volume']}")

Add order placement helpers

Create orders.py:

"""orders.py, Safe wrappers around kite.place_order()."""

from kiteconnect import KiteConnect
from kiteconnect.exceptions import InputException, OrderException, NetworkException


def place_market_order(
    kite: KiteConnect,
    exchange: str,
    symbol: str,
    transaction_type: str,
    quantity: int,
    product: str,
) -> str | None:
    """Place a market order and return the order_id, or None on failure."""
    if quantity <= 0:
        raise ValueError(f"Quantity must be positive, got {quantity}")
    if transaction_type not in ("BUY", "SELL"):
        raise ValueError(f"transaction_type must be BUY or SELL, got {transaction_type}")

    try:
        order_id = kite.place_order(
            variety=kite.VARIETY_REGULAR,
            exchange=exchange,
            tradingsymbol=symbol,
            transaction_type=transaction_type,
            quantity=quantity,
            product=product,
            order_type=kite.ORDER_TYPE_MARKET,
        )
        print(f"Order placed: {order_id}")
        return order_id
    except InputException as e:
        print(f"Input error: {e}")
    except OrderException as e:
        print(f"Order rejected: {e}")
    except NetworkException as e:
        print(f"Network error: {e}")
    return None


def get_order_status(kite: KiteConnect, order_id: str) -> dict | None:
    """Return the latest status dict for an order_id."""
    orders = kite.orders()
    for o in orders:
        if o["order_id"] == order_id:
            return o
    return None

To use in main.py:

from orders import place_market_order, get_order_status

# Uncomment to place a real order (ensure you have sufficient margin)
# order_id = place_market_order(kite, "NSE", "RELIANCE", "BUY", 1, "CNC")
# if order_id:
#     status = get_order_status(kite, order_id)
#     print("Order status:", status["status"])

Run the script

# With virtual environment active:
python auth.py     # authenticate once; token saved to kite_token.json
python main.py     # fetch portfolio and market data

On subsequent runs during the same trading day, auth.py loads the cached token and main.py runs without prompting.

Common SDK methods reference

MethodReturnsNotes
kite.profile()dictUser name, email, broker, exchanges, products
kite.margins()dictAvailable and used margin for equity and commodity
kite.holdings()listDelivery portfolio with average price and LTP
kite.positions()dictDay and net intraday positions
kite.orders()listAll orders placed today
kite.trades()listAll executed trades today
kite.quote(symbols)dictLive snapshot for a list of exchange:symbol strings
kite.ohlc(symbols)dictOHLC-only snapshot (lighter than quote)
kite.ltp(symbols)dictLTP-only snapshot (lightest)
kite.instruments(exchange)listAll tradable instruments on the exchange
kite.historical_data(...)listOHLC candles; intervals: minute, 3minute, 5minute, 15minute, 30minute, 60minute, day

What can go wrong

  • ModuleNotFoundError: No module named 'kiteconnect'. The virtual environment is not activated, or pip install kiteconnect was run in the system Python rather than the virtual environment. Activate .venv and reinstall.
  • KeyError: 'KITE_API_KEY'. The .env file is missing, in the wrong directory, or load_dotenv() was not called before reading os.environ. Ensure load_dotenv() is called at the top of the module.
  • TokenException on the validation call in get_kite_client. The saved token has expired (after 6:00 AM IST). Delete kite_token.json and run auth.py again for a fresh login.
  • Empty holdings or positions. Correct behaviour if your account has no holdings or no open intraday positions. The API returns an empty list, not an error.
  • DataException on historical_data. Historical data is not available for all instruments (for example, very new listings or delisted stocks) or for intervals shorter than the instrument’s trading history.
  • Rate limit errors. If your script calls multiple REST endpoints in a loop, you may hit the 10 requests-per-second limit. Add time.sleep(0.1) between calls.

References

  1. Zerodha, Kite Connect developer documentation, kite.trade/docs/connect/, accessed 2024.
  2. kiteconnect Python SDK repository and README, github.com/zerodha/pykiteconnect, accessed 2024.
  3. PyPI, kiteconnect package, pypi.org/project/kiteconnect/, accessed 2024.
  4. SEBI, Circular on algorithmic trading by retail investors, SEBI/HO/MRD/2021 series, sebi.gov.in.
  5. Zerodha Z-Connect blog, Building with Kite Connect, zerodha.com/z-connect.

Reviewed and published by

The WebNotes Editorial Team covers Indian capital markets, payments infrastructure and retail investor procedures. Every article is fact-checked against primary sources, principally SEBI circulars and master directions, NPCI specifications and the official support documentation published by the intermediary in question. Drafts go through a second-pair-of-eyes review and a separate compliance read before publication, and revisions are tracked against the SEBI and NPCI rule changes referenced in the methodology section.

Last reviewed
Conflicts of interest
WebNotes is independent. No relationship with any broker, registrar or bank named in this article.