Zerodha Kite Connect API with Python: a Real Tutorial

The short answer

The Kite Connect API lets a Python program authenticate once a day, read market data, and place, modify, and cancel orders through Zerodha using the official kiteconnect client. You exchange an API key and secret for a daily access token through a login handshake, then call place_order for trades and stream live prices over the KiteTicker WebSocket. Since the SEBI framework dated 4 February 2025, automated orders above an order-frequency threshold count as algorithmic and must be tagged with an exchange algo ID through your broker.

A working Kite Connect integration is a small amount of code around one hard truth: the API will do exactly what you tell it, instantly, with real money, whether or not you meant it. This tutorial walks the full pipeline as code, developer setup and the auth handshake, instruments and market data, orders and their real states, the live tick stream, and the risk layer you have to build yourself. It ends where most tutorials do not begin: the SEBI algorithmic-trading rules that decide whether your script is even allowed to run as it grows.

Everything below uses the official Python client, installed with a single command. Keep the current Kite Connect documentation open alongside it, because rate limits, endpoint access packs, and the exact token lifetime are set by Zerodha and the exchanges and can change without changing this article.

pip install kiteconnect

The authentication handshake

Kite Connect does not accept a username and password from your code. It uses a browser login handshake that ends in a short-lived token, and it is the part first-time developers get wrong most often. There are three secrets in play and they are not interchangeable. The API key publicly identifies your app. The API secret proves you are its registered developer and is shown once at creation, so save it then. The access token is the daily session key that actually authorises trading calls.

The flow is a sequence. You generate a login URL and send the user to it. They log in on Zerodha's own page, which is where two-factor authentication happens, and Kite redirects back to your app with a one-time request_token in the query string. You then exchange that request token for an access token. Under the hood the client builds a SHA-256 checksum of the API key, the request token, and the API secret concatenated together, and posts it so Zerodha can verify the exchange came from you. In Python the client does the checksum for you inside generate_session.

The Kite Connect authentication handshake Your app builds a login URL from the API key. The user logs in on Zerodha, clears two-factor authentication, and is redirected back with a one-time request token. Your app posts a SHA-256 checksum of the API key, request token, and secret to obtain a daily access token that signs all later calls and expires the next trading day. One handshake a day, one token to sign every call Your Python app Zerodha login page Kite token API 1. login_url() sends user to Zerodha 2. user logs in + 2FA 3. redirect back with request_token 4. SHA-256(key + token + secret) 5. generate_session() posts the checksum 6. returns daily access_token access_token signs every authenticated call, then expires the next trading day: regenerate daily
The token is the whole ballgame. Steps 1 to 3 happen in a browser and cannot be skipped, because two-factor authentication is enforced on Zerodha's page, not in your code. Steps 4 to 6 are automatic inside generate_session. The token you receive is good for one trading day, which is why a live system needs a login step every morning.

In code the handshake is short. The one part you must supply outside the snippet is capturing the request_token that Kite appends to your redirect URL, which in local development is often a loopback address you read from the browser.

from kiteconnect import KiteConnect

kite = KiteConnect(api_key="your_api_key")

# 1. Send the user to this URL in a browser.
print(kite.login_url())

# 2. Kite redirects to your redirect URL with ?request_token=...
#    Capture that value, then exchange it (the client builds the
#    SHA-256 checksum of api_key + request_token + api_secret for you):
data = kite.generate_session("request_token_from_redirect",
                             api_secret="your_api_secret")

access_token = data["access_token"]
kite.set_access_token(access_token)   # signs every later call
The daily-expiry trap. An access token is a single-day session token: Zerodha documents it as valid until the start of the next trading day, then invalidated. A system that boots at 09:15 with yesterday's token fails on its first call with a token error. In production this becomes a scheduled morning login, often automated with a scripted two-factor flow, that fetches a fresh token before the open and stores it out of source control. Verify the exact lifetime against the current documentation, since it is set by Zerodha, not by this page.

Instruments and market data

Kite identifies every tradable contract by a numeric instrument_token, not by its trading symbol. The market-data and streaming endpoints speak in these tokens, so the first thing a real system does is download the instrument master with instruments(), which returns the full dump of symbols, tokens, exchanges, expiry, and lot size. You cache it once a day and look up the token for whatever you intend to trade. Symbols and tokens are refreshed by the exchange, so treat the dump as a daily artefact rather than a constant.

