Skip to content
HostEthical
All guides

Guides 15 min read ·

Zerodha Kite Connect API in Python: Run Your Bot on a VPS

Run a Zerodha Kite Connect bot in Python on a VPS: plans and price, the static IP whitelist, the daily login, a first LIMIT order, KiteTicker and systemd.

To run a Zerodha trading bot on a server, you need three things: a Kite Connect app, your server's static IP on Zerodha's IP whitelist, and a fresh login every trading morning. As of October 2026, the Personal plan is free and covers orders, positions and holdings. The Connect plan costs ₹500 a month and adds live WebSocket prices and historical data. On both plans the access token expires at 6 AM the next day, and Zerodha says the daily login must be manual.

The examples use the official kiteconnect Python library, version 5.2.2, the latest on PyPI in October 2026, on a Linux VPS with no screen.

Before you start. Zerodha has no sandbox for Kite Connect, so every order in these examples is a real order. Change the symbol, quantity and price before you run anything. Nothing here is investment advice: the code shows how the API works, not what to trade.

Kite Connect plans and price in October 2026

Zerodha sells two plans, described on its API product page and sign-up guide, as of October 2026.

PlanPriceIncludesLeaves out
PersonalFreeOrders, GTT and alerts, margin calculation, positions, holdings and fundsLive market data, including WebSocket streaming, and historical candles
Connect₹500 a month per API keyEverything in Personal, plus live WebSocket streaming and historical candle data–

Zerodha's Kite Connect FAQ spells out the difference. A Personal app can't connect to the WebSocket at all (you get a 403 error), and its market data calls fail with an "Insufficient permission" error. The same errors appear if a Connect subscription lapses. If your bot reacts to live prices from Kite, you need Connect. If its signals come from somewhere else, such as your own data or a TradingView alert, Personal is enough to place orders.

From October 2026, paid users who generate ₹2,000 or more in brokerage in a calendar month receive 500 Developer Credits, worth ₹500, which pay for the next month's renewal. You pay on the console's Billing page, by linking your Kite account or through Razorpay (UPI, card or netbanking). Zerodha doesn't give coding help by phone or ticket, so take technical questions to the Kite Connect forum.

Create the developer app and choose a redirect URL

  1. Sign up at developers.kite.trade/signup, ideally with the email address linked to your Zerodha account, and choose a plan.
  2. Open My Apps and click Create New App.
  3. Fill in an app name, your Zerodha client ID, a redirect URL, an optional postback URL and a short description.
  4. Save the app. You get an API key, which identifies the app, and an API secret, which you must keep private.

The redirect URL is where Zerodha sends your browser, with a one-time request_token, after you log in. Zerodha's sign-up guide suggests a localhost address such as http://127.0.0.1:8000 for testing. That address also works for a personal bot on a VPS, as the login section below shows. If you want a public redirect URL instead, it must be an HTTPS page that you run on your own domain.

The postback URL is optional. Zerodha sends order updates to it, which means you'd have to run a public HTTPS endpoint. On the Connect plan, the same order updates also arrive over the WebSocket, so many bots leave this field empty. If you see the error "User not enabled for the app", the client ID on the app is wrong.

Whitelist your VPS's static IP with Zerodha

Since 1 April 2026, Zerodha has accepted API orders only from an IP address you have whitelisted, according to its static IP guide. On an Algo VPS, one command shows the address to register:

algo-ip
# IPv4 (register this with your broker): 203.0.113.10
# IPv6: ...
# Address your Python bots use right now: 203.0.113.10

In the developer console, open Profile, enter the IPv4 address under IP Whitelist, and click Update. As of October 2026, Zerodha's support pages say:

  • You must add one primary IP, and you can add a second as a backup.
  • You can make one change per calendar week. Each change takes effect immediately.
  • The whitelist applies to your whole developer account and covers every app under it.
  • Both IPv4 and IPv6 addresses are accepted.
  • Only order placement is checked. The WebSocket, order book and positions work from any IP.
  • You declare that only you and your immediate family will use the IP. Zerodha warns that sharing it more widely can get your Kite Connect account and all its API keys suspended.

The common trap is IPv6. Zerodha's FAQ notes that if your requests leave over IPv6 while only your IPv4 address is whitelisted, your orders are rejected. Algo VPS is set to prefer IPv4 for outgoing connections, and algo-ip warns you if Python is connecting from a different address. Our static IP guide for algo trading explains the IPv6 problem, family accounts and how the other brokers handle the same rule.

A whitelistable IP with Kite Connect ready to go. Algo VPS Mumbai has its own dedicated IPv4 address, IPv4-first networking and kiteconnect preinstalled in /opt/algo/venv. See Algo VPS →

The daily login, and why this guide doesn't automate it

