Scanning/Saved Scans & Performance

    Scanning

    Saved Scans & Performance

    A one-off scan answers what matches now. A saved scan keeps answering it: scanr.ai re-runs it in the background, records when stocks enter the result, and tracks how those stocks performed afterwards. The saved scans you create from the API are the same ones you see in the scanr.ai app, and the other way round.

    Save a scan

    POST /api/v2/scan/save-filters stores a payload under a name. It needs a token with the write scope. Saving under an existing name updates that scan.

    bash
    curl https://scanr.ai/api/v2/scan/save-filters \  -H "Authorization: Bearer $SCANR_API_TOKEN" \  -H "Content-Type: application/json" \  -d '{    "scanData": {      "name": "RS leaders above the 50-day",      "group": null,      "favorite": false,      "leviathan_id": null,      "payload": {        "Universe": {          "Exchange": ["nasdaq", "nyse", "amex", "arca", "bats", "bx"],          "Type": ["cs", "os", "adrc"]        },        "All": [          {            "Indicator": {              "RelativeStrengthMkt": [                { "Returns": { "Want": { "Value": { "GreaterThanOrEqual": 85 } } } }              ]            },            "Technical": {              "SMA": [                {                  "Timeframe": "1d",                  "Inputs": ["bar_close"],                  "Params": { "Period": { "Value": 50 } },                  "Returns": { "Want": { "SMA": { "Price": { "Comparison": { "LessThan": true } } } } }                }              ]            }          }        ],        "Sort": { "field": "market_cap", "direction": "desc" }      }    }  }'
    FieldMeaning
    nameThe scan's name, unique within your account.
    payloadThe scan payload, exactly as you would send it to /api/v2/scan as instructions.
    group, favoriteHow the scan is filed in the app. Required keys; null and false are fine.
    leviathan_idLeave null. scanr.ai assigns the scan's id.
    notesOptional free text.

    The response is the saved scan. Its leviathan_id is the scan's id, which the performance and alert endpoints below take as scan_id.

    To rename a scan, use POST /api/v2/scan/rename-saved-filter with {"filterName": "old", "newName": "new"}; a name that's already taken returns 409.

    Read your saved scans

    POST /api/v2/scan/myscans lists your saved scans, 10 per page by default (up to 100):

    bash
    curl https://scanr.ai/api/v2/scan/myscans \  -H "Authorization: Bearer $SCANR_API_TOKEN" \  -H "Content-Type: application/json" \  -d '{"savedFiltersPage": 0, "pageSize": 50, "sortBy": "created_at", "sortDir": "desc"}'

    Each entry has the scan's name, payload and leviathan_id. This is also how you move a scan from the app into code: build it visually in the scanr.ai scanner, save it, then read its payload here and run it with /api/v2/scan.

    Run a saved scan

    Run the saved payload like any other, and pass the scan's id in save_args to record the run against the saved scan instead of as a one-off:

    json
    {  "instructions": {    "Universe": {      "Exchange": ["nasdaq", "nyse", "amex", "arca", "bats", "bx"],      "Type": ["cs", "os", "adrc"]    },    "All": [      {        "Indicator": {          "RelativeStrengthMkt": [            { "Returns": { "Want": { "Value": { "GreaterThanOrEqual": 85 } } } }          ]        }      }    ]  },  "save_args": { "scan_id": 918273 }}

    The run is attached only when the payload matches the saved scan's definition; otherwise it's recorded as a one-off. Either way, the scan runs and you get the results.

    Alerts: when stocks enter a scan

    While a saved scan runs in the background, every change to its result is recorded as an event. POST /api/v2/scan/paginated-scan-events returns them newest first:

    bash
    curl https://scanr.ai/api/v2/scan/paginated-scan-events \  -H "Authorization: Bearer $SCANR_API_TOKEN" \  -H "Content-Type: application/json" \  -d '{"limit": 20, "scan_id": 918273}'
    json
    {  "results": [    {      "event_id": 55120,      "scan_id": 918273,      "event_time": "2026-09-29T14:35:00Z",      "event_type": "notification",      "tickers": ["CRDO"],      "all_tickers": ["NVDA", "AVGO", "ANET", "CRDO"],      "saved_scan_info": { "name": "RS leaders above the 50-day", "group": null, "favorite": false }    }  ],  "count": 1,  "next_page": { "event_id_less_than": 55120 }}

    tickers holds the stocks that triggered the event (the new entries); all_tickers is the whole result at that moment. For the next page, send next_page back as-is. Leave out scan_id to get events across all of your saved scans.

    An event with event_type: "disarmed" means the scan stopped refreshing in the background because its filters can no longer run, for example because a custom indicator or pattern model it uses was deleted. It has no tickers (tickers and all_tickers are null). Its detail says why, as a code with the values the message is built from:

    json
    {  "event_id": 55188,  "scan_id": 918273,  "event_time": "2026-10-03T14:35:00Z",  "event_type": "disarmed",  "tickers": null,  "all_tickers": null,  "detail": {    "code": "scan.custom_indicator.not_found",    "params": { "indicator_id": "42", "indicator_name": "My RSI", "version": 3 }  }}

    New codes can appear without notice, so treat a code you do not recognize as "this scan can no longer run". Fix the scan's filters and save it again under the same name (save-filters) to restart background refreshes.

    To limit the time range, add after and/or before. Both filter on event_time and include the boundary. Each takes an ISO 8601 date-time with a zone, such as "2026-09-29T13:30:00Z" or "2026-09-29T09:30:00-04:00". A bare date or a time with no zone returns 400. event_type returns one kind of event only, such as "notification".

    bash
    curl https://scanr.ai/api/v2/scan/paginated-scan-events \  -H "Authorization: Bearer $SCANR_API_TOKEN" \  -H "Content-Type: application/json" \  -d '{"scan_id": 918273, "after": "2026-09-29T13:30:00Z", "before": "2026-09-29T20:00:00Z"}'

    This is the efficient way to follow a scan through the day: poll the events every few minutes instead of re-running the scan. Set after to the newest event_time you have seen. Since after includes the boundary, that event comes back again, so skip any event_id you already hold.

    Scan history

    GET /api/v2/scan/myscans/scan-history lists the scans you ran (from the API, the MCP server or the app) in the last 30 days, with each run's payload and results. Filter it with scanName, and page with page (starting at 1) and pageSize:

    bash
    curl "https://scanr.ai/api/v2/scan/myscans/scan-history?page=1&pageSize=20" \  -H "Authorization: Bearer $SCANR_API_TOKEN"

    Performance

    Power User and Business plans.

    scanr.ai measures every saved scan as if you had bought each stock when it entered the scan and sold it after a fixed holding period. The holding periods are time horizons:

    Horizon1d3d5d10d1M3M6M1yytd
    Holding period1 day3 days5 days10 days1 month3 months6 months1 yearto year end

    The values are case-sensitive: 1M is a month, and there is no 1m horizon.

    For each horizon you get the statistics of those simulated trades: win_rate, avg_percent_return, expectancy, sharpe_ratio, sortino_ratio, calmar_ratio, avg_max_drawdown, total_trades and more. Percentages are fractions (0.0784 is 7.84%). insufficient_history is true when a scan hasn't run long enough to judge.

    The main endpoints:

    EndpointReturns
    POST /scan/myscans/most-recent-scan-performancesThe latest statistics for your scans, grouped by horizon. Body: {"scan_ids": [918273], "universe": "listed"}. scan_ids: null means all of your scans.
    POST /scan/myscans/ticker-trade-outcomesEvery simulated trade for one ticker in one scan. Body: {"scan_id": 918273, "ticker": "NVDA", "time_horizon": "1M"}.
    POST /scan/myscans/scans-for-tickerWhich of your scans have traded a ticker. Body: {"ticker": "NVDA"}.
    POST /scan/myscans/top-n-scansYour best scans by a chosen statistic.

    universe selects which trades count: all (the default), listed (exchange-listed only) or otc.

    Measure a Scan's Edge walks through reading these numbers without fooling yourself.

    Next