Tutorial: Find support and resistance levels for any stock or crypto with one API call
Support and resistance are the first lines most traders draw on a chart: the floors where buyers keep stepping in, and the ceilings where sellers keep taking over. Drawing them by hand is slow, a bit subjective, and impossible to repeat across a whole watchlist every day.
The Support & Resistance indicator (supportresistance) does it for you, for any crypto pair or US stock on TAAPI.IO, on any timeframe. One request tells you where the next floor and ceiling are, how far away they are in percent, and gives you every other level around price as well.
This tutorial explains what the indicator returns, walks through two real examples with charts, Bitcoin and NVIDIA, and ends with two small scripts you can copy and run.
What the indicator does
The indicator looks for swing highs and swing lows: candles where price turned and then moved away. When price turns at roughly the same price more than once, those swings are merged into one level. Each level is then labelled by where it sits right now: below the current price it’s support, above it it’s resistance.
One request returns:
- The nearest support and resistance (
nearest_support,nearest_resistance), with their distance from price in percent. These are always included. - The current price (
current_price), so you don’t need a second request to work out the distances. - Every level, when you add
results=max: its price, whether it’s support or resistance now (role), how many times price turned there (touches), when it last did, how far away it is, and whether price has closed through it since (broken).
So a single call answers the three questions traders ask first: where is the next floor, where is the next ceiling, and how much room is there to each?
Your first request
curl -G https://v2.taapi.io/indicator/supportresistance \
-H "Authorization: Bearer YOUR_API_KEY" \
--data-urlencode "exchange=binance" \
--data-urlencode "symbol=BTCUSDT" \
--data-urlencode "timeframe=1d" \
--data-urlencode "lookback=10" \
--data-urlencode "lookforward=10" \
--data-urlencode "results=max"
Why these settings? lookback=10 and lookforward=10 mean a swing only counts if it’s the highest or lowest candle of 21 days, about three weeks. That’s what we recommend for daily levels: it keeps the swings traders watch over several weeks and drops the day-to-day noise. results=max returns every level instead of just one.
This is a real response from 10 November 2026, shortened to the two nearest levels. With results=max, every array has one entry per level, in the same order:
{
"line_type": ["support", "resistance"],
"left_timestamp": [1741651200, 1634688000],
"right_timestamp": [1794268800, 1794268800],
"price": [78430, 66556.16],
"pattern_index": [7, 1],
"role": ["resistance", "support"],
"touches": [2, 4],
"last_touch_timestamp": [1743984000, 1784592000],
"distance": [4189.779999999999, -7684.059999999998],
"distance_percent": [5.6435446985474975, -10.350265664622219],
"broken": [true, true],
"broken_timestamp": [1744070400, 1787097600],
"current_price": 74240.22,
"nearest_support": 66556.16,
"nearest_support_distance_percent": -10.350265664622219,
"nearest_resistance": 78430,
"nearest_resistance_distance_percent": 5.6435446985474975,
"timestamp": [1794268800]
}
Here’s how to read it:
nearest_resistanceis 78,430, 5.6% above the current price of 74,240.nearest_supportis 66,556, 10.4% below.- In the arrays, each position is one level. The first level is at 78,430, sits above price (
role: "resistance") and was formed by 2 turns. The second, at 66,556, is below price (role: "support") and has been touched 4 times, which is why it ranks first (pattern_index: 1). - Use
role, notline_type.line_typesays whether the level was built from swing lows or swing highs. 78,430 was built from lows in spring 2025, so it’s still"support"there, but price is below it now, so itsroleis"resistance". Old floors often become new ceilings, androlealready accounts for that.
Example 1: Bitcoin (BTCUSDT)
Here are the levels from the response above, drawn on a weekly Bitcoin chart:
| Level | Price | From price | Where it comes from |
|---|---|---|---|
| Resistance | 85,129 | +14.7% | Lows of the December 2025 range, now overhead |
| Resistance | 81,447 | +9.7% | Capped rallies in May and September 2026 |
| Nearest resistance | 78,430 | +5.6% | Two lows in March and April 2025 |
| Current price | 74,240 | Latest daily candle | |
| Nearest support | 66,556 | −10.4% | Near the 2021 high; 4 touches, the strongest level in the list |
| Support | 62,824 | −15.4% | Lows in February and August 2026, never closed through |
How to read this: Bitcoin topped near 126,200 in October 2025, fell to 60,000 in February and has traded in a wide 58,000–87,000 range since. At 74,240 it sits between two levels. The first ceiling is 78,430, 5.6% higher. The first floor is 66,556, 10.4% lower, and it’s the most-tested level on the chart: it formed near the 2021 high, capped price again in July 2026, and turned into support when Bitcoin broke above it in August.
That gives a trader a quick sense of the room on each side: about 5.6% up to the first ceiling, and about 10.4% down to the first floor. Above 78,430, the next ceilings are 81,447 and 85,129, both of which have stopped rallies this year. Below 66,556 sits 62,824, the only nearby level that price has never closed through.
Example 2: NVIDIA (NVDA)
The same request works for US stocks. Just use exchange=stocks:
curl -G https://v2.taapi.io/indicator/supportresistance \
-H "Authorization: Bearer YOUR_API_KEY" \
--data-urlencode "exchange=stocks" \
--data-urlencode "symbol=NVDA" \
--data-urlencode "timeframe=1d" \
--data-urlencode "lookback=10" \
--data-urlencode "lookforward=10" \
--data-urlencode "results=max"
| Level | Price | From price | Where it comes from |
|---|---|---|---|
| Resistance | 190.20 | +5.9% | Tops in December 2025 and January 2026 |
| Nearest resistance | 182.86 | +1.8% | Turned price down in July 2025 and March 2026 |
| Current price | 179.56 | Latest daily candle, pre-market | |
| Nearest support | 176.89 | −1.5% | Lows in October and November 2025 |
| Support | 170.65 | −5.0% | Lows in December 2025 and February 2026 |
How to read this: NVIDIA ran up to about 243 in October, then fell hard and is now back in the 170–190 range it traded in last winter. That old range left clear marks. Price is squeezed between a ceiling at 182.86 and a floor at 176.89, only about 3.3% apart. On 9 November the stock dipped to 170.24, right at the next floor of 170.65, and bounced. Many traders would wait for a daily close outside the 176.89–182.86 band: above it, the next ceiling is 190.20; below it, 170.65 comes back into play.
You’ll notice all four NVDA levels show broken: true in the response. That only means price has closed through each of them at some point. Levels that price keeps returning to still matter, which is why the nearest_* fields include them.
What if there’s no level above price?
Apple is a good example. On the same day, AAPL traded at an all-time high, so there was no swing high above price to build a resistance level from:
{
"current_price": 351.248,
"nearest_support": 341.25,
"nearest_support_distance_percent": -2.8464219013346668,
"nearest_resistance": null,
"nearest_resistance_distance_percent": null
}
nearest_resistance is null, and so is its distance. The same can happen to support after a big sell-off to new lows. Always check for null before you use either value, as the scripts below do.
Script 1: Nearest levels for a watchlist (Python)
This prints the nearest support and resistance for a mix of crypto pairs and stocks:
import requests
API_KEY = 'YOUR_API_KEY'
WATCHLIST = [('binance', 'BTCUSDT'), ('binance', 'ETHUSDT'), ('stocks', 'NVDA'), ('stocks', 'AAPL')]
def fmt(price, pct):
if price is None:
return 'none found'.ljust(22)
return f'{price:>10,.2f} ({pct:+.1f}%)'.ljust(22)
for exchange, symbol in WATCHLIST:
r = requests.get(
'https://v2.taapi.io/indicator/supportresistance',
headers={'Authorization': f'Bearer {API_KEY}'},
params={'exchange': exchange, 'symbol': symbol, 'timeframe': '1d',
'lookback': 10, 'lookforward': 10},
)
sr = r.json()
line = (f"{symbol:<8} {sr['current_price']:>10,.2f} "
f"support {fmt(sr['nearest_support'], sr['nearest_support_distance_percent'])} "
f"resistance {fmt(sr['nearest_resistance'], sr['nearest_resistance_distance_percent'])}")
print(line.rstrip())
When we ran it, the output looked like this:
BTCUSDT 74,240.22 support 66,556.16 (-10.4%) resistance 78,430.00 (+5.6%)
ETHUSDT 2,091.38 support 1,993.30 (-4.7%) resistance 2,225.23 (+6.4%)
NVDA 179.56 support 176.89 (-1.5%) resistance 182.86 (+1.8%)
AAPL 351.25 support 341.25 (-2.8%) resistance none found
It makes one request per symbol. US stocks need the Pro plan or higher, which allows 30 requests per 15 seconds, so a watchlist this size runs comfortably. On the free plan, stick to crypto and one request every 15 seconds; see rate limits.
Script 2: A level ladder for one symbol (Node.js)
This lists every level within 20% of price, from the highest to the lowest, with the current price in between:
const API_KEY = 'YOUR_API_KEY';
async function ladder(exchange, symbol, rangePercent = 20) {
const params = new URLSearchParams({
exchange, symbol, timeframe: '1d', lookback: 10, lookforward: 10, results: 'max'
});
const res = await fetch(`https://v2.taapi.io/indicator/supportresistance?${params}`, {
headers: { Authorization: `Bearer ${API_KEY}` }
});
const sr = await res.json();
// Turn the parallel arrays into one object per level
const levels = sr.price.map((price, i) => ({
price,
role: sr.role[i],
touches: sr.touches[i],
distance: sr.distance_percent[i],
broken: sr.broken[i]
}));
const nearby = levels
.filter((l) => Math.abs(l.distance) <= rangePercent)
.sort((a, b) => b.price - a.price);
console.log(`${symbol} levels within ${rangePercent}% of ${sr.current_price}:`);
let pricePrinted = false;
for (const l of nearby) {
if (!pricePrinted && l.price < sr.current_price) {
console.log(` ──── price ${sr.current_price} ────`);
pricePrinted = true;
}
const pct = `${l.distance > 0 ? '+' : ''}${l.distance.toFixed(1)}%`;
console.log(` ${l.role.padEnd(10)} ${String(l.price).padStart(9)} ${pct.padStart(6)} ` +
`${l.touches} touches${l.broken ? '' : ', never closed through'}`);
}
}
ladder('binance', 'BTCUSDT');
Run it with Node.js 18 or newer (node ladder.js). When we ran it, the output looked like this:
BTCUSDT levels within 20% of 74240.22:
resistance 85129.43 +14.7% 2 touches
resistance 81447.01 +9.7% 2 touches
resistance 78430 +5.6% 2 touches
──── price 74240.22 ────
support 66556.16 -10.4% 4 touches
support 62823.64 -15.4% 2 touches, never closed through
The 62,824 support is the only one marked “never closed through”. That makes it a good candidate if you only want levels that have held every time: filter on broken === false.
Tips
- Pick the window for your timeframe.
lookback=10andlookforward=10work well on daily candles. The default of5suits intraday timeframes and finds more levels closer to price. Much wider windows leave very few levels. - Use
roleto tell support from resistance, notline_type. - Remember the confirmation delay. A swing is only confirmed after
lookforwardmore candles have closed, so with10on daily candles, a new level appears 10 days after the swing that forms it. - Use closed candles for signals. Add
backtrack=1if you act on the levels, so the current price comes from the last closed candle instead of the one still forming. - Combine the level with a trigger. A level tells you where to look. Many traders wait for a daily close beyond it, or a rejection candle at it, before acting.
As always, test a strategy on historical data before you trade it with real money. Support and resistance levels show where price has reacted before, not a guarantee that it will react there again. Nothing in this post is financial advice.
Where to go from here
- Full parameter and field reference: Support & Resistance documentation
- See how it compares with Support Line and Support Segments
- Fetch many symbols at once with bulk requests
The Support & Resistance indicator is available now on every plan. Give it a try, and if you build something with it, we’d love to hear about it: get in touch.