For prices you have two lanes. Snapshot reads over REST answer "what is it now": ltp() for the last traded price, ohlc() for the day's open, high, low, and close, and quote() for the fuller picture including depth. Historical candles come from historical_data(), which takes an instrument token, a date range, and an interval such as minute or day, and is the backbone of any backtest. Historical access is a paid add-on on the developer console and is separate from the basic connect subscription, so confirm your app is entitled before your backtest silently returns nothing.

# Look up a token from the daily instrument dump.
dump = kite.instruments("NSE")
token = next(i["instrument_token"] for i in dump
             if i["tradingsymbol"] == "YOUR_SYMBOL")

# Snapshot reads (REST).
print(kite.ltp([f"NSE:YOUR_SYMBOL"]))    # last traded price
print(kite.ohlc([f"NSE:YOUR_SYMBOL"]))   # day OHLC

# Historical candles for a backtest (requires the historical-data pack).
candles = kite.historical_data(
    instrument_token=token,
    from_date="2026-06-01 09:15:00",
    to_date="2026-06-30 15:30:00",
    interval="minute",
)

That candle dataframe is where a strategy is actually decided, long before an order is ever sent. Turning raw candles into a tested edge is a discipline in itself, and the pandas backtesting walkthrough covers how to do it without fooling yourself with lookahead. That upstream research work is exactly what the method we teach is built around: the API is only the delivery mechanism for a decision you already trust.

Placing and managing orders

An order in Kite Connect is a set of enumerated choices, and the client exposes each as a constant so you do not hardcode strings. You pass a variety (the order class, for example a regular order), an exchange, a tradingsymbol, a transaction_type (buy or sell), a quantity, a product that decides how the position is treated, an order_type that decides how it is priced, and a validity. The parameters below are the ones you touch daily.

The order parameters you set on every place_order call. Constants are exposed on the client, for example kite.PRODUCT_MIS.
ParameterCommon valuesWhat it decides
varietyVARIETY_REGULAR, VARIETY_AMO, VARIETY_ICEBERGThe order class. Regular is the everyday order; AMO is after-market; iceberg slices a large order.
productPRODUCT_MIS, PRODUCT_CNC, PRODUCT_NRMLMIS is intraday and auto-squared before close. CNC is delivery for equity. NRML carries derivatives positions.
order_typeORDER_TYPE_MARKET, ORDER_TYPE_LIMIT, ORDER_TYPE_SL, ORDER_TYPE_SLMMarket fills now at the best price. Limit rests at your price. SL and SL-M are stop-triggered limit and market orders.
validityVALIDITY_DAY, VALIDITY_IOC, VALIDITY_TTLDay rests until close. IOC fills what it can immediately then cancels. TTL lives for a set number of minutes.
# tradingsymbol is a placeholder. This is not advice to trade any instrument.
order_id = kite.place_order(
    variety=kite.VARIETY_REGULAR,
    exchange=kite.EXCHANGE_NSE,
    tradingsymbol="YOUR_SYMBOL",
    transaction_type=kite.TRANSACTION_TYPE_BUY,
    quantity=1,
    product=kite.PRODUCT_MIS,        # intraday
    order_type=kite.ORDER_TYPE_LIMIT,
    price=your_limit_price,          # a number you set
    validity=kite.VALIDITY_DAY,
    tag="strategy_v1",               # your own label for reconciliation
)

Here is the single most important sentence in this tutorial: the returned order_id is not a fill. It confirms only that Zerodha's order management system accepted your request for processing. From there the order walks through a lifecycle, and each of those states is a different reality for your position. Treating "I got an order_id" as "I am in the trade" is how people end up with positions they did not know existed, or with duplicate orders after a timeout.

An order_id is a receipt, not a fill place_order returns an order_id meaning the request was received and validation is pending. The order becomes open at the exchange, then resolves to complete, cancelled, or rejected. You confirm the terminal state with order_history or the order postback stream, never by assuming the order_id means execution. The order lifecycle behind a single order_id place_order() returns order_id VALIDATION pending at OMS OPEN live at exchange COMPLETE (filled) CANCELLED REJECTED The order_id proves receipt, not execution. Confirm the terminal state with kite.order_history(order_id) or the order postback stream before you act on it.
Confirm, do not assume. After every write, read the truth back. kite.order_history(order_id) returns the full state trail; kite.orders() lists the working set, kite.trades() the fills, and kite.positions() and kite.holdings() your book. For low latency, register the order postback so Zerodha pushes each state change to you instead of polling.

