SAHI DOCS

Sahi APIs

Introduction

Build your own trading tools on your Sahi account.

The Sahi APIs let you build your own tools on your Sahi account: read the market, place and manage your orders, and keep track of your positions, holdings and funds.

The REST endpoints are HTTPS with JSON bodies, and there are two WebSocket streams for anything live. Any HTTP client will do, and order updates arrive as JSON; if you would rather not write the HTTP yourself, the Python SDK wraps every endpoint here. The tick stream is the exception: its frames are protobuf, so you compile the schema published with the tick stream for your language before you can decode them, or let the SDK decode them for you.

The endpoints fall into two families:

Every request carries a Bearer access token, and one token covers every scope it was granted — market data included. You mint it yourself from an API key you create in your Sahi account; the key's scopes are fixed at creation and are listed under Create an API key.

Data APIs#

Quotes, candles and ticks, plus the instrument file they are addressed through. One scope covers all of it, marketdata.read, and none of it is IP-checked.

Instruments are named by instrument_id — the integer id from the scrip master. Look it up once at start of day and reuse it for every quote, candle and subscription.

The tick stream is one of the two WebSocket connections, and the simpler one: every tick is a full snapshot for its instrument, so the newest value wins and there is nothing to reconcile. What both sockets share — authentication, ping and pong, close codes — is under WebSocket connections.

Trade APIs#

Anything that acts on your account, and everything that reports back on it. Each area has its own scope — orders.write, orders.read, positions.read, holdings.read, funds.read, profile.read — so a key can be granted exactly what your own tool does and no more.

Orders name instruments by exchange_segment + symbol rather than by instrument_id, and placing, modifying or cancelling one must come from a registered static IP. Reads are not IP-checked.

Order updates stream over the other WebSocket. That one does need care on connect: a socket that drops replays nothing, so follow Stream + Snapshot to bootstrap without losing events.

How to work through this#

  1. Authentication comes first. Create an API key, register the static IP you place orders from, and exchange the key for a token. Nothing below works without one.
  2. Conventions, further down this page. The rules that hold for every endpoint — money in paise, quantities in units, timestamps in milliseconds, identifiers as strings. Reading them once saves debugging later.
  3. Scrip master. Download the daily instrument file and map the symbols you care about. Orders address instruments by exchange_segment and symbol; market data addresses them by instrument_id, and both come from this file.
  4. The endpoints you need, from the two groups above. Each page lists its endpoints with every field, and shows the request and response beside them.

Getting help#

The endpoint pages are generated from the same contract our own services are built against, so if something here disagrees with what the API does, that is a bug worth reporting. Write to developers@sahi.com.

Conventions#

Exchange segment#

Value Market Available on
NSE NSE Cash Order APIs + Market Data
NFO NSE Futures & Options Order APIs + Market Data
BSE BSE Cash Order APIs + Market Data
BFO BSE Futures & Options Order APIs + Market Data

Enum values#

txn_type
order_type
product
validity
variant
status

Error envelope#

Every non-2xx response uses this shape:

{
  "code": "VALIDATION_FAILED",
  "message": "trigger_price is required for SL-LMT orders"
}

code is a stable enum. The values used across these pages: INVALID_ORDER_STATE, ORDER_NOT_FOUND, VALIDATION_FAILED, FORBIDDEN, NOT_FOUND. UNAUTHORIZED (401) and RATE_LIMITED (429) can come back from any endpoint, IP_NOT_ALLOWED (403) from the order-write endpoints, and NOT_FOUND (404) from an unknown route — or from GET /v1/funds, POST /v1/margins/order, POST /v1/margins/basket and GET /v1/profile when the account behind the token is not yet set up for trading. UNDER_MAINTENANCE (503) comes back from POST /v1/margins/order and POST /v1/margins/basket during the daily 04:45–07:00 IST maintenance window.

Rate limits#

Each endpoint carries its own request budget for your user, over up to three windows. A call over budget is refused with 429 and the standard envelope —

{
  "code": "RATE_LIMITED",
  "message": "rate limit exceeded"
}

— carrying a Retry-After header in whole seconds. Wait that long: it is what the window that tripped has left to run, so an immediate retry only earns another 429.

Endpoint Per second Per minute Per day
POST /v1/orders, PUT /v1/orders/{order_id}, DELETE /v1/orders/{order_id} 10 250 10,000
GET /v1/orders, GET /v1/orders/{order_id}, GET /v1/orders/{order_id}/trades 5 250 10,000
GET /v1/positions 5 250 10,000
GET /v1/holdings 5 250 10,000
GET /v1/funds 1 — —
POST /v1/margins/order, POST /v1/margins/basket 5 250 10,000
GET /v1/profile 1 — —
GET /v1/marketdata/ltp 1 — —
GET /v1/marketdata/quotes 1 — —
GET /v1/marketdata/candles 2 250 100,000
GET /v1/marketdata/optionchain 2 120 20,000
GET /v1/marketdata/optionchain/expiries 5 250 10,000
WSS /v1/stream/orders 5 250 10,000
WSS /v1/stream/marketdata 5 250 10,000

Batch before you back off. ltp and quotes take up to 100 instrument_ids per request, so one call a second is a whole watchlist a second — the ceiling bites only if you loop an instrument at a time. Anything you follow continuously belongs on the tick stream rather than in a polling loop, which is also why the streams are budgeted for reconnects rather than for traffic.

Concurrent connections. Each stream allows 5 open at once — per account for order updates, per token for the tick stream — and refuses the next with close code 4429.

If a strategy genuinely needs more than a row allows, write to developers@sahi.com rather than sharding across keys to work around it.

WebSocket connections#

Two streams, each documented with the resource it carries: order updates is JSON-framed text, and the tick stream is protobuf-framed binary. Four rules hold for both.