# zrsclient
[](https://crates.io/crates/zrsclient)
[](https://docs.rs/zrsclient)
[](#license)
> **Warning:** This SDK is in active development. Please do not use it for live trading.
`zrsclient` is a Rust SDK over the API provided by the Indian stock broker
[Zerodha](https://kite.trade/docs/connect/v3/). It wraps the Kite Connect REST
endpoints and the WebSocket market-data stream, and includes retry/reconnect
logic so algorithm writers don't have to hand-handle recoverable failures.
## Features
- **REST coverage** — orders, GTT (read **and** write), alerts, portfolio
(holdings/positions), margins & charges, mutual funds, market quotes,
instruments, and historical data.
- **Builder-based requests** — `OrderParams` (incl. iceberg / auction / TTL /
market-protection / auto-slice), `GttParams`, and `AlertParams`.
- **Automatic retries** on recoverable REST errors (throttling, timeouts) with a
caller-supplied retry budget.
- **WebSocket streaming** on `tokio-tungstenite` with automatic reconnect
(exponential backoff) and subscription state that survives reconnects.
- **Typed ticks** — `on_ticks` yields `Vec<Tick>` (typed `Mode`, `Ohlc`,
`MarketDepth`) instead of untyped JSON.
## Installation
```sh
cargo add zrsclient
```
Or add it to your `Cargo.toml`:
```toml
[dependencies]
zrsclient = "0.3"
```
## Authentication
`zrsclient` consumes an existing Kite `api_key` / `access_token` (it does not
implement the login/session flow). Provide them via a `ZrsClientConfig`, or place
them in `~/.zrsclient/credentials` as TOML:
```toml
[default]
api_key = "your_api_key"
access_token = "your_access_token"
```
`KiteConnect::new(None)` resolves credentials from that file; passing
`Some(config)` supplies them directly.
## REST example
```rust
use zrsclient::connect::{KiteConnect, OrderParams};
use zrsclient::config::ZrsClientConfig;
use serde_json::Value as JsonValue;
// Supply credentials directly, or pass `None` to read ~/.zrsclient/credentials.
let config = ZrsClientConfig {
api_key: Some("API_KEY"),
access_token: Some("ACCESS_TOKEN"),
base_url: None,
};
let kite = KiteConnect::new(Some(config));
// Retry budget for recoverable errors (0 disables retries).
let retry = 10;
// Read portfolio.
let holdings: JsonValue = kite.holdings(retry).unwrap();
// Place an order with the builder.
let params = OrderParams::new("regular", "NSE", "INFY", "BUY", "LIMIT", 1, "CNC")
.price(1500.0)
.validity("DAY");
let placed: JsonValue = kite.place_order(¶ms, retry).unwrap();
```
### GTT and alerts
```rust
use zrsclient::connect::{GttParams, GttOrder, AlertParams};
// A single-leg GTT.
let gtt = GttParams::new("single", "NSE", "INFY", 1500.0)
.trigger_value(1450.0)
.order(GttOrder::new("BUY", 1, "LIMIT", "CNC", 1450.0));
kite.place_gtt(>t, retry).unwrap();
// A simple price alert.
let alert = AlertParams::simple(
"NIFTY above 27k", "INDICES", "NIFTY 50", "LastTradedPrice", ">=", 27000.0,
);
kite.create_alert(&alert, retry).unwrap();
```
## WebSocket streaming
```rust
use zrsclient::ticker::{KiteTicker, KiteTickerHandler, WebSocketHandler, Tick};
struct CustomHandler {
tokens: Vec<u32>,
mode: String,
}
impl KiteTickerHandler for CustomHandler {
fn on_open<T>(&mut self, ws: &mut WebSocketHandler<T>)
where T: KiteTickerHandler {
// Subscribe on connect. This also re-runs after an automatic reconnect.
ws.subscribe(self.tokens.clone()).unwrap();
ws.set_mode(&self.mode, self.tokens.clone()).unwrap();
}
fn on_ticks<T>(&mut self, _ws: &mut WebSocketHandler<T>, ticks: Vec<Tick>)
where T: KiteTickerHandler {
for tick in ticks {
println!("{} -> {}", tick.instrument_token, tick.last_price);
}
}
fn on_order_update<T>(&mut self, _ws: &mut WebSocketHandler<T>, order: serde_json::Value)
where T: KiteTickerHandler {
println!("order update: {:?}", order);
}
}
fn main() {
let mut ticker = KiteTicker::new("API_KEY", "ACCESS_TOKEN");
// Reconnection is on by default; tune it if needed:
// ticker.set_reconnect(true, 0, 1, 30);
let handler = CustomHandler { tokens: vec![256265, 260105], mode: "full".to_string() };
ticker.connect(handler, None).unwrap();
// The ticker runs on a background thread; keep the process alive.
loop { std::thread::sleep(std::time::Duration::from_secs(1)); }
}
```
## Testing
Unit tests (mocked HTTP, in-process WebSocket) run offline:
```sh
cargo test
```
Integration tests hit the live Kite API and are `#[ignore]`d by default. Provide
credentials via environment variables to run them:
```sh
KITE_API_KEY=xxx KITE_ACCESS_TOKEN=yyy cargo test --test immutable -- --ignored
```
> Real-time market-data endpoints (quote / ohlc / ltp / trigger_range) require a
> Kite "market data" subscription; without it those calls return
> `PermissionException` and the integration tests treat them as reachable-but-gated.
## Changelog
See [CHANGELOG.md](CHANGELOG.md). `0.3.0` is a **breaking** release — see the
migration notes there before upgrading from `0.2.x`.
## License
Licensed under either of [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE) at
your option.