Modifying and cancelling follow the same shape. kite.modify_order(variety, order_id, price=new_price) changes a resting order in place; kite.cancel_order(variety, order_id) pulls it. The tag you attached at placement earns its keep here: after a network timeout you can query for that tag and learn whether the order actually reached the exchange, instead of guessing and firing a duplicate.

Streaming live ticks with KiteTicker

REST reads are snapshots. To react to the market as it moves you need the push feed, and Kite delivers it over a WebSocket exposed as the KiteTicker class. You construct it with the API key and today's access token, register callbacks, subscribe to a list of instrument tokens, and pick a mode. LTP mode sends only the last price and is the lightest. Quote mode adds open, high, low, close, and volume. Full mode adds five levels of order-book depth. You choose the smallest mode that your strategy actually uses, because bandwidth and parsing cost scale with it.

from kiteconnect import KiteTicker

kws = KiteTicker("your_api_key", access_token)

def on_connect(ws, response):
    ws.subscribe([token])                 # instrument token, an int
    ws.set_mode(ws.MODE_FULL, [token])    # LTP, QUOTE, or FULL

def on_ticks(ws, ticks):
    for t in ticks:
        handle_tick(t)                    # your strategy logic

def on_close(ws, code, reason):
    # The socket WILL drop. Pause strategies and let reconnect run.
    pause_all_strategies()

kws.on_connect = on_connect
kws.on_ticks = on_ticks
kws.on_close = on_close
kws.connect(threaded=True)
The silent-staleness failure. The WebSocket disconnects, routinely, and the dangerous version is the one you do not notice: your strategy keeps computing on the last tick it saw and acts on a price that is minutes old. A live feed needs an explicit reconnect discipline: detect the drop, pause every strategy, reconnect with exponential backoff, resubscribe to all previously subscribed tokens, reconcile your positions against the broker over REST, and only then resume. Skipping the resubscribe or the reconcile is how a stale feed quietly trades your account.

The scoop: SEBI's algo rules now sit on top of your code

This is the section that dates every generic Kite tutorial. Placing orders programmatically in India is no longer purely a technical question. SEBI issued a framework titled Safer participation of retail investors in Algorithmic trading on 4 February 2025, and it draws a bright line around exactly the kind of automated order flow this tutorial produces. The implementation timeline was extended through a September 2025 circular, with reporting pointing to full go-live around 1 April 2026. Because the phasing has already moved once, treat any single date as verify-before-relying and check the current SEBI and exchange circulars.

The core mechanic is a frequency line. Orders placed through a broker API are treated as algorithmic once they cross an order-per-second threshold, reported to be in the region of ten orders per second per client, at which point each order must carry a unique exchange-assigned algo ID. Below that line, an occasional API user placing manual-speed orders is generally not caught; above it, the flow is an algorithm in the regulator's eyes and must be registered and tagged. The exact number is set by the exchanges, so confirm it rather than hardcoding it into your risk logic.

How an API order becomes a registered algo Your code sends orders through the broker API. Below the exchange-set order-rate threshold the flow is treated as regular API activity. Above the threshold, reported near ten orders per second per client, the flow is algorithmic: the broker tags each order with a unique exchange algo ID that the exchange can trace. The broker is the principal that empanels the algo, and client-specific keys, static IP whitelisting, and two-factor authentication are required. The rate threshold decides what your order legally is Your Python code place_order() Broker API measures order rate order rate vs threshold? below regular API flow, no algo ID above (~10/sec) treated as an algorithm broker tags a unique exchange algo ID The broker is the principal. The broker empanels and onboards the algo with the exchange; you cannot register directly. Access is hardened: client-specific keys, static IP whitelisting, and two-factor authentication. All figures are exchange and broker set, verify before relying.
Registration is your broker's job, and your obligation. Because the broker is the principal, your algo is empanelled and tagged through them, not with the exchange directly. The same framework hardens API access itself: client-specific keys, static or whitelisted IPs, and two-factor authentication, so open, shared keys are out.

Two more pieces matter for a developer. First, access hardening: the framework pushes brokers toward client-specific API keys, static or whitelisted IP addresses, and mandatory two-factor authentication, which is one reason the daily login handshake above cannot be shortcut. Second, the classification of the strategy itself. A white-box algo has transparent, reproducible logic, a readable Python strategy is usually this kind, while a black-box algo hides its logic and carries heavier scrutiny and documentation. The table below summarises the realities you should design around, every figure flagged as something to confirm against current circulars.

