SAHI DOCS

API reference

Authentication

Create an API key, register the static IP you call from, and exchange the key for an access token.

Every Sahi API request carries a Bearer access token, and you mint your own. There is no OAuth redirect to build and no secret we hold on your behalf: you create an API key in your Sahi account, exchange it for a short-lived token, and send that token on every call.

Three things have to be true before a token will work, and each has a section below. The key must exist with the scopes you need. You must have approved it for the current trading day. And requests that place, modify, or cancel an order must reach us from a registered static IP. The last two are exchange requirements rather than Sahi policy, and both are checked per request rather than once at onboarding.

Paths on this page are relative to two base URLs: the auth base URL, which serves /api/v1/apikeys/token, and the API base URL, which serves everything under /v1. If you do not have them, write to developers@sahi.com.

Create an API key#

Keys are self-serve, from the API keys section of your Sahi account. You are asked to complete two-factor verification first: creating a credential that can place orders is not something a first-factor session can do. The same section is where you approve a key for the day and revoke it.

Give the key a name and choose its scopes.

Scopes are fixed for the life of the key. A token minted from it can carry that set or a subset of it, never more. To trade under a different set, create a different key.

Scope Grants
orders.write Place, modify and cancel orders
orders.read Read the order book and trade history
positions.read Read open positions
holdings.read Read demat holdings
funds.read Read available funds and margin
profile.read Read profile details
marketdata.read Read quotes, historical candles and instrument data

What you get back. A key id, which begins pk_, and a secret, which begins sahi_sk_. The secret is shown exactly once. Sahi stores only a hash of it and cannot show it again, so put it straight into your secret store; if you lose it, revoke the key and create another. The key id is not a secret and is safe to log.

You may hold at most 5 active keys at a time. Revoked keys do not count against that, but revocation is terminal: a revoked key can never be reactivated.

Revoking is immediate, and it is retrospective. Revoking a key does not merely stop it minting new tokens; every token already minted from it stops being accepted straight away, rather than running out its remaining life. That is the lever to pull if a secret leaks.

Register your static IP#

Placing, modifying, or cancelling an order is permitted only from IP addresses registered in advance. This follows SEBI's framework for retail algorithmic trading, in force since 1 April 2026, and applies to everyone placing orders through the API.

Registration lives with your account, not with a key: one registration covers every key you hold, and adding a key does not mean registering an address again. You register from the same part of the Sahi account UI that manages keys, and, as with keys, it takes a two-factor session.

There are two slots, a primary and a secondary, each holding one address. The second exists for connectivity redundancy: register the egress address of a backup line or a second region, not a second trading process. Both IPv4 and IPv6 are accepted.

Four rules are worth knowing before you register rather than after:

What enforcement looks like. The check covers the three order-write endpoints — POST /orders, PUT /orders/{order_id}, and DELETE /orders/{order_id}. One of those from an unregistered address is refused with 403 and the code IP_NOT_ALLOWED, whatever the token would otherwise have permitted. Everything else — the order book, positions, funds, holdings, profile, market data, and both WebSocket streams — is not IP-checked, so a read-only client needs no registration. Removing your last registered address therefore costs you the ability to trade through the API, not your access to it.

The address that matters is the public egress address your traffic actually arrives from, which is often not the address configured on the machine. Check it from the host that will run your client before you register.

Approve the key for the trading day#

A key mints tokens only on trading days you have approved it for. Approval is a daily act, done from the same API keys section, and it is an exchange requirement rather than something Sahi can waive.

A trading day runs 06:00 IST to 06:00 IST. Approval lapses at that boundary, so a key approved on Tuesday morning stops minting on Wednesday at 06:00 until it is approved again. Creating a key counts as approving it, so a key is usable on the day it is made without a second step.

An unapproved key still exists and still lists; it simply cannot mint. Attempts to mint from one are refused with 403.

Mint an access token#

This is the only endpoint your code calls to authenticate, and the only public one on the auth host. It takes the key id and secret in the body; there is no Authorization header on this request.

POST /api/v1/apikeys/token
Content-Type: application/json
{
  "key_id": "pk_d1jqhcg55l6bd0e0oq3g",
  "secret": "sahi_sk_5Kw...Qr_9b1c2d3e",
  "scopes": ["marketdata.read"]
}
Field Type Required Notes
key_id string yes The key id shown when you created the key.
secret string yes The secret shown once at creation.
scopes array no Narrows the token to a subset of the key's scopes. Omit it to get the key's full set. It can only narrow: asking for a scope the key does not hold is rejected.

The response is 200 OK:

{
  "access_token": "eyJhbGciOiJFUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 65275,
  "expires_at": "2026-08-21T06:00:00+05:30",
  "scope": "marketdata.read"
}
Field Type Notes
access_token string The Bearer token for every REST and WebSocket call.
token_type string Always Bearer.
expires_in integer Seconds until the token expires.
expires_at string The same moment as an RFC 3339 timestamp: the next 06:00 IST boundary.
scope string Space-separated scopes the token actually carries.

Mint one token per trading day and reuse it. The endpoint is rate-limited per caller and key, and a client that mints on every request will meet that limit; on 429, back off and retry in the next minute rather than immediately. That ceiling is the auth host's own: the /v1 endpoints carry separate per-endpoint budgets, listed under Rate limits.

Narrow the scopes when you can. A process that only reads quotes has no business holding a token that can place orders, even if the key behind it can. Passing scopes costs nothing and shrinks what a leaked token is worth.

Call the API#

Send the token on every request to the API host:

GET /v1/positions
Authorization: Bearer eyJhbGciOiJFUzI1NiIs...

One token covers everything it is scoped for, market data included. There is no separate market-data credential.

WebSocket connections authenticate the same way, with the header on the upgrade request. See WebSocket connections.

Token lifetime#

When something goes wrong#

The auth host and the API host use the same envelope, {"code", "message"}, with different code vocabularies: the auth host uses PascalCase, and everything under /v1 uses SCREAMING_SNAKE_CASE. Match on the status code and the host, not on the shape of the string.

Minting, on the auth host:

Status Code Usually means
400 InvalidRequest A field is missing or malformed, or the requested scopes are not a subset of the key's
401 Unauthorized Unknown key id, wrong secret, or the key has been revoked. These are deliberately indistinguishable
401 Locked Your account is frozen or locked
403 Forbidden The key is not approved for the current trading day
429 TooManyRequests Too many mint attempts. Back off to the next minute

Calling the API, on the API host:

Status Code Usually means
401 UNAUTHORIZED The token is missing, malformed, expired, or was revoked with its key
403 IP_NOT_ALLOWED An order write did not come from a registered static IP
403 FORBIDDEN The token is valid but lacks the scope the endpoint needs
429 RATE_LIMITED Over the endpoint's rate limit. Wait out the Retry-After header before retrying

A 403 IP_NOT_ALLOWED on a call that worked yesterday almost always means your egress address moved: a new NAT gateway, a failed-over line, a container rescheduled into another subnet.

Checklist#

  1. Create a key in your Sahi account with the scopes you need, and store the secret as a backend secret. It is shown once.
  2. If the client places orders, register the public egress IP it calls from, checked from the host that will run it. Add a secondary only for a genuine backup path.
  3. Approve the key each trading day.
  4. Mint a token at start of day from /api/v1/apikeys/token, narrowing scopes to what that process needs, and reuse it until 06:00 IST.
  5. Download the scrip master at start of day and map instruments to instrument_id for market data, or exchange_segment plus symbol for orders.
  6. Call the REST endpoints with the Bearer token, and subscribe to the order-update and tick streams for live updates — applying Stream + Snapshot on every order-stream connect.