Zerodha's login documentation describes the flow:

  1. Your code prints kite.login_url(), which points to https://kite.zerodha.com/connect/login?api_key=...&v=3.
  2. You open that link in a browser and log in with your user ID, password and TOTP code.
  3. Zerodha sends the browser to your redirect URL with request_token=... in the address. The token works once and expires after a few minutes.
  4. Your code calls generate_session(request_token, api_secret). The SDK computes the required checksum (a SHA-256 hash of the API key, request token and API secret) and gets back an access_token, which signs every later API call.

The access token lasts until 6 AM the next day, which Zerodha's docs call a regulatory requirement. It stops working sooner if you log out through the API or do a master logout from Kite Web. It also stops if you start a new session with the same API key, so a quick test login from your laptop kills the token your server is using.

Can you automate the login? Many scripts online fill in the login form with a saved password and a TOTP secret. For years, Zerodha has advised against this on its official forum. In July 2026 a forum moderator wrote: "As per exchange requirements, users must log in manually at least once each day. For this reason, we do not recommend automating the Kite Connect login flow." In the same reply, the moderator said Zerodha is exploring ways to make token generation "more seamless". Forum administrator sujith gave the same answer in 2021 and 2022. An August 2026 reply added that you can't batch the logins of several family accounts: each one has to log in separately.

This guide sticks to that: you log in by hand each morning, and the server does the rest. Algo VPS comes with pyotp installed, but don't use it to script the Kite login, and don't store your Zerodha password or TOTP secret on the server. Our guide to SEBI's algo trading rules covers the wider framework, including the daily logout.

A morning login routine for a server with no screen

The VPS has no browser, but your laptop or phone does. Your browser does the login and passes only the short-lived request_token to the server. Your API secret never leaves the server, and the request token is useless without it.

1. Put your keys in /opt/algo/.env

sudo nano /opt/algo/.env

KITE_API_KEY=your_api_key
KITE_API_SECRET=your_api_secret

The file is already readable only by root (chmod 600). Write the values without quotes. Both the algo@ service and the login helper below read this file.

2. Save the login helper

Save this as /opt/algo/kite_login.py:

# /opt/algo/kite_login.py: run once each trading morning, after 6 AM IST
import json, os, sys
from datetime import date
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import parse_qs, urlparse
from kiteconnect import KiteConnect

TOKEN_FILE = "/opt/algo/kite_token.json"

for line in open("/opt/algo/.env"):              # load KITE_API_KEY and KITE_API_SECRET
    if "=" in line and not line.lstrip().startswith("#"):
        key, value = line.strip().split("=", 1)
        os.environ.setdefault(key, value)

kite = KiteConnect(api_key=os.environ["KITE_API_KEY"])

def save_session(request_token):
    session = kite.generate_session(request_token, api_secret=os.environ["KITE_API_SECRET"])
    fd = os.open(TOKEN_FILE, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
    with os.fdopen(fd, "w") as f:
        json.dump({"date": date.today().isoformat(), "access_token": session["access_token"]}, f)
    print("Logged in as", session["user_id"])

if len(sys.argv) > 1:                            # request_token pasted by hand
    save_session(sys.argv[1])
    sys.exit()

class Redirect(BaseHTTPRequestHandler):
    def do_GET(self):
        token = parse_qs(urlparse(self.path).query).get("request_token", [""])[0]
        if token:
            save_session(token)
            self.server.done = True
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b"Logged in. You can close this tab." if token else b"No request_token here.")

print("Open this link and log in:\n" + kite.login_url())
server = HTTPServer(("127.0.0.1", 8000), Redirect)
server.done = False
while not server.done:
    server.handle_request()

3. Log in each morning

Set the app's redirect URL to http://127.0.0.1:8000. From your laptop, open an SSH session that forwards port 8000, then run the helper:

ssh -L 8000:127.0.0.1:8000 root@your-server-ip
sudo /opt/algo/venv/bin/python /opt/algo/kite_login.py

Open the link the helper prints and log in. Zerodha then sends your browser to http://127.0.0.1:8000. The SSH tunnel carries that request to the helper on the server, which exchanges the token and saves the day's access token to /opt/algo/kite_token.json, a file only root can read. No firewall port is opened: the helper listens only on the server's loopback address.

No laptop to hand? Open the login link on any device. When the login finishes, the browser shows a "can't connect" page for 127.0.0.1, but the address bar still contains request_token=.... Copy that value, and within a few minutes run sudo /opt/algo/venv/bin/python /opt/algo/kite_login.py YOUR_REQUEST_TOKEN. An SSH app on your phone is enough for this.

Want to log in with one tap? Use an HTTPS redirect URL on your own domain, served by nginx with a Let's Encrypt certificate. This means opening port 443. The setup is the same as for the webhook receiver in our TradingView webhook guide. Whichever method you use, never print or share the access token.

