Placing an order through Kite Connect is one function call. Knowing whether it actually filled is where most first scripts go wrong. By the end of this guide you'll have placed, checked and cancelled one real order, after rehearsing it in a paper mode that sends nothing.
Kite Connect has no public sandbox. Every order the API sends is real. That's why the paper mode comes first.
Before you start
| You need | Why |
|---|---|
| A Kite Connect app (API key and secret) | Identifies your code. Free on the Personal plan. |
| Today's access token | Expires at 6 AM every day. Getting the API key and refreshing the token covers it. |
| A whitelisted static IP | Order requests from any other IP are rejected. Add it under Profile → IP Whitelist in the developer console. You can add up to two, and change them once a week. |
| The Connect plan (₹500/month) | Needed for the paper mode below, which calls kite.ltp() for live prices. On the free plan, pass the price in by hand instead. |
Install the library:
pip install kiteconnect
Step 1: Connect with today's token
from kiteconnect import KiteConnect
kite = KiteConnect(api_key="YOUR_API_KEY")
kite.set_access_token("TODAYS_ACCESS_TOKEN")
print(kite.profile()["user_name"]) # quick check that the token works
Keep the key, secret and token out of your code in real use. Environment variables or a file outside your repository are the usual options.
Step 2: Know the order parameters
These are the fields place_order() needs, from the Kite Connect orders documentation:
| Parameter | Common values | What it means |
|---|---|---|
variety |
regular, amo |
A normal order, or an after-market order queued for the next session |
exchange |
NSE, BSE, NFO |
Cash market, or futures and options (NFO) |
tradingsymbol |
INFY, NIFTY26OCT25000CE |
Exact symbol from the instruments list |
transaction_type |
BUY, SELL |
Side |
quantity |
1 |
Shares, or units for F&O (a multiple of the lot size) |
product |
CNC, MIS, NRML |
Delivery, intraday, or carry-forward F&O |
order_type |
LIMIT, MARKET, SL, SL-M |
How the order is priced |
price |
1450.5 |
Your limit price (for LIMIT and SL) |
trigger_price |
1440 |
Trigger for stop-loss orders |
validity |
DAY, IOC |
How long the order stays open |
tag |
first-order |
Your own label, up to 20 characters, handy for matching orders to your logs |
market_protection |
-1 or a percentage |
Required on MARKET and SL-M orders sent through the API |
Start with LIMIT orders. Since the SEBI retail algo rules came in, market orders placed through the API must carry market protection, and an order without it is rejected with a message along the lines of "Market orders without market protection are not allowed via API". Some versions of the Python library on PyPI shipped without the market_protection argument, so a limit order is also the simplest way to avoid that problem.
Step 3: Build a paper mode first
The idea is simple: one function that everything in your code calls to place an order. In paper mode it records what it would have sent, with a simulated fill at the current price. In live mode it sends the order.
import datetime as dt
import json
import uuid
PAPER = True # flip to False only when you're ready
def place(kite, symbol, side, qty, price, exchange="NSE", product="CNC", tag="first-order"):
order = dict(
variety=kite.VARIETY_REGULAR,
exchange=exchange,
tradingsymbol=symbol,
transaction_type=side,
quantity=qty,
product=product,
order_type=kite.ORDER_TYPE_LIMIT,
price=price,
validity=kite.VALIDITY_DAY,
tag=tag,
)
if PAPER:
ltp = kite.ltp([f"{exchange}:{symbol}"])[f"{exchange}:{symbol}"]["last_price"]
# A buy limit fills only if the market is at or below your price, and vice versa.
fills = (side == "BUY" and ltp <= price) or (side == "SELL" and ltp >= price)
record = {
"time": dt.datetime.now().isoformat(timespec="seconds"),
"mode": "paper",
"order_id": "PAPER-" + uuid.uuid4().hex[:8],
"order": order,
"ltp": ltp,
"status": "COMPLETE" if fills else "OPEN",
"fill_price": ltp if fills else None,
}
print(json.dumps(record, indent=2))
return record["order_id"]
order_id = kite.place_order(**order)
print("sent live order", order_id)
return order_id
Run it a few times in paper mode:
place(kite, "INFY", "BUY", 1, price=1400.0)
INFY here is only a placeholder for any liquid symbol.
Look at what it prints. Is the symbol right? The quantity? The product? Would a buy at that price fill right now? Most mistakes show up here, where they cost nothing.
A paper fill at the last traded price is kinder than reality. A real order meets the other side of the order book, and on a fast-moving option that gap can be large. More on that in paper trading vs live trading.
Step 4: Send one real order
When the paper output looks right, set PAPER = False and place the smallest possible order. For a first test, a limit price a little away from the market is useful: the order will sit open, which lets you practise checking and cancelling it without buying anything.
from kiteconnect import exceptions as kex
try:
order_id = place(kite, "INFY", "BUY", 1, price=1300.0)
except kex.InputException as e:
print("bad parameters:", e) # wrong symbol, quantity, price, etc.
except kex.TokenException as e:
print("token expired, log in again:", e)
except kex.NetworkException as e:
print("network or rate limit:", e) # don't blindly retry an order
except kex.OrderException as e:
print("order rejected:", e)
If any exception fired, stop here and look at the order book in Kite before going further. The steps below assume you have an order_id.
One warning on retries: if the call times out, the order may still have reached Zerodha. Check the order book before sending it again, or you can end up with two orders.
Step 5: Check what actually happened
place_order() returns an order ID. That means Zerodha accepted the request, nothing more. The order can still be rejected by margin or risk checks, sit open, or fill in parts.
history = kite.order_history(order_id)
latest = history[-1]
print(latest["status"], latest.get("status_message"))
print("filled", latest["filled_quantity"], "of", latest["quantity"], "at", latest["average_price"])
The statuses you'll see most:
| Status | Meaning |
|---|---|
OPEN |
Waiting at the exchange |
COMPLETE |
Fully filled |
REJECTED |
Refused; status_message says why |
CANCELLED |
Cancelled by you or by the system |
TRIGGER PENDING |
A stop-loss order waiting for its trigger |
For a running system, polling is the slow way. The Kite websocket also sends order updates as they happen, which is how most bots track fills.
Step 6: Cancel it
kite.cancel_order(variety=kite.VARIETY_REGULAR, order_id=order_id)
print(kite.order_history(order_id)[-1]["status"]) # CANCELLED
You've now placed, checked and cancelled a real order through the API. Everything else in automated trading is built on these three calls.
Limits worth knowing on day one
- 10 orders per second per trading account. Rejected requests count too. Going above it needs your strategy registered with the exchange.
- No sandbox. Keep the paper switch in your code permanently, not just for the first day.
- Static IP for orders only (see the table at the top).
- Tokens expire at 6 AM. An order placed with yesterday's token fails with a token error.
The full list of costs and limits is in Zerodha Kite Connect: what it costs and whether it's worth it.
Stay safe
- Keep the paper switch, and log every order your code sends along with the reason it sent it.
- Start with one share and limit orders.
- Never publish your API secret, access token or TOTP secret.
Sources
- Kite Connect orders API documentation
- Zerodha: static IP for API order placement
- Zerodha: Kite Connect API FAQs
- Kite Connect forum: preparing for SEBI's retail algo rules
- pykiteconnect issue on the missing market_protection argument
This guide explains how a broker API works, for information only. It is not investment advice or a recommendation to buy or sell any security. I am not registered with SEBI as an investment adviser or research analyst.