The regulatory and operational realities that sit on top of the Kite API. Treat every specific figure as current-verify, because the exchanges and Zerodha set them.
RealityWhat it means for your codeStatus
Daily access tokenRegenerate the token every trading day before the open; a stale token fails the first call.Set by Zerodha; verify lifetime in the docs.
Order-rate thresholdCross it and each order needs a unique exchange algo ID, tagged through the broker.Reported near ten per second per client; exchange-set, verify.
Broker as principalYour algo is empanelled and onboarded through the broker, not registered directly with the exchange.Framework dated 4 Feb 2025; phased go-live, verify current date.
Access hardeningExpect client-specific keys, static or whitelisted IPs, and enforced two-factor login.Per SEBI framework; confirm your broker's implementation.
Per-endpoint rate limitsOrder, quote, and historical calls are capped separately; a breach is rejected, not queued.Published by Zerodha; treat any number as verify-before-relying.

The risk and kill-switch layer you build yourself

Nothing in the client stops your program from doing something catastrophic. The API is deliberately neutral, so the guardrails are yours to write, and this is the layer that separates a script from a system. Its whole purpose is to survive the failure modes that are unique to automation: a token that expired mid-session, a WebSocket feeding old prices, a rate-limit rejection mistaken for a transient blip, and above all a retry that turns one intended order into several real ones.

At minimum, a live retail setup wraps every outbound call in a token-bucket rate limiter per endpoint category, so it backs off instead of tripping limits. It refuses to retry any write, place, modify, or cancel, without first reading the current state, because a blind retry after a timeout is the fastest way to double a position. It carries a hard cap on orders per minute and on total exposure that the strategy cannot override. And it has a kill switch: a single, non-overridable control that halts all new orders and, if configured, flattens open positions, triggered either by a monitored loss limit or by a human.

# The smallest useful guard: a manual, non-overridable kill switch.
class KillSwitch:
    def __init__(self):
        self._tripped = False

    def trip(self, reason):
        self._tripped = True
        log.critical("KILL SWITCH: %s", reason)

    def guard(self):
        if self._tripped:
            raise RuntimeError("kill switch engaged, orders blocked")

kill = KillSwitch()

def safe_place(**params):
    kill.guard()                      # blocks if engaged
    limiter.acquire("orders")         # per-endpoint token bucket
    return kite.place_order(**params) # only now touch the broker
Why this is the real deliverable. A bug in a manual workflow costs one misclick. A bug in an automated one can fire many orders before you can react, which is the operational risk that API trading adds and manual trading does not. The strategy is the interesting part; the risk layer is the part that keeps a live account solvent long enough for the strategy to matter. Sequencing that judgement before any capital goes near the API is the point of a structured path rather than a copied snippet.

Where this sits in a real workflow

Put the pieces in order and the pipeline is coherent: authenticate for the day, load the instrument dump, pull historical candles to drive a strategy you have already tested, place orders through the guarded wrapper, confirm every outcome by reading state back, stream live ticks over KiteTicker to react, and register the whole thing as an algo with your broker once its order rate warrants it. The API is a few dozen method calls. The engineering is everything wrapped around them, and the regulation now sits on top of both.

If you are building this to learn rather than to reach a specific target overnight, sequence it: paper-trade the logic, add the risk layer before real capital, and read the current circulars before you scale order rate. That progression, research first, execution second, compliance throughout, is the spine of the broader algorithmic-trading path in India and of how to start algo trading here without skipping the steps that protect your account.

Frequently asked questions

You register an app to get an API key and secret, then call login_url() and send the user there. After login, Kite redirects back with a request_token in the query string. You pass that token and your secret to generate_session(), which the client uses to build a SHA-256 checksum of api_key plus request_token plus api_secret, POST it, and return a fresh access_token. You set that token with set_access_token(), and it signs every later request until it expires the next morning.

An access token is a single-day session token. Zerodha documents it as valid until the start of the next trading day, after which it is invalidated and any call using it returns a token error. A live system therefore needs a login step every morning before the market opens, either run by hand or automated with a scripted two-factor login. Treat token regeneration as a daily prerequisite, not an occasional refresh, and verify the current expiry behaviour against the official documentation.

