API reference
The nightly scan
/scanKey requiredWhat the nightly scan found across the market, ranked
Returns every setup the last nightly run sees across the whole market (stocks & crypto), ranked exactly as the TradingPal screener ranks them: rank 1 is the best-ranked setup of the night. Default status forming returns setups whose trigger has not fired; in_progress returns the ones that triggered and are running; all returns both. Each row is the same Setup object the per-ticker route returns, plus rank, expected_gain_pct (the ranking value) and distance_to_trigger_pct (how close the last close is to the trigger). Narrow with family, direction or max_distance_pct, or run your own screen with filters, the same filter list the TradingPal screener takes (price, market cap, volume, RSI, moving averages, performance, win rate, reward to risk, security type, exchange, industry and more); page with limit and offset; count says how many matched. The book changes nightly and the answer is served from a short cache, so page through it within a session and say as_of_session when presenting it. Needs a key on an account with Premium+; no ticker needs to be tracked.
Authentication
Needs a key: send Authorization: Bearer tp_live_… (see Authentication). Counted against the key's limits of 60 requests a minute and 10,000 a day.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
family | query | string (enum) | no | Keep one family: rising_wedge, falling_wedge, bullish_pennant, bearish_pennant, triangle. Omit for all five. Anything else answers 400 bad_request. One of rising_wedge, falling_wedge, bullish_pennant, bearish_pennant, triangle. |
direction | query | string (enum) | no | Keep one direction. Omit for both. One of bullish, bearish. |
status | query | string (enum) | no | forming (default): setups whose trigger has not fired. in_progress: setups that triggered and are running. all: both, forming first. Outcomes are never served here. One of forming, in_progress, all. |
max_distance_pct | query | number | no | Keep forming setups whose last close is within this many percent of the trigger (absolute distance), for "about to break out". In-progress rows are not filtered by it. |
filters | query | string | no | The screener's own filters, URL-encoded: a JSON array of predicates, one per kind: numeric kinds take op (<, <=, >, >=, =) and value, choice and text kinds take value, boolean kinds take value true or false, and the moving-average, performance and high/low kinds also take period. For example filters=[{"kind":"price","op":">","value":10},{"kind":"rsi","op":"<","value":40},{"kind":"asset_type","value":"stock"}]. Every predicate must match; a setup with no evidence for a kind never matches. The kinds, with what each takes and means, are the x-filters list on this operation (the reference page shows it as a table). A malformed list answers 400 bad_request with the reason. |
limit | query | integer | no | Page size, 1 to 200 (default 50); clamped. count says how many matched. |
offset | query | integer | no | Rows to skip, for paging. |
X-TradingPal-Client | header | string | no | Optional name of the agent or script making the call. It is recorded in the account's usage log so the user can see which client made which calls; the User-Agent is recorded when it is absent. |
Filters
The filters parameter runs your own screen: the same filter list the TradingPal screener takes, as a JSON array with one predicate per kind, URL-encoded. Every predicate must match, and a setup with no evidence for a kind never matches. Numeric kinds take op (<, <=, >, >=, =) and value; choice and text kinds take value; boolean kinds take value true or false; the moving-average, performance and high/low kinds also take period. Dollar and share counts are plain numbers. The response echoes what applied as filters.predicates.
[
{
"kind": "price",
"op": ">",
"value": 10
},
{
"kind": "rsi",
"op": "<",
"value": 40
},
{
"kind": "asset_type",
"value": "stock"
}
]| kind | takes | unit | meaning |
|---|---|---|---|
direction | value one of bearish, bullish | The traded direction. Also a plain query parameter. | |
phase | value one of armed, confirmed, forming | forming, armed (price near the trigger) or confirmed, as the site's screener labels the setup. | |
dist_to_entry | op (<, <=, >, >=, =) and a number | % | Distance between the last price and the trigger, absolute percent. |
min_win_rate | op (<, <=, >, >=, =) and a number | % | Historical win rate of this pattern on this ticker, percent. |
min_avg_r | op (<, <=, >, >=, =) and a number | R | Average R per past trade on this ticker. |
min_expected_r | op (<, <=, >, >=, =) and a number | R | Expected R, TradingPal's ticker-plus-family estimate. |
min_rr | op (<, <=, >, >=, =) and a number | ratio | Reward to risk: distance to the target divided by distance to the stop. |
target_move | op (<, <=, >, >=, =) and a number | % | Trigger to target, percent. |
pre_pattern_move | op (<, <=, >, >=, =) and a number | % | The run-up before the pattern, percent. |
price | op (<, <=, >, >=, =) and a number | $ | Last price. |
market_cap | op (<, <=, >, >=, =) and a number | $ | Market capitalization. |
avg_volume | op (<, <=, >, >=, =) and a number | shares | 20-day average daily volume. |
dollar_volume | op (<, <=, >, >=, =) and a number | $ | 20-day average dollar volume. |
dollar_volume_30d | op (<, <=, >, >=, =) and a number | $ | Price times the 30-day average volume. |
current_dollar_volume | op (<, <=, >, >=, =) and a number | $ | Price times today's volume. |
current_volume | op (<, <=, >, >=, =) and a number | shares | Today's volume; the day may still be in progress. |
volume_ratio | op (<, <=, >, >=, =) and a number | x | The last full day's volume over its 20-day average (1 = average). |
volume_percentile | op (<, <=, >, >=, =) and a number | 0 to 100 | Full-day volume rank over the last three months (100 = highest). |
rsi | op (<, <=, >, >=, =) and a number | 0 to 100 | 14-day RSI. |
adr | op (<, <=, >, >=, =) and a number | % | 14-day average daily range as a percent of price. |
atr | op (<, <=, >, >=, =) and a number | % | 14-day average true range, gaps included, as a percent of price. |
volatility | op (<, <=, >, >=, =) and a number | % | 20-day realized volatility. |
ema_distance | op (<, <=, >, >=, =) and a number; period 8, 21, 60 | % | Percent above (+) or below (-) the exponential moving average; period 8, 21 or 60. |
ema_cross | op (<, <=, >, >=, =) and a number | % | Percent EMA8 is above EMA21; at least 0 includes equality. |
sma_distance | op (<, <=, >, >=, =) and a number; period 20, 50, 200 | % | Percent above (+) or below (-) the simple moving average; period 20, 50 or 200. |
price_above_sma | value true or false; period 20, 50, 200 | true/false | true when price is above the simple moving average; period 20, 50 or 200. |
sma_cross | value true or false | true/false | true when the 50-day average is above the 200-day. |
performance | op (<, <=, >, >=, =) and a number; period 5, 21, 63, 126, 252, ytd | % | Price change over the period: 5, 21, 63, 126 or 252 sessions, or ytd. |
daily_change | op (<, <=, >, >=, =) and a number | % | One-day price change. |
change_from_open | op (<, <=, >, >=, =) and a number | % | Open to last, percent. |
gap | op (<, <=, >, >=, =) and a number | % | Opening gap. |
distance_from_high | op (<, <=, >, >=, =) and a number; period 20, 50, 252, or a calendar window 4w to 1w | % | Percent below the period high; period 20, 50 or 252 sessions, or 1w to 12m. |
distance_from_low | op (<, <=, >, >=, =) and a number; period 20, 50, 252, or a calendar window 4w to 1w | % | Percent above the period low; period 20, 50 or 252 sessions, or 1w to 12m. |
asset_type | value one of crypto, etf, stock | stock, etf or crypto. | |
exchange | value, a label | Market identifier code: XNYS, XNAS, ARCX, BATS or XASE. | |
industry | value, a label | One of: Semiconductors, Software, Hardware & Electronics, Internet & Media, Telecom, Biotech & Pharma, Healthcare Equipment & Services, Banks, Insurance, Capital Markets & Asset Management, Fintech & Payments, Real Estate, Oil & Gas, Utilities & Renewables, Metals & Mining, Chemicals & Materials, Industrials & Construction, Aerospace & Defense, Transport & Logistics, Retail & E-Commerce, Consumer Goods & Services, Food & Beverage, Restaurants, Travel & Leisure, Autos & Mobility, Crypto, Broad Market & Index, Bonds & Fixed Income, Commodities. | |
country | value, a label | US or GLOBAL. |
Request
curl "https://api.tradingpal.io/api/v1/scan?limit=10&max_distance_pct=3" \
-H "Authorization: Bearer $TRADINGPAL_API_KEY"Response
JSON. The fields this route adds are below; every response also carries the envelope fields (api_version, as_of_session, data_version, source, disclaimer).
| Field | Type | Description |
|---|---|---|
available | boolean | True when the nightly has a current book. False when no book exists for the session; setups is then empty. |
status | string (enum) | The status filter applied: forming, in_progress or all. One of forming, in_progress, all. |
filters | object | The other filters as applied, null where none was sent. |
filters.family | string · nullable | The family filter. |
filters.direction | string · nullable | The direction filter. |
filters.max_distance_pct | number · nullable | The distance cap. |
filters.predicates | array of object | The screener predicates as applied, in canonical form and order; empty when none was sent. |
count | integer | How many setups matched the filters, before paging. |
limit | integer | The page size applied (1 to 200). |
offset | integer | The offset applied. |
setups | array of ScanSetup | This page of the ranked setups, best rank first. |
Example response
{
"api_version": "v1",
"as_of_session": "2026-09-25",
"available": true,
"status": "forming",
"filters": {
"family": null,
"direction": null,
"max_distance_pct": null,
"predicates": []
},
"count": 212,
"limit": 50,
"offset": 0,
"setups": [
{
"rank": 1,
"expected_gain_pct": 6.8,
"distance_to_trigger_pct": 3.69,
"setup_id": "NVDA:D:falling_wedge:1",
"lineage_id": "lineage-1",
"symbol": "NVDA",
"interval": "D",
"family": "falling_wedge",
"family_label": "Falling wedge",
"pattern_type": "falling_wedge",
"direction": "bullish",
"status": "forming",
"pattern_start": "2026-08-08",
"pattern_end": "2026-09-07",
"as_of_session": "2026-09-25",
"last_close": 101.5,
"price_scale": "log",
"lines": {
"upper": {
"start": {
"date": "2026-08-08",
"price": 110
},
"end": {
"date": "2026-09-07",
"price": 104
},
"touch_count": 3,
"touches": [
{
"date": "2026-08-08",
"price": 111.2
}
]
},
"lower": {
"start": {
"date": "2026-08-08",
"price": 90
},
"end": {
"date": "2026-09-07",
"price": 98
},
"touch_count": 2,
"touches": []
}
},
"trigger_rule": "A daily close above the upper line; the trigger price moves with the line.",
"plans": [
{
"direction": "bullish",
"trigger_price": 105.25,
"stop_price": 96,
"target_price": 120
}
],
"track_record": {
"family": {
"scope": "family",
"family": "falling_wedge",
"symbol": null,
"win_rate_pct": 54,
"sample_size": 1287,
"wins": 695,
"losses": 592,
"avg_win_pct": 8.1,
"avg_loss_pct": -4.2,
"median_win_pct": 6.3,
"avg_trade_pct": 2.4,
"avg_r": 0.41,
"expected_r": 0.41,
"profit_factor": 1.61,
"avg_hold_bars": 11.2,
"backtest_range": [
"2006-09-25",
"2026-09-25"
],
"measured": "Every detector-confirmed breakout … open trades are excluded.",
"summary": "Falling wedge: 1,287 resolved trades from 2006-09-25 to 2026-09-25; 54% reached the target, average win +8.1%, average loss -4.2%, median win +6.3%."
},
"ticker": null
}
}
]
}Errors
Errors are JSON with the Error shape; the fix field says what to do.
| Status | Code | Meaning | Fix |
|---|---|---|---|
| 401 | missing_api_key | The request had no Authorization: Bearer tp_live_… header and the route needs one. | Send the header on every keyed call. Only the demo, families and universe routes answer without it. |
| 401 | invalid_api_key | The key is not one we know, or it has been revoked. | Create a new key on the API Keys page and replace it wherever the old one was stored. |
| 403 | forbidden | The account behind the key is not on Premium+. API and MCP access requires Premium+ subscription at $30/month; Premium alone does not unlock the API. | Get Premium+ ($30/month, all of the benefits in Premium plus API and MCP access) from the Quickstart, or switch an existing Premium subscription to it there, then retry with the same key. |
| 429 | rate_limited | The key passed 60 requests in a minute or 10,000 in a day, or a keyless caller passed 30 a minute from one IP. | Wait details.retry_after_seconds, then retry. Cache the nightly answers; they do not change until the next session. |
| 400 | bad_request | A query parameter is malformed: limit or offset is not an integer, outcome is not all, resolved or open, a scan filter (family, direction, status, max_distance_pct) is not one of its allowed values, or filters is not a valid predicate list (the message names the predicate). | The message names the parameter. Correct it and retry. |