Place a LIMIT order and read positions with kiteconnect

On Algo VPS, kiteconnect is already installed in /opt/algo/venv. Check the version, and upgrade when Zerodha releases a new one:

/opt/algo/venv/bin/pip show kiteconnect
sudo /opt/algo/venv/bin/pip install --upgrade kiteconnect

Save this as /opt/algo/kitebot.py. It waits for today's login, places one LIMIT order, and logs your positions:

# /opt/algo/kitebot.py: start with  sudo systemctl enable --now algo@kitebot
import json, logging, os, sys, time
from datetime import date
from kiteconnect import KiteConnect, exceptions

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
TOKEN_FILE = "/opt/algo/kite_token.json"
TAG = "vpsdemo"                                  # alphanumeric, up to 20 characters

def todays_token():
    while True:                                  # wait until kite_login.py has run today
        try:
            with open(TOKEN_FILE) as f:
                saved = json.load(f)
            if saved["date"] == date.today().isoformat():
                return saved["access_token"]
        except FileNotFoundError:
            pass
        logging.info("No Kite login yet today: run kite_login.py")
        time.sleep(60)

access_token = todays_token()
kite = KiteConnect(api_key=os.environ["KITE_API_KEY"])
kite.set_access_token(access_token)

try:
    already_sent = any(o["tag"] == TAG and o["status"] != "REJECTED" for o in kite.orders())
    if not already_sent:
        order_id = kite.place_order(             # a REAL order: change these values first
            variety=kite.VARIETY_REGULAR,
            exchange=kite.EXCHANGE_NSE,
            tradingsymbol="INFY",
            transaction_type=kite.TRANSACTION_TYPE_BUY,
            quantity=1,
            product=kite.PRODUCT_CNC,
            order_type=kite.ORDER_TYPE_LIMIT,
            price=1500,
            validity=kite.VALIDITY_DAY,
            tag=TAG,
        )
        logging.info("Order accepted by Kite: %s", order_id)
    for p in kite.positions()["net"]:
        logging.info("%s %s qty=%s pnl=%s", p["exchange"], p["tradingsymbol"], p["quantity"], p["pnl"])
except exceptions.TokenException:
    logging.error("Session expired or replaced: run kite_login.py again")
    os.remove(TOKEN_FILE)                        # after the restart, the bot waits for a new login
    sys.exit(1)
except (exceptions.InputException, exceptions.PermissionException) as e:
    logging.error("Order refused, not retrying: %s", e)
    sys.exit(0)                                  # a clean exit, so systemd doesn't resend it

A few details matter here:

  • It's safe to restart. Before placing the order, the bot checks the day's order book for its own tag, so a restart doesn't send a second copy.
  • An order ID isn't a fill. Kite returns an order_id once the order is registered with its order management system. Whether the order executes depends on funds, risk checks and the market, so check kite.order_history(order_id) or listen for order updates.
  • Errors don't loop. A refused order exits cleanly, so systemd doesn't resend it. An expired session deletes the token file and exits with an error, so systemd restarts the bot and it waits for your next login.

As of October 2026, the orders documentation and FAQ add the following:

  • Varieties: regular, amo (after-market), co (cover), iceberg and auction. Validity can be DAY, IOC or TTL (in minutes), but MCX doesn't accept IOC in the algo segment.
  • Market orders need market protection. Zerodha's FAQ says MARKET orders with a market_protection of 0 are rejected. Pass market_protection=-1 for automatic protection, or a percentage up to 100. It applies only to MARKET and SL-M orders.
  • Algo ID: place_order also accepts an optional algo_id. Exchange algo IDs come with registering an algo, which Zerodha says you need only above 10 orders a second. Below that, leave the field out.

Stream live prices with KiteTicker

KiteTicker is the SDK's WebSocket client, and it needs the paid Connect plan. Zerodha's WebSocket documentation allows up to 3,000 instruments on one connection and up to 3 connections per API key. Each instrument streams in one of three modes: ltp (last price only), quote, or full, which adds market depth. Add this to the end of kitebot.py:

from kiteconnect import KiteTicker

kws = KiteTicker(os.environ["KITE_API_KEY"], access_token)

def on_connect(ws, response):
    ws.subscribe([738561])                       # RELIANCE, as in Zerodha's own example
    ws.set_mode(ws.MODE_LTP, [738561])

def on_ticks(ws, ticks):
    for t in ticks:
        logging.info("tick %s %s", t["instrument_token"], t["last_price"])

def on_order_update(ws, data):
    logging.info("order %s is now %s", data["order_id"], data["status"])

def on_close(ws, code, reason):
    logging.warning("ticker closed: %s %s", code, reason)

