Skip to content

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()
  • MARKET orders (place LIMIT orders 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.