Analysis API
The Analysis API is the market data half of the Stolo Developer API. It covers what an
options script needs before it trades: the ATM strike and expiries for an underlying, the
option chain for one expiry, and minute candles for any option. Every endpoint lives
under /analysis. Data for the current trading day is free; a request for a past date is
charged in Stolo Tokens, and the same request is free again for 24 hours (see
token charges).
All four are GET requests. Send the fields as query parameters, such as
?symbol=NIFTY&date=2026-09-24, with no request body. URL-encode the values, so a symbol
with a space like INDIA VIX goes as INDIA%20VIX (requests and URLSearchParams
do this for you). A blank optional parameter, such as date=, counts as not sent. Calling
one of these endpoints with POST returns 405, and isn't charged.
| Endpoint | What it returns |
|---|---|
GET /analysis/symbol/info | ATM strike, exchange, sector, and the next five expiries for a spot symbol |
GET /analysis/option-chain | Calls and puts for 41 strikes around the ATM, live or for a past date, with IV and Greeks |
GET /analysis/candles/intraday | Candles for the latest completed session |
GET /analysis/candles/historical | Candles for any date range, up to 30 days per call |
The usual order is the order of this page: symbol info gives you the expiry, the option chain gives you the option symbol, and the candles use that symbol.
Symbol Info
The Symbol Info API is usually your first call for an underlying. It tells you the at-the-money strike and the next five expiry dates, which are exactly the two things you need to ask for the right option chain. Pass a past date and you get both as they stood on that day.
GET /analysis/symbol/info
Query parameters
| Field | Type | Required | Description |
|---|---|---|---|
symbol | string | Yes | A spot symbol, such as NIFTY, BANKNIFTY, or RELIANCE. Option and futures symbols are rejected |
date | string | No | YYYY-MM-DD. Defaults to the current market date |
- curl
- Python
- JavaScript
curl 'https://algoapi.stolo.in/v1/analysis/symbol/info?symbol=NIFTY&date=2026-09-24' \
-H 'Authorization: Bearer YOUR_TOKEN'
res = requests.get(
"https://algoapi.stolo.in/v1/analysis/symbol/info",
params={"symbol": "NIFTY", "date": "2026-09-24"},
headers={"Authorization": f"Bearer {token}"},
timeout=10,
)
info = res.json()["data"]
const query = new URLSearchParams({ symbol: "NIFTY", date: "2026-09-24" });
const res = await fetch(`https://algoapi.stolo.in/v1/analysis/symbol/info?${query}`, {
headers: { Authorization: `Bearer ${token}` },
});
const info = (await res.json()).data;
Response
The data field of the response (values are illustrative):
{
"symbol": "NIFTY",
"name": "NIFTY",
"sector": "index",
"exchange": "NSE",
"date": "2026-09-24",
"atm": 25100,
"expiries": ["2026-09-29", "2026-10-06", "2026-10-13", "2026-10-20", "2026-10-27"]
}
| Field | Type | Description |
|---|---|---|
symbol | string | The spot symbol |
name | string | Display name |
sector | string | Sector, or index for indices |
exchange | string | Exchange, for example NSE |
date | string | The date the answer applies to (YYYY-MM-DD). This is the date you sent, or the current market date if you didn't send one |
atm | number or null | The at-the-money strike: the spot's last traded price on date, rounded to the nearest listed strike. null when there's no price or no strikes for that date |
expiries | string[] | Up to the next five expiry dates on or after date, nearest first. An expiry that falls on date itself is included. Empty for a spot with no F&O contracts |
This endpoint doesn't return the lot size.
Errors
| HTTP | message | Cause |
|---|---|---|
404 | Symbol not found | The spot symbol isn't one Stolo tracks |
422 | Only spot symbols are allowed | You sent an option or futures symbol |
422 | Validation failed | symbol is missing, or date isn't a valid date |
How to use it
Picking the chain to load. Take expiries[0] for the current weekly, or expiries[1]
to skip an expiry-day chain that's about to die. Pass it as expiry to
/analysis/option-chain.
Finding the ATM contract. atm tells you which row of the option chain is at the
money. With "atm": 25100, the NIFTY ATM call and put are the strike: 25100 row.
Looking back. With a past date, both atm and expiries are as of that session.
For example, on an expiry Tuesday expiries[0] is that same day, because an expiry on
date is still listed.
atm comes from the last price of the day for past dates, and from the latest price
during a live session. It moves as the market moves, so re-read it before you rely on
it.
Option Chain
The Option Chain API returns calls and puts for one underlying and one expiry: up to 20 strikes above and 20 below the at-the-money strike, each with its last traded price (LTP), open interest (OI), volume, the day's opening values, and implied volatility and Greeks (delta, gamma, theta, vega). During market hours the prices are live. Pass a past date and you get the chain as it closed that day.
It's also where you get option symbols for the candle endpoints. If the terms are new, the guide on how to read an option chain covers them.
GET /analysis/option-chain
Query parameters
| Field | Type | Required | Description |
|---|---|---|---|
symbol | string | Yes | A spot symbol, such as NIFTY or BANKNIFTY |
date | string | No | YYYY-MM-DD. Defaults to the current market date. A past date returns that day's closing chain |
expiry | string | No | YYYY-MM-DD. Defaults to the nearest expiry on or after date. Must be one of the symbol's listed expiries; get them from /analysis/symbol/info |
- curl
- Python
- JavaScript
curl 'https://algoapi.stolo.in/v1/analysis/option-chain?symbol=NIFTY&expiry=2026-09-29' \
-H 'Authorization: Bearer YOUR_TOKEN'
res = requests.get(
"https://algoapi.stolo.in/v1/analysis/option-chain",
params={"symbol": "NIFTY", "expiry": "2026-09-29"},
headers={"Authorization": f"Bearer {token}"},
timeout=10,
)
chain = res.json()["data"]
const query = new URLSearchParams({ symbol: "NIFTY", expiry: "2026-09-29" });
const res = await fetch(`https://algoapi.stolo.in/v1/analysis/option-chain?${query}`, {
headers: { Authorization: `Bearer ${token}` },
});
const chain = (await res.json()).data;
Response
data is an array with one row per strike, lowest strike first (values are illustrative
and trimmed to one row):
[
{
"strike": 25100,
"call": {
"symbol": "NIFTY26092925100CE",
"strike": 25100,
"option_type": "CE",
"ltp": 142.35,
"oi": 5821125,
"volume": 48213900,
"iv": 13.42,
"delta": 0.5187,
"gamma": 0.0014,
"theta": -18.62,
"vega": 12.09,
"open_tick": {
"symbol": "NIFTY26092925100CE",
"price": 118.6,
"volume": 402150,
"oi": 4960500,
"time": "...",
"ohlc": { "...": "..." }
}
},
"put": {
"symbol": "NIFTY26092925100PE",
"strike": 25100,
"option_type": "PE",
"ltp": 96.8,
"oi": 6310875,
"volume": 51877500,
"iv": 13.85,
"delta": -0.4813,
"gamma": 0.0014,
"theta": -17.94,
"vega": 12.09,
"open_tick": { "...": "..." }
}
}
]
Row fields
| Field | Type | Description |
|---|---|---|
strike | number | The strike price |
call | object | The call (CE) at this strike |
put | object | The put (PE) at this strike |
Leg fields (call and put)
| Field | Type | Description |
|---|---|---|
symbol | string | The option symbol. Use it as-is for the candle endpoints |
strike | number | Strike price |
option_type | string | CE or PE |
ltp | number | Last traded price in rupees |
oi | number | Open interest, as reported by the exchange feed |
volume | number | Traded volume for the day so far |
iv | number or null | Implied volatility, annualized and in percent (13.42 means 13.42%, not 0.1342), solved independently for this leg from its own ltp. null when there's no reliable price to solve from (see IV and Greeks) |
delta | number or null | Option delta |
gamma | number or null | Option gamma |
theta | number or null | Option theta, per calendar day |
vega | number or null | Option vega, per 1 point of volatility |
open_tick | object | The option's first tick of the day: price, volume, oi, time, and ohlc |
Legs carry a few more instrument fields than the ones listed here, and the set can grow. Ignore fields you don't use rather than failing on unknown ones.
IV and Greeks
iv, delta, gamma, theta, and vega are computed server-side from Black-Scholes,
using the leg's own ltp and the chain's spot price at the same instant — so a call and
its matching put always get their own independently-solved IV (they trade at different
implied vols in practice; Stolo never mirrors one side's IV onto the other). rho isn't
returned.
A leg gets null for all five fields, instead of a misleading 0, when there's no
reliable price to solve from: before the leg's first trade of the day, or within the last
2 minutes before expiry (implied vol is numerically unstable that close to expiry and
isn't worth trusting). Treat null as "nothing to show," not "IV is zero."
What's in the chain
- Up to 41 strikes. The ATM strike plus 20 on each side. Near the edge of the listed strikes you get fewer.
- Only complete strikes. A strike is included only when both its call and put exist. You'll never see a row with a missing leg.
- Live during the session. With no
date, or today's date,ltp,oi, andvolumecome from Stolo's continuously updated intraday tick store. There's no delay and no 3:40 PM cutoff here, unlike candles. - Closing values for past dates. With a past
date, the values are the last ones recorded that session. - Before the open. Before the first tick of the day, expect missing or stale
ltp,oi, andopen_tickvalues, andnullforiv/delta/gamma/theta/vega, rather than an error.
Errors
| HTTP | message | Cause |
|---|---|---|
422 | Invalid expiry for this symbol | expiry isn't a listed expiry for this symbol, or it's before date |
422 | No expiry found for this symbol | You left out expiry and the symbol has no upcoming expiry, for example a spot with no F&O contracts |
422 | No data available for select expiry | No strikes around the ATM have data for this expiry |
422 | Validation failed | symbol is missing, date isn't a date, or expiry isn't YYYY-MM-DD |
501 | Option Chain data not found for the selected Instrument and Expiry | Stolo has no chain stored for this symbol, expiry, and date |
502 | varies | An internal Stolo service didn't respond. Retry with backoff |
Working with the chain
Change in OI since the open
There's no ready-made OI change field. Compute it from the opening tick:
for row in chain:
ce = row["call"]
oi_change = ce["oi"] - ce["open_tick"]["oi"]
In the example above, the 25100 CE opened the day at 4,960,500 OI and now shows 5,821,125: an addition of 860,625, or about 17%. Rising OI with a rising premium points to fresh long positions; rising OI with a falling premium points to fresh writing. The open interest guide goes deeper on reading these.
Put-call ratio for the visible strikes
total_put_oi = sum(row["put"]["oi"] for row in chain)
total_call_oi = sum(row["call"]["oi"] for row in chain)
pcr = total_put_oi / total_call_oi
This is the PCR for the 41 strikes returned, not the whole expiry. It's close for liquid indices, where far strikes carry little OI, but not identical.
Finding a strike
atm_row = next(r for r in chain if r["strike"] == info["atm"])
atm_ce_symbol = atm_row["call"]["symbol"] # feed this to /analysis/candles/historical
info here is the response from /analysis/symbol/info.
How often to poll
Each chain call costs one request against your rate limit, and the limit is 3,000 calls a day. Three underlyings every 30 seconds uses about 2,250 of those in a session; eight underlyings once a minute would use all 3,000. One call already returns every strike you need, so ask for the chain rather than individual strikes, and poll only as often as your strategy actually acts.
Candles
The Candles API returns price candles for a single option: open, high, low, close, volume, and open interest for each interval, built from Stolo's one-minute data. Pick any whole-minute resolution your strategy uses: 1, 3, 5, 15, or anything else. There are two endpoints: one for the latest completed session and one for any date range you choose, up to 30 days per request.
The 3:40 PM rule
Read this before anything else in this section. A session's candles become available at 3:40 PM IST, after the market closes. Until then, a request for today's candles quietly falls back to the previous trading day.
| When you call | What /analysis/candles/intraday returns |
|---|---|
| Tuesday 11:00 AM | Monday's candles |
| Tuesday 3:39 PM | Monday's candles |
| Tuesday 3:40 PM onward | Tuesday's candles |
| Saturday | Friday's candles |
The same rule applies to /analysis/candles/historical when your range reaches today, or any date
after the last available session: the end of the range is pulled back to the last
available session. The response always tells you what happened by echoing the dates you
asked for next to the dates actually used. If they differ, the fallback kicked in.
Candles aren't built for live use. For prices while the market is open, read the option chain, which returns live LTP, OI, and volume for every strike.
GET /analysis/candles/intraday
Candles for the latest completed session (see the 3:40 PM rule above).
| Field | Type | Required | Description |
|---|---|---|---|
symbol | string | Yes | An option symbol, such as NIFTY26092925100CE |
resolution | integer | Yes | Candle size in minutes. Any whole number from 1 up |
There's no date parameter; the server works out the session for you.
curl 'https://algoapi.stolo.in/v1/analysis/candles/intraday?symbol=NIFTY26092925100CE&resolution=5' \
-H 'Authorization: Bearer YOUR_TOKEN'
The response has requested_date (the current market date) and effective_date (the
session you actually got):
{
"symbol": "NIFTY26092925100CE",
"requested_date": "2026-09-28",
"effective_date": "2026-09-25",
"resolution": 5,
"data": [
{ "timestamp": "2026-09-25 09:15:00", "open": 131.4, "high": 138.0, "low": 127.9, "close": 135.2, "volume": 2104500, "oi": 6118875 }
]
}
That example was called on Monday morning, so it fell back to Friday's session.
GET /analysis/candles/historical
Candles for one session, or every session in a date range.
| Field | Type | Required | Description |
|---|---|---|---|
symbol | string | Yes | An option symbol, such as NIFTY26092925100CE |
start_date | string | Yes | First session, YYYY-MM-DD |
end_date | string | No | Last session, YYYY-MM-DD. Leave it out for just the start_date session |
resolution | integer | Yes | Candle size in minutes. Any whole number from 1 up |
The range is inclusive and can cover at most 30 calendar days, so 2026-09-01 to
2026-09-30 is the longest range starting on 1 September. Weekends and holidays inside the
range just contribute no candles. For a longer history, split it into 30-day requests.
- curl
- Python
- JavaScript
curl 'https://algoapi.stolo.in/v1/analysis/candles/historical?symbol=NIFTY26092925100CE&start_date=2026-09-21&end_date=2026-09-25&resolution=5' \
-H 'Authorization: Bearer YOUR_TOKEN'
import pandas as pd
res = requests.get(
"https://algoapi.stolo.in/v1/analysis/candles/historical",
params={"symbol": "NIFTY26092925100CE", "start_date": "2026-09-21", "end_date": "2026-09-25", "resolution": 5},
headers={"Authorization": f"Bearer {token}"},
timeout=10,
)
payload = res.json()["data"]
df = pd.DataFrame(payload["data"])
df["timestamp"] = pd.to_datetime(df["timestamp"])
df[["open", "high", "low", "close", "volume", "oi"]] = df[["open", "high", "low", "close", "volume", "oi"]].astype(float)
df["session"] = df["timestamp"].dt.date # one range call can span several sessions
const query = new URLSearchParams({ symbol: "NIFTY26092925100CE", start_date: "2026-09-21", end_date: "2026-09-25", resolution: "5" });
const res = await fetch(`https://algoapi.stolo.in/v1/analysis/candles/historical?${query}`, {
headers: { Authorization: `Bearer ${token}` },
});
const { effective_start_date, effective_end_date, data: candles } = (await res.json()).data;
Response
Values are illustrative and trimmed to two candles:
{
"symbol": "NIFTY26092925100CE",
"start_date": "2026-09-21",
"end_date": "2026-09-25",
"effective_start_date": "2026-09-21",
"effective_end_date": "2026-09-25",
"resolution": 5,
"data": [
{ "timestamp": "2026-09-21 09:15:00", "open": 96.2, "high": 101.5, "low": 93.8, "close": 99.4, "volume": 1520325, "oi": 3902250 },
{ "timestamp": "2026-09-25 15:25:00", "open": 162.9, "high": 164.0, "low": 160.1, "close": 162.4, "volume": 948750, "oi": 6205125 }
]
}
| Field | Type | Description |
|---|---|---|
symbol | string | The symbol you asked for |
start_date | string | The start date you sent |
end_date | string | The end date you sent, or the same as start_date if you didn't send one |
effective_start_date | string | The first session actually queried |
effective_end_date | string | The last session actually queried, after the 3:40 PM rule |
resolution | number | Candle size in minutes |
data | array | Candles for every session in the range, oldest first. Use each timestamp to tell sessions apart |
When end_date is past the last available session, only the end moves back. The start
moves too only when the whole range lies past that session. For example, on Monday
28 September at 11 AM:
| You send | You get |
|---|---|
start_date 2026-09-21, end_date 2026-09-28 | 21 to 25 September (Monday's session isn't ready yet) |
start_date 2026-09-28, no end_date | 25 September only |
Candle fields
Both endpoints return the same candle objects in data:
| Field | Description |
|---|---|
timestamp | Start of the candle, in IST. The 9:15 candle at 5-minute resolution covers 9:15:00 to 9:19:59 |
open | First price in the candle |
high | Highest price in the candle |
low | Lowest price in the candle |
close | Last price in the candle |
volume | Total volume traded in the candle |
oi | Open interest at the end of the candle |
Cast numeric fields with float() or Number() rather than assuming their JSON type.
How many candles to expect
Candles are aligned to the clock in IST, not to the market open. For sizes that fit evenly into 9:15 (1, 3, 5, and 15 minutes), the first candle starts at 9:15 AM and a full session from 9:15 AM to 3:30 PM (375 minutes) gives:
| Resolution | Candles in a full session |
|---|---|
| 1 minute | 375 |
| 3 minutes | 125 |
| 5 minutes | 75 |
| 15 minutes | 25 |
For other sizes, such as 30 or 45 minutes, the first candle can be stamped before 9:15
(for example 9:00 at 30 minutes) and holds only the minutes after the open. Its open is
still the first traded price of the day. If you need candles anchored exactly on 9:15,
request 1, 3, 5, or 15-minute candles and combine them yourself.
A thinly traded strike can have fewer candles, because a minute with no trades produces no row and an interval with no rows produces no candle. Don't assume a fixed count; use the timestamps.
Empty results
An empty data array is not an error. It usually means one of these:
- The symbol string is wrong. Copy it from the option chain rather than building it.
- The contract didn't exist or hadn't started trading on that date.
- Every day in the range was a weekend or holiday. Check the effective dates against the calendar.
Errors
| HTTP | message | Cause |
|---|---|---|
422 | Validation failed | symbol is missing, or resolution isn't a whole number of 1 or more |
422 | Validation failed | Historical only: start_date is missing, a date isn't valid, end_date is before start_date (End date must be on or after the start date), or the range is longer than 30 days (Date range can't exceed 30 days) |
402 | You don't have enough Stolo Tokens for this action | The balance can't cover every new past day returned. See token cost |
The detail for each failed field is in data, as described in
validation errors.
Token cost
Both candle endpoints are charged 1 Stolo Token per symbol per past day that returns
candles, whatever the resolution. The current trading day, weekends, and holidays are
free, so /analysis/candles/intraday costs nothing once today's session is served (after
3:40 PM); before that it returns the previous session, which is charged. The same symbol
and day is free to request again, through either endpoint, for 24 hours after it was
charged. If your balance can't cover every new day, the request fails with 402 and
nothing is charged.
Token charges
has a worked example.
Worked example: an opening range on the ATM call
You want the 9:15 to 9:30 range of the NIFTY 25100 CE on 24 September 2026, then how it closed.
- Request 15-minute candles:
{"symbol": "NIFTY26092925100CE", "start_date": "2026-09-24", "resolution": 15}. - The first candle (
09:15:00) hashigh: 131.2andlow: 115.2. That's the opening range: 16 points wide, about 13% of the premium. - The last candle (
15:15:00) closes at162.4, 31.2 points above the range high.
Widen the request to a range, for example start_date 2026-09-01 and end_date
2026-09-30, and group the candles by session. Now you have the raw data to test an
opening range breakout rule on options across a month, in one call. Remember that each
option contract only trades until its expiry, so for longer studies pick the contract
that was near the money in each week. Stolo's
ORB screener does the same study on stocks.