kws.on_connect = on_connect
kws.on_ticks = on_ticks
kws.on_order_update = on_order_update
kws.on_close = on_close
kws.connect()                                    # blocks here and reconnects on its own

connect() blocks, so it goes last, and it keeps the bot's process running. If the connection drops, the SDK reconnects on its own, by default up to 50 times. Use connect(threaded=True) if your main thread has other work to do. You can look up instrument tokens with kite.instruments("NSE"). It uses the same access token, so it also stops at 6 AM.

Run the bot 24/7 with the algo@ systemd template

Algo VPS includes a systemd template, so you don't have to write a service file:

sudo systemctl enable --now algo@kitebot      # runs /opt/algo/kitebot.py
journalctl -u algo@kitebot -f                  # follow the logs
sudo systemctl restart algo@kitebot            # after you edit the code

The template runs /opt/algo/kitebot.py with the Python in /opt/algo/venv, loads /opt/algo/.env, restarts the bot if it crashes, and starts it again after a reboot. Because the bot waits for the login, a reboot at 3 AM does no harm. Once you run kite_login.py, the bot picks up the new token within a minute.

A bot left running overnight loses its session at 6 AM: API calls raise TokenException and the ticker can't reconnect. The tidiest fix is to stop the bot after the close and start it again in the morning. Our guide to running a Python trading bot 24/7 covers systemd timers, skipping NSE holidays, and alerts for mornings when you forget to log in.

Algo VPS Mumbai (4 vCPUs, 8 GB RAM) costs ₹1,510 a month, or about ₹1,325 a month when billed yearly. A plain Cloud VPS works too if you're happy to install Python, the SDK, the IPv4 preference and the service yourself. It starts at ₹860 a month, or about ₹774.17 a month when billed yearly, and Mumbai adds a monthly location fee. The static IP guide shows those steps.

Rate limits and common Kite Connect errors

These limits are from Zerodha's API documentation, as of October 2026:

WhatLimit
Quote calls1 request a second
Historical candles3 requests a second
Order placement10 requests a second, 400 orders a minute
All other endpoints10 requests a second
Orders per day5,000 per user or API key, across all segments
Changes to one order25, after which you cancel and place it again

Requests above the limit get an HTTP 429 error. Invalid requests that Kite blocks count towards the daily limit too, even though they never show up in your order book. A bot that retries a bad order in a loop can use up its daily allowance.

  • TokenException (403). The session has ended. Either the 6 AM expiry has passed, you logged out, or a newer login with the same API key replaced it. Run the login again rather than retrying.
  • InputException (400). A parameter is missing or has a bad value. Read the message, fix the call, and don't resend it unchanged.
  • PermissionException: "IP (…) is not allowed to place orders for this app." Forum users reported this wording in 2026. Compare the IP in the message with the output of algo-ip. The usual causes are an order sent from your laptop, a request that left over IPv6, a whitelist change that was never saved, or this week's change already used up.
  • NetworkException or DataException. Kite couldn't reach its order management system, or couldn't read its reply. Check the order book before you retry, because the order may have gone through.
Log in each morning, then let the server run your bot. Algo VPS Mumbai comes with a fixed IPv4, IST time, kiteconnect and a ready algo@ service, billed in rupees. See Algo VPS →

Frequently asked questions

Is the Zerodha Kite Connect API free?

Partly. As of October 2026, the Personal plan is free and lets you place orders and track positions, holdings and funds, but it has no live market data or historical data. The Connect plan costs ₹500 a month per API key and adds WebSocket streaming and historical candles. From October 2026, paid users who generate ₹2,000 or more in brokerage in a month get 500 Developer Credits, which pay for the next month's renewal.

Can I automate the Kite Connect login with TOTP?

Zerodha advises against it. In July 2026 a moderator on Zerodha's official Kite Connect forum wrote that exchange requirements mean users must log in manually at least once a day, and that Zerodha does not recommend automating the login flow. The compliant routine is to log in by hand in a browser each morning and let your server exchange the request token for that day's access token.

When does the Kite Connect access token expire?

At 6 AM the next day, which Zerodha's documentation calls a regulatory requirement. The token stops working earlier if you log out through the API, do a master logout from Kite Web, or start a new session with the same API key, for example by logging in on your laptop for a test. Once it expires, the API raises TokenException and you have to log in again.

Why does Kite Connect say my IP is not allowed to place orders?

Your order reached Zerodha from an address that isn't on your IP whitelist. Compare the IP in the error message with the addresses under Profile, IP Whitelist in the developer console. The usual causes are testing from a laptop instead of the server, a server that sends requests over IPv6 while only its IPv4 address is registered, or a change that was never saved. Zerodha allows one whitelist change per calendar week.