Sandbox¶
The Kite Connect sandbox at sandbox.kite.trade lets you build and test with
demo credentials and no real money. This page is the reference for
authenticating against it and calling the APIs pykiteconnect supports there.
Every call below (outside Authentication) assumes an authenticated kite
client. The sandbox follows production's
rate limits, so avoid firing requests in rapid
succession.
Note
Priming an AI coding agent (Codex, Claude Code, Cursor) for a sandbox
session? Point it at the ready-made prompt:
https://kite.trade/docs/connect/v3/agent-setup/sandbox-prompt.md.
It embeds a runnable local-callback bootstrap script and the sandbox-specific
tweaks below.
Authentication¶
Credentials¶
The sandbox uses a shared demo app. These credentials are safe to keep in plain text: they are not tied to a real account or real money.
| Key | Value |
|---|---|
api_key |
sandboxdemo |
api_secret |
sandboxdemo-secret |
| API root | https://sandbox.kite.trade |
Sandbox serves all SDK routes under an /oms prefix (except /instruments).
The route table is patched once, right after creating the client, so every
call after that picks it up automatically.
Login¶
Open the login URL in a browser:
https://sandbox.kite.trade/connect/login?api_key=sandboxdemo
Log in with your sandbox user. Kite redirects back to your app's registered
redirect URL with a request_token in the query string:
?action=login&status=success&request_token=<TOKEN>
Copy the request_token value. It is single-use and short-lived.
Generate an access token¶
from kiteconnect import KiteConnect
api_key = "sandboxdemo"
api_secret = "sandboxdemo-secret"
request_token = "<TOKEN_FROM_LOGIN>"
kite = KiteConnect(api_key=api_key, root="https://sandbox.kite.trade")
# Sandbox serves SDK routes under /oms (except the instruments dumps).
passthrough = {"market.instruments.all", "market.instruments"}
kite._routes = {
key: value if key in passthrough else "/oms" + value
for key, value in kite._routes.items()
}
session_data = kite.generate_session(request_token, api_secret=api_secret)
access_token = session_data["access_token"]
kite.set_access_token(access_token)
User¶
Profile¶
profile = kite.profile()
print(profile["user_id"], profile["user_name"], profile["email"])
Market data¶
Instruments master¶
nse_instruments = kite.instruments(exchange="NSE")
print(len(nse_instruments))
Quote¶
quote = kite.quote(["NSE:HDFCBANK"])
hdfcbank = quote["NSE:HDFCBANK"]
print(hdfcbank["last_price"], hdfcbank["ohlc"])
OHLC¶
ohlc = kite.ohlc(["NSE:HDFCBANK"])
print(ohlc["NSE:HDFCBANK"]["ohlc"])
LTP¶
ltp = kite.ltp(["NSE:HDFCBANK"])
print(ltp["NSE:HDFCBANK"]["last_price"])
Historical data¶
reliance_token = kite.quote(["NSE:RELIANCE"])["NSE:RELIANCE"]["instrument_token"]
candles = kite.historical_data(reliance_token, "2020-01-01", "2026-01-01", "day")
print(candles[-1])
Note
Historical data may not be enabled in every sandbox. If a call returns an error, it is not available in yours yet.
A single "day" request only accepts about 1900 days of range before the API
errors out. For a wider range, split into chunks and merge:
from datetime import datetime, timedelta
def historical_data_chunked(kite, instrument_token, from_date, to_date, interval="day", max_days=1900):
start = datetime.strptime(from_date, "%Y-%m-%d")
end = datetime.strptime(to_date, "%Y-%m-%d")
candles = []
while start <= end:
chunk_end = min(start + timedelta(days=max_days), end)
candles += kite.historical_data(
instrument_token, start.strftime("%Y-%m-%d"), chunk_end.strftime("%Y-%m-%d"), interval
)
start = chunk_end + timedelta(days=1)
return candles
WebSocket ticker¶
KiteTicker does not add user_id to its connection URL by default, but
sandbox requires it. Without it, ticks never authenticate.
from kiteconnect import KiteTicker
user_id = session_data["user_id"]
instrument_token = kite.quote(["NSE:HDFCBANK"])["NSE:HDFCBANK"]["instrument_token"]
kws = KiteTicker(api_key, access_token, root="wss://ws-sandbox.kite.trade")
kws.socket_url = f"{kws.socket_url}&user_id={user_id}"
def on_connect(ws, response):
ws.subscribe([instrument_token])
ws.set_mode(ws.MODE_FULL, [instrument_token])
def on_ticks(ws, ticks):
print(ticks)
kws.on_connect = on_connect
kws.on_ticks = on_ticks
kws.connect(threaded=True)
Orders¶
Place an order¶
Only LIMIT orders are available for API-based order placement. The MARKET
order type is not to be used via the API.
ltp = kite.quote(["NSE:HDFCBANK"])["NSE:HDFCBANK"]["last_price"]
resting_price = round(ltp * 0.98, 1) # a couple % below LTP so it rests instead of filling instantly
order_id = kite.place_order(
variety="regular",
exchange="NSE",
tradingsymbol="HDFCBANK",
transaction_type="BUY",
quantity=1,
product="MIS",
order_type="LIMIT",
price=resting_price,
tag="mytag01",
)
print(order_id)
Warning
Pricing too far from LTP (around 20% or more away) trips the exchange's
price-band check and the order is auto-REJECTED instead of resting OPEN.
Keep the price within a couple percent of the last price.
Orders (list)¶
orders = kite.orders()
print(len(orders))
Order history¶
history = kite.order_history(order_id)
print([h["status"] for h in history])
Order trades¶
order_trades = kite.order_trades(order_id)
print(order_trades)
This is empty until the order actually fills, and populated once it is matched.
Modify an order¶
new_price = round(resting_price * 0.99, 1)
kite.modify_order(variety="regular", order_id=order_id, price=new_price)
This only works while the order is still OPEN. A filled, rejected, or
cancelled order has nothing left to modify.
Cancel an order¶
kite.cancel_order(variety="regular", order_id=order_id)
Same condition as modify: only works on an order still OPEN.
Trades (list)¶
trades = kite.trades()
print(len(trades))
Portfolio¶
Margins¶
all_margins = kite.margins()
equity_margin = kite.margins("equity")
print(equity_margin["net"])
Positions¶
positions = kite.positions()
print(positions["day"])
Holdings¶
holdings = kite.holdings()
print(len(holdings))
Convert position¶
kite.convert_position(
exchange="NSE",
tradingsymbol="HDFCBANK",
transaction_type="BUY",
position_type="day",
quantity=1,
old_product="MIS",
new_product="CNC",
)
This requires an existing open position matching these parameters.
Not supported in the sandbox¶
The following are not available here. Use the production API for these:
- GTT orders (
place_gtt(),get_gtts(), and the rest of the GTT family) - Margin calculations:
order_margins(),basket_order_margins(),get_virtual_contract_note() MARKETorders (placeLIMITorders instead)
Moving to production¶
When you are ready to trade against a real account, create an app on the
Kite Connect developer console to get your
own api_key and api_secret, then point the client at
https://api.kite.trade and drop the sandbox /oms route patch. See
Agent setup and the User login flow for the
production setup.