Stolo Trading API
The Stolo Trading API lets your code place and cancel option orders through the broker you've connected on Stolo, and read back your positions, order book, and margins. It sits next to the market data endpoints and uses the same key, secret, and bearer token. A typical script reads the option chain, decides on a strike, and sends the order here.
Every endpoint lives under /trade and needs the token from
authentication. Placing is POST with a JSON body.
Cancelling is DELETE, and positions, orders, and margins only read, so they're GET;
these send their fields in the query string. Using the wrong method returns 405.
| Endpoint | What it does | Answer |
|---|---|---|
POST /trade/place | Places a buy or sell order for an option | Queued, not filled (see below) |
DELETE /trade/cancel | Cancels an open order | Queued |
GET /trade/positions | Today's positions at the broker | Straight from the broker |
GET /trade/orders | Today's order book at the broker | Straight from the broker |
GET /trade/margins | Balance, used margin, and available funds | Straight from the broker |
/trade/place sends a live order to your broker. There is no sandbox. Test with one lot
and a limit price you're comfortable with.
Before your first order
Trade calls go through extra checks the market data calls don't. All of these must hold:
- Your broker is connected on Stolo and logged in today. Most brokers expire the session daily. See brokers supported for the list and setup guides.
- You send
broker_slugon every call, and it matches the broker currently active on your Stolo account. If Zerodha is active and you sendfyers, the call fails. - The account that owns the app has an active Stolo Pro or Stolo Trial (15 days) plan, the same as for every other endpoint.
The accepted broker_slug values are zerodha, fyers, upstox, tradesmart, dhan,
fivepaisa, shoonya, kotakneo, angelone, sharekhan, nuvama,
flattrade, and bigul.
Placing an order
POST /trade/place takes the option you want, which side, and how much:
| Field | Type | Required | Description |
|---|---|---|---|
broker_slug | string | Yes | Your active broker, from the list above |
symbol | string | Yes | An option symbol, such as NIFTY26100625100CE. Copy it from the option chain |
bid_type | string | Yes | BUY or SELL |
product_type | string | Broker dependent | MIS (intraday, squared off by the broker) or NRML (carry forward) |
order_type | string | No | LIMIT or MARKET. Leaving it out means a market order |
quantity | integer | Yes | Units, not lots: lots × lot size |
limit | number | For LIMIT | Your limit price in rupees. Rounded to the nearest 0.05 tick |
Two rules catch most first-time callers. quantity is in units, so two lots of NIFTY at a
lot size of 75 is 150, not 2. And only options are accepted: a stock or futures
symbol comes back as 422 Only options trading is allowed at this moment.
- curl
- Python
- JavaScript
curl -X POST 'https://algoapi.stolo.in/v1/trade/place' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-d '{"broker_slug":"zerodha","symbol":"NIFTY26100625100CE","bid_type":"BUY","product_type":"MIS","order_type":"LIMIT","quantity":75,"limit":142.5}'
order = post("/trade/place", {
"broker_slug": "zerodha",
"symbol": "NIFTY26100625100CE",
"bid_type": "BUY",
"product_type": "MIS",
"order_type": "LIMIT",
"quantity": 75,
"limit": 142.5,
}, token)
print(order) # {'status': 'queued'}
The post helper is the one from the quickstart.
const order = await post("/trade/place", {
broker_slug: "zerodha",
symbol: "NIFTY26100625100CE",
bid_type: "BUY",
product_type: "MIS",
order_type: "LIMIT",
quantity: 75,
limit: 142.5,
}, token);
console.log(order); // { status: "queued" }
A 200 means queued, not filled
The response data is only this:
{ "status": "queued" }
Stolo accepts the order, records it, and hands it to its order pipeline, which sends it to
the broker in the background. So the HTTP call returns before the broker has seen the
order, and a 200 tells you nothing about whether it was filled or rejected. The result
arrives on the order update WebSocket, covered below.
Two things happen in that pipeline that you don't have to code yourself:
- Market orders become limit orders. Exchange rules don't allow a true market order
from an API, so a
MARKETorder (or one with noorder_type) is sent as aLIMITa small buffer away from the last traded price. If the exact price matters, send your ownLIMIT. - Big orders are split at the freeze limit. If your quantity is above the exchange's freeze quantity for that contract, Stolo splits it into several broker orders. Each slice gets its own broker order id.
Cancelling an order
DELETE /trade/cancel takes broker_slug and the broker's own order id,
broker_order_id, in the query string. Don't send them as a JSON body: it's ignored on a
DELETE, and you get Please specify the broker.
- curl
- Python
- JavaScript
curl -X DELETE 'https://algoapi.stolo.in/v1/trade/cancel?broker_slug=zerodha&broker_order_id=261006000123456' \
-H 'Authorization: Bearer YOUR_TOKEN'
res = requests.delete(
"https://algoapi.stolo.in/v1/trade/cancel",
params={"broker_slug": "zerodha", "broker_order_id": "261006000123456"},
headers={"Authorization": f"Bearer {token}"},
timeout=10,
)
const query = new URLSearchParams({ broker_slug: "zerodha", broker_order_id: "261006000123456" });
const res = await fetch(`https://algoapi.stolo.in/v1/trade/cancel?${query}`, {
method: "DELETE",
headers: { Authorization: `Bearer ${token}` },
});
Take broker_order_id from /trade/orders or from an order_update message. Like
placing, the answer is {"status": "queued"}, and the order update tells you when the
broker actually cancelled it. There's no modify endpoint: to change the price, cancel and
place again.
Positions, orders, and margins
These three are GET requests. They take only broker_slug, in the query string, and
answer straight from your broker, so a 200 here is the broker's current view.
- curl
- Python
- JavaScript
curl 'https://algoapi.stolo.in/v1/trade/positions?broker_slug=zerodha' \
-H 'Authorization: Bearer YOUR_TOKEN'
res = requests.get(
"https://algoapi.stolo.in/v1/trade/positions",
params={"broker_slug": "zerodha"},
headers={"Authorization": f"Bearer {token}"},
timeout=10,
)
positions = res.json()["data"]
const res = await fetch("https://algoapi.stolo.in/v1/trade/positions?broker_slug=zerodha", {
headers: { Authorization: `Bearer ${token}` },
});
const positions = (await res.json()).data;
/trade/orders and /trade/margins work the same way.
| Endpoint | data | Fields you'll use |
|---|---|---|
/trade/positions | Array of positions | symbol, product_type, open_quantity, average_entry_price, average_exit_price, realised_pnl, status (ongoing or complete) |
/trade/orders | Array of orders, newest first | symbol, broker_order_id, bid_type, order_type, quantity, filled_quantity, pending_quantity, price, limit_price, status, time |
/trade/margins | One object | total_balance, used_margin, available_balance, realized_pnl |
{ "total_balance": 100000, "used_margin": 20000, "available_balance": 80000, "realized_pnl": 1500 }
Stolo maps each broker's response to these field names, but the content still varies a
little by broker: status wording, how broker_symbol looks, decimal precision. Any
margins field can be null when the broker doesn't report it. Before you hardcode
anything, place one small order and read the raw responses for your broker.
Tracking orders with the order update WebSocket
The Communication WebSocket is a separate connection from the HTTP API. It pushes an
order_update message every time one of your app's orders changes at the broker. Connect
and log in before you place anything:
wss://<communication-socket-host>
- Send
{"action": "app-login", "token": "<your token>"}as soon as the socket opens. A bad or expired token gets{"type": "invalid-credentials"}and the socket closes. - Read
order_updatemessages as they arrive:
{
"type": "order_update",
"data": {
"broker": "zerodha",
"symbol": "NIFTY26100625100CE",
"broker_order_id": "261006000123456",
"status": "COMPLETED",
"bid_type": "BUY",
"quantity": 75,
"price": 142.5,
"message": "Order Executed"
}
}
- Answer the server's ping, sent every 60 seconds, with
{"action": "pong"}.
You only get updates for orders placed through your own app, never your manual trades on
Stolo or another app's orders. And updates aren't stored for you: if the socket was down
when an order filled, that message is gone. After every reconnect, call /trade/orders
once to catch up.
Worked example: buying the NIFTY ATM call
It's 10:20 AM on a Tuesday, NIFTY is near 25,090, and your script wants one lot of the ATM call for the 6 October expiry.
/analysis/symbol/inforeturns"atm": 25100. The option chain row for 25100 has"call": {"symbol": "NIFTY26100625100CE", "ltp": 141.8}./trade/marginsshowsavailable_balance: 80000. One lot of 75 at about ₹142 needs roughly ₹10,650, so there's room./trade/placewithquantity: 75,order_type: "LIMIT", andlimit: 142.5. The answer is{"status": "queued"}straight away.- A moment later the socket delivers an
order_updatefor261006000123456with statusCOMPLETEDat142.5. /trade/positionsnow shows the contract withopen_quantity: 75.
If step 4 had shown a rejection instead, message carries the broker's reason, such as
insufficient margin or a price outside the circuit band.
Errors
Every trade call first runs the broker check, and each failure is a 422:
message | Cause |
|---|---|
Please specify the broker | No broker_slug: in the body for place, in the query string for cancel, positions, orders, and margins |
Sorry, but currently trading is not supported with <slug> broker | broker_slug isn't one of the 13 above |
Please connect your broker to trade | No broker is connected on your Stolo account |
Looks like your current active broker is <X>, please switch to <Y>... | A different broker is active on Stolo |
Please connect your broker | The broker is active, but its login on Stolo has expired. Log in again on Stolo |
Quantity cannot be 0 | /trade/place with a zero quantity |
Symbol not found in <broker> | The option isn't listed for that broker, often a typo or an expired contract |
Missing fields come back as 422 Validation failed with the detail in data, as on every
other endpoint (see requests, responses, and errors).
Unattended scripts should treat any 422 from a trade call as "stop and tell a human",
not something to retry in a loop.
Rate limits and cost
Trade calls have their own fixed limit, 10 requests per second and 300 per minute per
app, counted separately from the market data limits. They are not charged Stolo
Tokens. See rate limits and usage
for the headers and 429 handling, which work the same way.
Try the Trading API in your browser
Open the API Playground, authenticate, and switch to the Trades tab to call positions, orders, margins, and place or cancel an order.
Open the API Playground