Kite Connect Integration
SkopaqTrader integrates with Zerodha's Kite Connect API for live market data, order execution, and portfolio management. The integration is in skopaq/broker/kite_client.py.
Architecture
User Browser
│
│ GET /api/kite/login
▼
FastAPI Server (skopaq/api/server.py)
│
│ Redirect to Zerodha
▼
Zerodha Login Page
│
│ OAuth callback with request_token
▼
GET /api/kite/callback?request_token=XXX
│
│ Exchange for access_token
▼
KiteClient (skopaq/broker/kite_client.py)
│
│ Store token (memory + file + env var)
▼
Ready for trading
OAuth Flow
Step 1: Login Redirect
The user visits /api/kite/login, which redirects to Zerodha's login page:
@app.get("/api/kite/login")
async def kite_login():
client = KiteClient(api_key=config.kite_api_key)
return RedirectResponse(client.login_url)
Step 2: Callback
After login, Zerodha redirects to /api/kite/callback with a request_token:
@app.get("/api/kite/callback")
async def kite_callback(request_token: str, status: str):
client = KiteClient(api_key=..., api_secret=...)
session = client.generate_session(request_token)
# Token stored automatically
Step 3: Token Persistence
The access token is stored in three places for maximum reliability:
- Module-level cache (
_access_token) -- Fastest access - File (
/data/skopaq_kite_token.jsonor/tmp/...) -- Survives module reloads - Environment variable (
SKOPAQ_KITE_ACCESS_TOKEN) -- Subprocess access
Token retrieval priority:
def get_access_token() -> str:
# 1. Module cache (fastest)
# 2. Persisted file (/data or /tmp)
# 3. SKOPAQ_KITE_ACCESS_TOKEN env var
# 4. SkopaqConfig (from .env)
Daily token expiry
Kite access tokens expire at the end of each trading day (around 6:00 AM IST next day). You must re-login daily. The Telegram bot sends a reminder at 09:00 IST.
API Mapping
The KiteClient wraps the kiteconnect Python SDK to match the same interface as INDstocksClient:
| Method | Kite SDK Call | Returns |
|---|---|---|
get_quote(instrument) |
kite.quote(instrument) |
Quote model |
get_positions() |
kite.positions()["net"] |
list[Position] |
get_holdings() |
kite.holdings() |
list[Holding] |
get_funds() |
kite.margins("equity") |
Funds model |
get_orders() |
kite.orders() |
list[OrderResponse] |
place_order(order) |
kite.place_order(...) |
ExecutionResult |
Both KiteClient and INDstocksClient return the same Pydantic v2 models (Quote, Position, Funds, etc.) defined in skopaq/broker/models.py. This allows the OrderRouter to switch between brokers transparently.
API Endpoints
The FastAPI server exposes these Kite-related endpoints:
| Endpoint | Method | Description |
|---|---|---|
/api/kite/login |
GET | Redirect to Zerodha login |
/api/kite/callback |
GET | Handle OAuth callback |
/api/kite/status |
GET | Check token validity |
/api/kite/token |
GET | Get current access token |
/api/kite/postback |
POST | Receive order postback webhooks |
Configuration
Required environment variables:
# Kite Connect credentials (from developer console)
SKOPAQ_KITE_API_KEY=your_api_key
SKOPAQ_KITE_API_SECRET=your_api_secret
# Optional: pre-set access token (useful for testing)
SKOPAQ_KITE_ACCESS_TOKEN=your_token
Instrument Format
Kite Connect uses the EXCHANGE:TRADINGSYMBOL format:
| Context | Format | Example |
|---|---|---|
| Equities | NSE:SYMBOL |
NSE:RELIANCE |
| Index | NSE:NIFTY 50 |
NSE:NIFTY 50 |
| Options | NFO:NIFTY2640124000CE |
NFO:NIFTY2640124000CE |
The MCP tools accept plain symbols (e.g., RELIANCE) and add the exchange prefix internally.
Fallback Behavior
If Kite Connect is not available (no token), the system falls back to INDstocks for market data and the paper engine for portfolio/orders:
kite = _get_kite()
if kite:
quote = await kite.get_quote(f"NSE:{symbol}")
else:
# Fall back to INDstocks
async with INDstocksClient(config, token_mgr) as client:
quote = await client.get_quote(scrip_code)
This means the system always works -- Kite just provides better data and live trading.
Order Postback
Zerodha can send order status updates via webhook to /api/kite/postback. This enables real-time notification of order fills, rejections, and cancellations without polling.
Configure the postback URL in your Kite Connect app settings:
File Reference
| File | Purpose |
|---|---|
skopaq/broker/kite_client.py |
KiteClient class, token management |
skopaq/api/server.py |
OAuth endpoints (/api/kite/*) |
skopaq/broker/models.py |
Shared Pydantic models (Quote, Position, etc.) |
skopaq/mcp_server.py |
_get_kite() helper for MCP tools |