place_order returns an order_id, which only confirms that the order management system accepted your request for processing. It is not a fill and not even an exchange acknowledgement. The order then moves through states such as validation pending, open, and finally complete, cancelled, or rejected. You must confirm the outcome separately by reading order_history(order_id) or by listening to the order postback stream. Assuming a returned order_id means execution is the classic beginner mistake.

Under the SEBI framework dated 4 February 2025, automated orders placed through a broker API above an order-frequency threshold are treated as algorithmic and must be tagged with a unique exchange algo ID, registered through the broker. Reporting put the threshold near ten orders per second per client, but the exact number and go-live are exchange and broker set, so verify current limits before relying on them. Your broker is the principal who empanels and tags the algo; you cannot register directly with the exchange.

The framework distinguishes algos by whether their logic is transparent to the user. A white-box or execution algo has fully visible, reproducible logic, such as a simple time-sliced or price-limit execution, and carries a lighter registration path. A black-box algo hides its logic and faces stricter scrutiny and additional documentation obligations. A retail developer writing an open, readable strategy in Python is normally building the white-box kind, but the classification and its obligations are set by the exchange and broker.

Live ticks come from the KiteTicker WebSocket, not the REST endpoints. You create a KiteTicker with your API key and access token, attach on_connect and on_ticks callbacks, subscribe to a list of instrument tokens, and choose a mode. LTP mode sends only the last price, quote mode adds open, high, low, close and volume, and full mode adds five levels of market depth. The stream can drop, so you must handle disconnects, resubscribe on reconnect, and reconcile state before you trust it again.

You call place_order with a variety, exchange, tradingsymbol, transaction_type, quantity, product, order_type, and validity, and it returns an order_id. To change a resting order you call modify_order with the same variety and that order_id plus the fields you want to update, such as price or quantity. To pull an order you call cancel_order with the variety and order_id. You inspect the working set with orders(), the fills with trades(), and your book with positions() and holdings().

Because code fires without hesitation. A manual trader who sees a wrong price pauses; a loop that misreads a tick or catches an error and retries can place many wrong orders in seconds. The failure modes are software failure modes: a stale token, a dropped WebSocket feeding old prices, a rate-limit rejection mistaken for a network blip, or a retry that duplicates a live order. This is why a self-built risk layer, a hard order cap, and a kill switch matter more than the strategy itself.

Kite Connect applies per-endpoint request limits, with order placement, quote reads, and historical reads each capped separately, and exceeding a limit returns a rejection rather than a queue. Published numbers change, so treat any specific figure as current-verify and read the official documentation before trusting it. In practice you wrap outbound calls in a token-bucket limiter per endpoint category, back off on a rejection instead of hammering, and never retry a write blindly, since a duplicate order is worse than a delayed one.

Where the facts come from

  • SEBI, Safer participation of retail investors in Algorithmic trading (4 February 2025). The circular that treats API orders above a frequency threshold as algorithmic and requires unique exchange algo IDs, broker empanelment, and hardened API access. sebi.gov.in
  • SEBI, Extension of timeline for the algo trading framework (September 2025). Establishes that the implementation was phased and pushed out, which is why the go-live date should be verified against current circulars. sebi.gov.in
  • Kite Connect v3 API documentation. The authoritative reference for the auth handshake, endpoints, order parameters, and current rate limits. kite.trade/docs/connect/v3
  • pykiteconnect, the official Python client. Source and reference for KiteConnect and KiteTicker method names, constants, and behaviour used throughout this tutorial. github.com/zerodha/pykiteconnect
Educational note. This guide explains how the Kite Connect API works with Python and the regulatory rules that govern automated trading in India. It is not a recommendation to trade or invest, not affiliated with or endorsed by Zerodha, and not investment advice. API trading adds operational risk, because a software bug can place many orders in seconds. Always verify current rate limits, token behaviour, and regulatory requirements against the official documentation and SEBI and exchange circulars. Bharath Shiksha is an educational publisher, not a SEBI-registered investment adviser or research analyst.

Related guides

Ready to go deeper than this article?

Bharath Shiksha is a 30-volume curriculum across 6 stages, from chart reading at ₹14,999 through research-to-production and live deployment, or the full bundle at ₹1,49,999. Every volume has a companion worksheet, a gate quiz, and a 7-day money-back guarantee. Start with a free diagnostic to see where you sit.

Take the free diagnostic →