A technical signals API returns the current state of standard technical indicators, computed server-side, so you don't have to port RSI, MACD, or moving average math into every project. SiftingIO now publishes two REST endpoints for exactly this: a live signal snapshot under /v1/last/signals and a per-bar signal history under /v1/hist. Both cover US stocks, forex pairs, crypto assets, and commodities through the same request shape and the same API key you already use for bars and quotes.
One framing note before the details. Everything these endpoints return is data: the mechanical output of textbook indicator formulas applied to price bars. A buy vote from RSI(14) means the formula's value crossed a conventional threshold, nothing more. Treat the labels as inputs you render, filter, backtest, or alert on, never as recommendations or forecasts.
What the technical signals API returns#
The live endpoint is GET /v1/last/signals/:venue/:symbol, where venue is one of stocks, crypto, forex, or commodities, and the symbol uses the same concatenated uppercase form as the rest of the API: AAPL, BTCUSD, EURUSD, XAUUSD. An optional interval query parameter selects the bar size the signal is computed on, from 1m up to 1mo. The live endpoint defaults to 1h.
The response has three layers. At the top sits a summary: a label from strong_sell to strong_buy, a score from -1 to +1, and a vote tally across every indicator that produced an opinion. Below that are two groups. The oscillators group covers RSI(14), MACD(12,26,9), the stochastic oscillator, CCI, Williams %R, and momentum. The moving averages group evaluates SMA and EMA at periods 10, 20, 30, 50, 100, and 200. Each indicator row carries its current value and its vote: buy, neutral, or sell. MACD additionally reports its signal_line, and the stochastic reports k and d.
One behavior is worth calling out early. Indicators still in warmup are omitted from the response rather than reported as neutral, so the vote counts always sum to the rows actually returned. The denominator can differ between symbols and intervals, and your code should read it from the response instead of assuming a fixed indicator count.
Calling the live signal endpoint#
Authentication is the standard X-API-Key header. A live signal for BTCUSD on hourly bars looks like this:
curl -H "X-API-Key: $SIFTING_KEY" \
"https://api.sifting.io/v1/last/signals/crypto/BTCUSD?interval=1h"
A trimmed response:
{
"data": {
"summary": {
"signal": "buy",
"score": 0.36,
"counts": {"buy": 11, "neutral": 6, "sell": 3}
},
"oscillators": {
"signal": "neutral",
"score": 0.08,
"indicators": [
{"name": "RSI(14)", "value": 58.2, "vote": "neutral"},
{"name": "MACD(12,26,9)", "value": 42.10, "signal_line": 30.44, "vote": "buy"},
{"name": "Stoch(14,3,3)", "k": 82.3, "d": 78.1, "vote": "sell"}
]
},
"moving_averages": {
"signal": "strong_buy",
"score": 0.83,
"indicators": [
{"name": "SMA(10)", "value": 61240.5, "vote": "buy"},
{"name": "EMA(50)", "value": 59870.2, "vote": "buy"}
]
},
"price": {"close": 61980.4, "bar_status": "forming"}
},
"meta": {"as_of": "2026-08-13T14:32:07Z", "symbol": "BTCUSD", "interval": "1h"}
}
Two fields do most of the work in application code. data.summary.score gives you a single number to sort or threshold on, and data.price.bar_status tells you whether the signal was computed on a still-forming bar or a closed one. The same call works for a stock by switching the venue and symbol: /v1/last/signals/stocks/AAPL?interval=1d.
Signal history, one point per bar#
The second endpoint, GET /v1/hist/:venue/:symbol/signals, returns the signal as a time series with one point per bar. It defaults to interval=1d and limit=100, and points come back oldest first:
curl -H "X-API-Key: $SIFTING_KEY" \
"https://api.sifting.io/v1/hist/crypto/BTCUSD/signals?interval=1d&limit=90"
{
"data": [
{"t": 1754870400000, "close": 60120.0, "summary": "buy", "score": 0.28, "events": []},
{"t": 1754956800000, "close": 61010.5, "summary": "strong_buy", "score": 0.55, "events": ["macd_cross_up"]},
{"t": 1755043200000, "close": 61980.4, "summary": "strong_buy", "score": 0.61, "events": ["golden_cross"]}
],
"meta": {"as_of": "2026-08-13T14:32:07Z", "symbol": "BTCUSD", "interval": "1d"}
}
Each point carries the bar's close, the summary label, the score, and an events array of discrete markers:
golden_cross: a fast moving average crossed above a slow onedeath_cross: a fast moving average crossed below a slow onemacd_cross_up: the MACD line crossed above its signal linemacd_cross_down: the MACD line crossed below its signal line
The t field is the bar-open time in Unix epoch milliseconds, the same convention as the bars routes. That makes joining signal history onto OHLCV data a plain merge on t, with no timestamp gymnastics.
Where the endpoints fit in a real workflow#
The live endpoint suits anything that shows current state. A symbol page can render the summary label and score next to the price, with the two group breakdowns behind a details toggle. A watchlist screener can loop a list of symbols at interval=1d, sort by score, and surface the extremes for a human to look at.
The history endpoint is the research tool. Because each point aligns with a bar open time, you can pull 90 days of daily signals for EURUSD or XAUUSD, join them to the matching bars from /v1/hist, and measure what actually followed each label or cross event in your own data. That measurement step matters: crossover events are descriptive facts about two lines on a chart, and whether they carry any predictive weight for a given symbol and interval is exactly the question a backtest answers. The endpoint hands you the annotated series so you can ask it.
Event markers also make alerting cheap. Poll the most recent history point on your chosen interval and notify when events is non-empty, rather than recomputing MACD yourself on every tick.
Common pitfalls#
A 422 insufficient_history response means there aren't enough bars to compute the signal for that symbol and interval. Long-period moving averages are the usual cause: SMA(200) on weekly bars needs nearly four years of history, which recently listed symbols don't have. Handle the error code explicitly and fall back to a shorter interval instead of retrying.
The live endpoint computes on the current bar by default, and bar_status: "forming" flags this. A vote can flip between now and the bar close as the price moves, which is the classic repaint problem. Logic that must be repaint-free should act only on closed bars, either by checking bar_status or by reading completed points from the history endpoint.
Don't hardcode the number of indicators. Warmup omission means counts can sum to different totals across symbols and intervals, so derive any percentage from the returned tally. Separately, a 403 market_not_entitled error means your key's subscription doesn't cover that market; US stock signals require a paid tier for the stocks market, while the venue itself is valid.
The full parameter and field reference, including every error code, lives in the API documentation. Read the docs



