API reference

The nightly scan

GET/scanKey required

What 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

NameInTypeRequiredDescription
familyquerystring (enum)noKeep 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.
directionquerystring (enum)noKeep one direction. Omit for both. One of bullish, bearish.
statusquerystring (enum)noforming (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_pctquerynumbernoKeep 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.
filtersquerystringnoThe 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.
limitqueryintegernoPage size, 1 to 200 (default 50); clamped. count says how many matched.
offsetqueryintegernoRows to skip, for paging.
X-TradingPal-ClientheaderstringnoOptional 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.

filters
[
  {
    "kind": "price",
    "op": ">",
    "value": 10
  },
  {
    "kind": "rsi",
    "op": "<",
    "value": 40
  },
  {
    "kind": "asset_type",
    "value": "stock"
  }
]
kindtakesunitmeaning
directionvalue one of bearish, bullishThe traded direction. Also a plain query parameter.
phasevalue one of armed, confirmed, formingforming, armed (price near the trigger) or confirmed, as the site's screener labels the setup.
dist_to_entryop (<, <=, >, >=, =) and a number%Distance between the last price and the trigger, absolute percent.
min_win_rateop (<, <=, >, >=, =) and a number%Historical win rate of this pattern on this ticker, percent.
min_avg_rop (<, <=, >, >=, =) and a numberRAverage R per past trade on this ticker.
min_expected_rop (<, <=, >, >=, =) and a numberRExpected R, TradingPal's ticker-plus-family estimate.
min_rrop (<, <=, >, >=, =) and a numberratioReward to risk: distance to the target divided by distance to the stop.
target_moveop (<, <=, >, >=, =) and a number%Trigger to target, percent.
pre_pattern_moveop (<, <=, >, >=, =) and a number%The run-up before the pattern, percent.
priceop (<, <=, >, >=, =) and a number$Last price.
market_capop (<, <=, >, >=, =) and a number$Market capitalization.
avg_volumeop (<, <=, >, >=, =) and a numbershares20-day average daily volume.
dollar_volumeop (<, <=, >, >=, =) and a number$20-day average dollar volume.
dollar_volume_30dop (<, <=, >, >=, =) and a number$Price times the 30-day average volume.
current_dollar_volumeop (<, <=, >, >=, =) and a number$Price times today's volume.
current_volumeop (<, <=, >, >=, =) and a numbersharesToday's volume; the day may still be in progress.
volume_ratioop (<, <=, >, >=, =) and a numberxThe last full day's volume over its 20-day average (1 = average).
volume_percentileop (<, <=, >, >=, =) and a number0 to 100Full-day volume rank over the last three months (100 = highest).
rsiop (<, <=, >, >=, =) and a number0 to 10014-day RSI.
adrop (<, <=, >, >=, =) and a number%14-day average daily range as a percent of price.
atrop (<, <=, >, >=, =) and a number%14-day average true range, gaps included, as a percent of price.
volatilityop (<, <=, >, >=, =) and a number%20-day realized volatility.
ema_distanceop (<, <=, >, >=, =) and a number; period 8, 21, 60%Percent above (+) or below (-) the exponential moving average; period 8, 21 or 60.
ema_crossop (<, <=, >, >=, =) and a number%Percent EMA8 is above EMA21; at least 0 includes equality.
sma_distanceop (<, <=, >, >=, =) and a number; period 20, 50, 200%Percent above (+) or below (-) the simple moving average; period 20, 50 or 200.
price_above_smavalue true or false; period 20, 50, 200true/falsetrue when price is above the simple moving average; period 20, 50 or 200.
sma_crossvalue true or falsetrue/falsetrue when the 50-day average is above the 200-day.
performanceop (<, <=, >, >=, =) 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_changeop (<, <=, >, >=, =) and a number%One-day price change.
change_from_openop (<, <=, >, >=, =) and a number%Open to last, percent.
gapop (<, <=, >, >=, =) and a number%Opening gap.
distance_from_highop (<, <=, >, >=, =) 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_lowop (<, <=, >, >=, =) 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_typevalue one of crypto, etf, stockstock, etf or crypto.
exchangevalue, a labelMarket identifier code: XNYS, XNAS, ARCX, BATS or XASE.
industryvalue, a labelOne 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.
countryvalue, a labelUS 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).

FieldTypeDescription
availablebooleanTrue when the nightly has a current book. False when no book exists for the session; setups is then empty.
statusstring (enum)The status filter applied: forming, in_progress or all. One of forming, in_progress, all.
filtersobjectThe other filters as applied, null where none was sent.
filters.familystring · nullableThe family filter.
filters.directionstring · nullableThe direction filter.
filters.max_distance_pctnumber · nullableThe distance cap.
filters.predicatesarray of objectThe screener predicates as applied, in canonical form and order; empty when none was sent.
countintegerHow many setups matched the filters, before paging.
limitintegerThe page size applied (1 to 200).
offsetintegerThe offset applied.
setupsarray of ScanSetupThis page of the ranked setups, best rank first.

Example response

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.

StatusCodeMeaningFix
401missing_api_keyThe 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.
401invalid_api_keyThe 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.
403forbiddenThe 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.
429rate_limitedThe 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.
400bad_requestA 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.