Products
Quant Signals
Quant Signals exposes stored model predictions, feature attributions, voter evidence, model-health diagnostics, regime reads, factor data, and research candidates. Coverage and freshness depend on the available source snapshot; a returned row does not guarantee usable numerical evidence.
What's inside
Read each route's availability and source fields before using its output.
| Layer | Returned evidence | Availability limit |
|---|---|---|
| ML predictions | Point estimate, score, rank, and source date | available means a row exists; also check prediction_ |
| Prediction intervals | Bounds, half-width, method, and status | Missing or inconsistent intervals have null numerical fields |
| Feature drivers | SHAP attributions of the model serving now | Refused with 503 unless the drivers file names the active model bundle; refused for every ticker on 2026-09-25 |
| Voter decomposition | Available per-voter scores and diagnostics | A configured voter is not evidence that it supplied a usable signal |
| Model health | Voter IC drift, factor decay, directional hit rate, prediction tracker | Hit rate measures direction only; it is not interval coverage |
| Regimes | Forecast probabilities and past regime labels | A forecast can only weight regimes seen in its fitted history |
| Factor data | Firm characteristics, factor exposures, academic predictors | Lagged monthly archives; read the as_of date served |
| Research candidates | Candidate scores and contextual gates | A research score is not trade authorization |
The interval's available flag means its numbers are compatible with the
reported method. Adaptive intervals can be asymmetric: half_width is the
maximum absolute residual offset from a bias-corrected center, not necessarily
half the span or a radius around the raw prediction. Read geometry_status and
half_width_semantics; the stored row does not retain every calibration input.
Numerical compatibility does not verify calibration. calibrated_and_coherent
remains false and stated_coverage remains null without the matching calibration
artifact. A method name containing "conformal" is not proof of 90% coverage.
Recent realized coverage is unavailable: realised_ and
coverage_as_of are null, and realised_ is
unavailable_. Legacy coverage used an incompatible horizon.
Neither a stored method name nor a stated coverage target proves measured 30-day
performance. Respect capital_authorized, capital_, and
use when integrating the research output.
Access
3 credits per call · Starter and up
Quant Signals is included in Starter ($99/mo) and all paid plans. Each call to the ML stack (predictions, drivers, intervals, voters, trade setups, scans, factor data) costs 3 credits from your monthly wallet. The server-sent-event feeds for signals and decisions belong to the same product; see Real-Time Streaming, which also says which feeds carry data today. See Pricing & credits for plan limits and per-product breakdowns.
Endpoints
Every route on this page is GET, costs 3 credits and needs Starter or above. The MCP tool column is the tool name on the Wealthnow MCP server; each tool takes the same parameters as its route.
Predictions and uncertainty
| Method | Path | MCP tool | Description |
|---|---|---|---|
GET | /api/ | intel_ml_prediction | Stored prediction, nullable return estimate and interval, voter evidence, and universe rank. Interpret blended_score using score_semantics. Takes ?asset_. Optional (defaults to equity), but pass it explicitly for the nine colliding tickers so the namespace is never inferred (see below). Carries data_freshness, and data_staleness_days computed when you call. A fund or ADR is answered available: false with an excluded block, which reaches you as 503 paid_, not billed (see Funds and ADRs). |
GET | /api/ | prediction_intervals | Stored interval bounds for one ticker's latest prediction. ?alpha= (number, 0.01-0.5, default 0.1) sets the reported coverage_target (1 - alpha). Returns {ticker, alpha, coverage_. The bounds are withheld: lo, hi and half_width are null, with interval.withheld: "uncalibrated_ and a magnitudes_withheld block, because the band is built around blended_score × 100. A fund or ADR answers interval: null with excluded. See Example 4. |
GET | /api/ml/predict/{ticker} | ml_predict | Legacy route: the latest stored ensemble prediction row for one ticker (blended score, conviction, decile rank, per-voter sub-scores). predicted_return_pct, interval_lo, interval_hi and interval_half_width are null, with withheld: "uncalibrated_, point_estimate and point_ (see Withheld magnitudes). The row adds data_freshness, and data_staleness_days is computed when you call. A fund or ADR returns 404 with error: "excluded_ (see Funds and ADRs). Prefer ml_prediction for the nullable interval and research-use fields. |
GET | /api/v2/predict/multi_ | multi_horizon_batch | Batch predictions for up to 200 tickers in one call. ?tickers= (required, comma list of 1 to 200 symbols), ?horizons= (comma subset of 1d, 1w, 1m, 3m, 1y; default 1m,3m,1y). Only the 1m training horizon carries a leg; every other requested horizon is null. Each leg gives direction and percentile (100 = best); predicted_return_pct is null while point_ is false. Every ticker is in rows or missing_tickers; a fund or ADR is also named in excluded_tickers. See Batch predictions. |
GET | /api/v3/decision/ultimate | ultimate_decision | One consolidated verdict per ticker, projected from the latest stored ensemble prediction: direction (LONG/SHORT/NEUTRAL), blended score, predicted return, conviction, decile, rank, regime and interval bounds. ?ticker= (string, optional) returns {ticker, decision}; omit it for decisions[], up to 100 names ordered by absolute blended score. decision is null when the ticker has no stored prediction, and a fund or ADR answers decision: null with excluded. Answers read from the ensemble book (source: "ensemble_) carry data_freshness, and the list adds book_guard. |
Batch predictions across many tickers
GET / (MCP multi_horizon_batch, Quant Signals, Starter and up, 3 credits per call) answers up to 200 tickers from the stored prediction book in one call:
- One horizon carries a leg. The model was trained for
1m, so only1mgets a leg; every other horizon you ask for isnull, because no forecast was trained for it.training_horizonis1m,evaluated_horizonis1d, andhorizon_statusisunknown: do not present the1mleg as a one-month return forecast. - What a leg holds.
direction(positive,negativeorflat),percentile(100 = best rank in the scored universe;nullwhen the full ranking is not available) andsource. Whilepoint_isestimate_ supported false,predicted_return_pctisnulland the leg carrieswithheld: "uncalibrated_. The number it would have been ispoint_ estimate" blended_score × 100, a scored ordering described inpoint_estimate, not a forecast. Legs carry no interval. - Every ticker is accounted for. Each requested ticker appears once, in
rows(keyed by ticker, then horizon) or inmissing_tickers.missing_tickersmeans the ticker is not in the scored equity universe, never that the read failed. A fund or ADR the book scored is inmissing_tickersand also inexcluded_tickers, as{ticker, reason}; the response adds thebook_guardblock (see Funds and ADRs). - Dates.
as_of_tsis the newest prediction timestamp in the book,snapshot_age_sis how long ago this server loaded it, andstaleistruewhenas_of_tsis more than 3 weekdays old. - Refusals. A malformed symbol returns
400witherror: "invalid_naming them, more than 200 distinct symbols returnstickers" 400 too_many_tickers, no usable symbol returns400 no_valid_tickers, and an unknown horizon returns400 invalid_horizons. A server that has not loaded the book yet answers503witherror: "snapshot_cold"andretry_after_s: 5(alsoRetry-After: 5); retry after the wait. A loaded book that lacks the columns the route needs answers503 snapshot_unusable, also withRetry-After: 5, rather than reporting every ticker missing. While the book guard loads its sources the route answers503 snapshot_cold(Retry-After: 5), and when the guard cannot verify the book,503 snapshot_unusablewithRetry-After: 60and thebook_guardblock. None of these is billed.
curl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v2/predict/multi_horizon?tickers=AAPL,MSFT,ZZZZ&horizons=1m,3m" # Response structure; values are placeholders, not live data:# {# "ok": true,# "contract": "firmMultiHorizonBatch.v3",# "as_of_ts": "...",# "snapshot_age_s": ...,# "stale": true,# "horizons": ["1m", "3m"],# "horizon_status": "unknown",# "point_estimate_supported": false,# "point_estimate": { ... },# "training_horizon": "1m",# "evaluated_horizon": "1d",# "rows": {# "AAPL": {# "1m": {"predicted_return_pct": null, "direction": "positive", "percentile": ...,# "withheld": "uncalibrated_point_estimate", "source": "ensemble"},# "3m": null# },# "MSFT": { "...": "..." }# },# "missing_tickers": ["ZZZZ"]# }Rankings and market-wide scans
| Method | Path | MCP tool | Description |
|---|---|---|---|
GET | /api/v3/decision/trade_ | trade_setups | Research candidates with scores and regime context; availability and authorization gates still apply. ?limit= (1-500, default 25), ?min_conviction= (0-1, default 0). Each setup's direction is LONG when blended_score is 0.15 or more, SHORT when it is -0.15 or less, and NEUTRAL in between. The response carries universe_skew, counted over LONG and SHORT setups only: bullish or bearish when at least three setups are LONG or SHORT and at least 70% of those point the same way, otherwise mixed. regime_ names a curated basket for a one-sided screen (momentum for bullish, defensive for bearish) and is null when the skew is mixed. The book's age: as_of_ts (the newest prediction timestamp in the book), book_age_sessions, and is_stale, true when the book is more than stale_after_sessions (2) US equity sessions old, plus data_freshness. Funds and ADRs are left out and counted in book_guard. |
GET | /api/ | universe_scan | The latest scored universe ordered by absolute blended_score, so the strongest long and short names come first. ?limit= (1-5000, default 100). Rows below a liquidity floor are dropped and counted (see Example 7). Funds and ADRs are left out and counted in book_guard; the response carries data_freshness, and snapshot_stale is also true when the book is stale. |
GET | /api/ml/top-picks | ml_top_picks | Top-ranked tickers from the latest ensemble scoring snapshot, drawn from the top decile only (decile 10) and ordered by decile, blended score and conviction. The spec lists ?limit= (integer, default 20), but the server currently ignores it and returns up to 20 picks; ?min_conviction= (number, default 0.0) filters. Returns {as_; each pick is a stored prediction row, with predicted_return_pct, interval_lo, interval_hi and interval_half_width null, withheld: "uncalibrated_ and one magnitudes_withheld block on the response, and data_staleness_days computed when you call. as_of_ts is the newest prediction time among the served rows. Funds and ADRs are left out. picks is empty when no name is in the top decile. For a longer or deeper ranked list use trade_setups or universe_scan. |
GET | /api/v3/signals/fusion | signals_fusion | Fused signal score after voter aggregation (consensus across all layers). ?limit= (1-5000, default 100). Funds and ADRs are left out and counted in book_guard; the response carries data_freshness, and each row's data_staleness_days is computed when you call. |
Explaining the model
| Method | Path | MCP tool | Description |
|---|---|---|---|
GET | /api/ | intel_ml_drivers | Top-N SHAP feature attributions: which features pushed the score bullish or bearish and by how much. ?top= (default 5, max 20). Served only when the drivers explain the model bundle serving now; otherwise 503 with error_, not billed, which is every ticker on 2026-09-25. See Example 2. |
GET | /api/ | intel_voter_attribution | Evidence behind one voter's score on a ticker. Instrumented voters: insider_flow, options_flow and fundamental (metadata mode). ?voter= picks one of them; ?days_back= sets the lookback in days (for example 30). Other voters are not yet instrumented. |
GET | /api/ | intel_factor_importance | Fama-French 5-factor loadings plus the ensemble's Bayesian voter posteriors: "what drives the strategy's returns?" ?top_n= (1-500, default 50). Served from the factor-loading and voter-weight reports rather than a model importance table, it adds note, report_freshness and stale (see Example 8). With no importance measurement published: 503 with error_, not billed (details). |
GET | /api/v2/features/importance | feature_importance | The same ranked importance list with an optional model filter. ?top_n= (1-500, default 50), ?model= (string). Returns {features[], model, source}; each feature carries feature, importance and kind, and a list served from the factor-loading and voter-weight reports adds note, report_freshness and stale. With no importance measurement published: 503 with error_, not billed (details). |
GET | /api/ml/weights | ml_weights | Current ensemble voter weights, one row per market regime. Returns {regimes: {<regime>: row}, as_. Read it before interpreting a prediction. |
GET | /api/ml/weights/history | ml_weights_history | Voter weights over time, per regime, newest first. ?days= (1-365, default 30). Returns {rows[]}. |
GET | /api/v2/signals/correlation | signal_correlation | Pairwise correlation of the voter signals feeding the ensemble. High correlation means the vote count overstates independent evidence. Returns {correlation[], source}. |
Model health and track record
| Method | Path | MCP tool | Description |
|---|---|---|---|
GET | /api/v3/intel/voter_ | intel_voter_ic_drift | Per-voter information-coefficient drift against baseline, recomputed daily: live_ic, baseline_ic, ic_ratio, drift_status (green/yellow/red; gray means too little data, not degradation), sorted by drift severity. Carries stale: true when the measurement is older than 48 hours; do not downweight voters from a stale payload. The per-voter table has not been produced yet, so the route currently answers with no voter rows. |
GET | /api/v2/models/drift | model_drift | Feature and prediction drift. ?window_days= (1-365, default 30). Returns {drift[], window_days}, plus summary, source, report_freshness and stale when served from the daily voter-drift snapshot (see Example 5). With no drift measurement and no voter-drift snapshot: 503 with error_, not billed (details). |
GET | /api/v2/features/decay | factor_decay | How fast each signal's edge is fading: per factor live_ic, baseline_ic, retention (live_ic / baseline_ic; below 1 means decaying, below 0 a sign flip) and status, plus as_of, report_freshness and stale when served from the daily voter-drift snapshot. With no decay measurement and no voter-drift snapshot: 503 with error_, not billed (details). |
GET | /api/v3/learning/accuracy | prediction_accuracy | Directional hit rate of resolved predictions over a rolling window: {accuracy: {n, hit_. ?window_days= (1-3650, default 90). |
GET | /api/ | prediction_tracker | Recent predictions with their resolution state, newest first. ?limit= (1-5000, default 200). Returns {predictions[]}. When it answers from the stored book (source: "ensemble_), funds and ADRs are left out and the response adds data_freshness and book_guard. |
Regimes
| Method | Path | MCP tool | Description |
|---|---|---|---|
GET | /api/v2/regime/forecast | regime_forecast | Regime probabilities N days ahead. ?horizon_days= (1-252, default 21). Returns {current_; forecast is null when no forecast source is available. current_regime_as_of is the date of the prediction-book row current_regime was read from, and stale is true when that book is more than 2 US equity sessions old (book_age_sessions, stale_after_sessions), so the forecast starts from a past regime. A 503 with Retry-After: 5, not billed, means the book is still loading on that server. See Example 6. For today's label use market_regime on Macro, Rates & FX. |
GET | /api/v2/regime/history | regime_history | Daily regime labels over the last N calendar days. ?days= (1-3650, default 180). Returns {history: [{date, regime}]}; from the recorded sequence it adds coverage_end, window_days, as_of, stale and report_freshness. With no label in the window: 503 with error_, not billed, naming coverage_end, window_days and min_days_with_labels. See Example 6. |
Factor and characteristic data
| Method | Path | MCP tool | Description |
|---|---|---|---|
GET | /api/ | factor_predictors | Open-source academic predictor panel: 13 replicated accounting anomalies (Sloan accruals, Cooper-Gulen-Schill asset growth, Titman capital investment, Novy-Marx gross profitability, Fama-French operating profitability, net share issuance, and others) at month-end grain. The ticker is resolved to its security key automatically (the panel has no ticker column). ?latest=true returns the single most-recent row as a name→value map; otherwise the newest-last series (?start/?end, ?limit= 1-2000, default 600). Lagged archive: latest-available if no dates. Returns {ticker, permno, series | predictors, predictor_. |
GET | /api/ | factor_characteristics | The latest monthly firm-characteristic vector (about 460 columns, including the 102 published anomaly characteristics such as size, book-to-market, momentum, liquidity and accruals). ?as_of= (ISO date) returns the latest vector at or before that date. Returns {ticker, permno, as_. The panel's realized forward returns (fwd_* columns and excess_ret_1m) are the return after as_of, so they are withheld from characteristics and named in outcome_labels_withheld (see Example 9). |
GET | /api/ | factor_ | Monthly series of one characteristic, newest first. ?char= (required; a typo returns 422 unknown_, and so does a realized forward return such as fwd_ret_1m or excess_ret_1m), ?start= / ?end= (ISO dates, default full history), ?limit= (1-1200, default 600). Returns {ticker, permno, characteristic, n, series: [{date, value}]}. |
GET | /api/ | factor_exposures | Rolling Fama-French 5 plus momentum betas (beta_mkt, beta_smb, beta_hml, beta_rmw, beta_cma, beta_umd) with alpha, idiosyncratic and total volatility and regression R², plus their monthly history. ?limit= (1-1200 months, default 60). Returns {ticker, permno, model, as_. |
Price snapshot
| Method | Path | MCP tool | Description |
|---|---|---|---|
GET | /api/snapshot/{ticker} | quote_and_fundamentals | Latest price plus basic trading stats and a small valuation block for one ticker: {ticker, quote, fundamentals, prediction}. A heavy route: fetch one ticker per call. See Example 10. |
How old the prediction book is
The stored prediction book has not been regenerated since 2026-09-14. The routes that serve it say how old it is in a data_freshness block, graded when you call, on the rows actually served:
| Field | Meaning |
|---|---|
data_as_of | The newest as_of_ts among the rows served: the book's time, never the time of the response. |
data_age_hours, data_age_days | Its age when you called. |
sessions_behind | NYSE sessions after the book's New York date, up to and including today's. |
max_sessions_behind | 2. |
is_stale | true when sessions_behind is more than 2, when the age cannot be established, or when the stamp is in the future. |
stale_reason | Why the book is stale, naming its date; null when it is not. |
A stale book is still served, with this block, so read is_stale before presenting a score, rank or pick as current. data_freshness is on ml_prediction, ml_predict, ml_top_picks, prediction_intervals, ultimate_decision, trade_setups, universe_scan, signals_fusion and the book fallback of prediction_tracker. data_staleness_days on a served prediction row is computed from the row's own as_of_ts when you call; it used to pass through a stored value that was always 0.0.
Funds and ADRs are not ranked as equities
The equity model ranks companies on company characteristics that a fund or an ADR does not have, yet the book carries an equity-model score for several thousand of them, many on the same no-feature constant (SPY, AGG, QQQ and GLD share one). Since 2026-09-25 a fund or ADR (by security class in the security master) whose row carries an equity-model score is removed before any ranking is built or any per-ticker prediction is served. A row the pipeline's quarantine list says to refuse is removed the same way. Nothing is re-ranked: rank and decile keep the book's own values, computed over every row.
Ranking routes (ml_top_picks, trade_setups, universe_scan, ultimate_decision without a ticker, signals_fusion, the book fallback of prediction_tracker, and multi_horizon_batch) carry a book_guard block: verified, n_rows, n_served, n_excluded, excluded_by_reason (non_ counts funds and ADRs, quarantined:<code> refused quarantine rows), checks and rank_note.
A fund or ADR you ask for by name:
| Route | Answer |
|---|---|
ml_predict | 404 with error: "excluded_. detail says the ticker is a fund or ADR, that this is the answer for it and not an argument error, and that another ticker is not a substitute. |
ml_prediction | available: false with an excluded block, which reaches you as 503 paid_, not billed. |
prediction_intervals | 200 with interval: null, excluded and data_freshness. |
ultimate_ | 200 with decision: null and excluded. |
kelly_uncertainty | 200 with kelly_fraction: null and excluded. |
multi_horizon_batch | In missing_tickers, and named with its reason in excluded_tickers. |
quote_and_fundamentals | prediction: null with prediction_excluded. |
An excluded block carries error_, error_message, reason and ticker.
When the guard cannot load the security master it cannot verify any row. A ranking or per-ticker prediction then answers 503 with error: "upstream_, error_, the book_guard block and Retry-After: 60, not billed, instead of serving unchecked rows. multi_horizon_batch answers 503 snapshot_cold or snapshot_unusable instead (see Batch predictions), and quote_and_fundamentals keeps its quote and fundamentals and names the gap in prediction_unavailable.
Examples
Example 1: Read a prediction and handle unavailable intervals
Request an explicit asset class. The response excerpt below is illustrative, not a live result or a measured performance claim.
curl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v3/intel/ml_prediction/AAPL?asset_class=equity"{ "ok": true, "ticker": "AAPL", "available": true, "prediction_evidence_available": true, "use": "research_only_prediction", "capital_authorized": false, "prediction": { "predicted_return_pct": null, "blended_score": 0.027183, "conviction": 0.2767, "decile": 9, "rank": 2013, "n_universe": 13049, "percentile_rank": 84.57, "withheld": "uncalibrated_point_estimate" }, "point_estimate_supported": false, "conformal_interval": { "available": false, "status": "unavailable_invalid_or_missing_interval", "reason": "Interval bounds, half-width, and point estimate are missing, non-finite, or inconsistent; numeric interval withheld.", "lo": null, "hi": null, "half_width": null, "method": null, "calibrated_and_coherent": false, "stated_coverage": null, "realised_coverage_recent": null, "coverage_as_of": null, "realised_coverage_status": "unavailable_migration_pending" }}import mathimport osimport requests def finite_number(value): return isinstance(value, (int, float)) and not isinstance(value, bool) and math.isfinite(value) r = requests.get( "https://firm.wealthnow.io/api/v3/intel/ml_prediction/AAPL", params={"asset_class": "equity"}, headers={"Authorization": f"Bearer {os.environ['WEALTHNOW_API_KEY']}"}, timeout=30,)r.raise_for_status()data = r.json() if data.get("ok") and data.get("prediction_evidence_available"): pred = data.get("prediction") or {} interval = data.get("conformal_interval") or {} print(f"Ticker: {data['ticker']}") estimate = pred.get("predicted_return_pct") if finite_number(estimate) and data.get("point_estimate_supported") is True: print(f"Predicted return: {estimate:.2f}%") elif pred.get("withheld"): # Withheld on purpose: the score is a ranking, not a return forecast. print(f"Rank {pred.get('rank')} of {pred.get('n_universe')}, " f"decile {pred.get('decile')} (magnitude withheld: {pred['withheld']})") else: print("Predicted return unavailable.") lo, hi = interval.get("lo"), interval.get("hi") if interval.get("available") is True and finite_number(lo) and finite_number(hi): print(f"Stored research interval: [{lo:.2f}%, {hi:.2f}%]") coverage = interval.get("stated_coverage") if interval.get("calibrated_and_coherent") is True and finite_number(coverage): print(f"Stated coverage target: {coverage:.0%}") else: print("Interval is uncalibrated; no coverage target is established.") else: print(f"Forecast interval unavailable: {interval.get('reason') or interval.get('status') or 'no interval supplied'}") realized = interval.get("realised_coverage_recent") if finite_number(realized): print(f"Reported realized coverage: {realized:.1%}") else: print("Recent realized coverage unavailable.") if data.get("capital_authorized") is not True: print("Research only; capital authorization is not established.")else: print("Numerical prediction evidence unavailable.")Example 2: Get SHAP feature drivers and top trade setups
Pull the top bullish/bearish features for a ticker and the current highest-conviction trade picks.
SHAP values explain one model, so ml_drivers serves drivers only when every row of the drivers
file names the same model bundle (model_bundle_sha256) and that bundle is the one serving now.
Otherwise it answers 503 with error: "upstream_ and
error_, not billed, and binding.status says why:
binding.status | Meaning | Retry-After |
|---|---|---|
unbound | The drivers file records no model bundle, so it cannot be tied to the model serving now. | 3600 |
mixed_bundles | The file mixes rows from more than one model bundle. | 3600 |
foreign_bundle | The drivers were computed by a model bundle other than the one serving now. | 3600 |
active_bundle_unknown | The serving model bundle could not be read, so the drivers cannot be checked against it. | 300 |
On 2026-09-25 the published drivers file records no bundle, so every ticker is refused with
unbound. A failed read of the file is refused the same way, with error_code
archive_read_timeout (the read took more than 5 seconds, Retry-After: 300),
archive_read_failed (the file could not be read, 300) or upstream_ (the file
holds no rows, 3600). None of these refusals means the ticker is outside the model's scoring
universe, and none is an empty result: the drivers could not be established, so do not describe
them.
A served answer carries model_bundle_sha256, as_of (when the drivers were computed), stale
(true past 72 hours) and report_freshness. When the bound file has no rows for the ticker, the
answer is marked available: false and carries the same fields; like any paid result marked
unavailable, it reaches you as a 503 paid_, not billed.
# Top SHAP drivers for AAPLcurl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v3/intel/ml_drivers/AAPL?top=5" # The refusal on 2026-09-25 (HTTP 503, Retry-After: 3600). error, error_code and# binding.status are the values captured for AAPL; the rest shows the shape:# {# "ok": false,# "error": "upstream_unavailable",# "error_code": "upstream_dead_drivers_unbound",# "product": "ml_drivers",# "data_available": false,# "is_empty_result_set": false,# "detail": "NO DRIVERS WERE SERVED for AAPL: the SHAP drivers file does not record which model bundle produced it, ... THIS DOES NOT MEAN AAPL IS OUTSIDE THE MODEL'S SCORING UNIVERSE, ...",# "binding": {"bound": false, "status": "unbound", "drivers_bundle_sha256": null,# "active_bundle_sha256": null, "as_of": "..."},# "coverage": {"drivers_as_of": "..."},# "billing": {"charged": false, "policy": "..."},# "retry_after_s": 3600,# "ticker": "AAPL"# } # A served answer. Illustrative response structure, not a live sample:# {# "ok": true,# "timestamp": "2026-07-05T10:30:15Z",# "ticker": "AAPL",# "available": true,# "tier": "tier_2",# "n_features_returned": 5,# "drivers": [# {# "rank": 1,# "feature": "price_momentum_20d",# "shap_value": 0.287453,# "direction": "bullish"# },# {# "rank": 2,# "feature": "earnings_surprise_pct",# "shap_value": 0.156240,# "direction": "bullish"# },# {# "rank": 3,# "feature": "short_interest_change",# "shap_value": -0.082150,# "direction": "bearish"# },# {# "rank": 4,# "feature": "insider_net_transactions",# "shap_value": 0.058640,# "direction": "bullish"# },# {# "rank": 5,# "feature": "options_iv_percentile",# "shap_value": -0.031240,# "direction": "bearish"# }# ],# "model_bundle_sha256": "...",# "as_of": "2026-07-05T02:10:00+00:00",# "stale": false,# "report_freshness": {"ml_drivers": {"as_of": "2026-07-05T02:10:00+00:00", "as_of_field": "as_of_ts",# "provenance": "payload", "report_age_hours": 8.3, "max_age_hours": 72.0, "stale": false}},# "interpretation": "Top-5 SHAP attributions for the tier_2-tier ensemble model's score on AAPL. Positive shap_value = the feature pushed the score higher (bullish), negative = lower (bearish).",# "source": "reports/ml_drivers/latest.parquet (S3 firm-runtime sidecar)"# } # Top trade setups (LONG/SHORT picks with conviction)curl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v3/decision/trade_setups?limit=10&min_conviction=0.5" # Illustrative partial structure, not a live sample:# {# "ok": true,# "timestamp": "2026-07-05T10:30:30Z",# "setups": [# {# "ticker": "AAPL",# "direction": "LONG",# "conviction": 0.642,# "blended_score": 0.642,# "predicted_return_pct": null,# "interval_lo": null,# "interval_hi": null,# "withheld": "uncalibrated_point_estimate",# "decile": 7,# "rank": 1850# },# {# "ticker": "TSM",# "direction": "SHORT",# "conviction": 0.534,# "blended_score": -0.534,# "predicted_return_pct": null,# "interval_lo": null,# "interval_hi": null,# "withheld": "uncalibrated_point_estimate",# "decile": 2,# "rank": 12388# }# ],# "universe_skew": "bullish",# "regime_warranted_alternative": {# "strategy": "momentum",# "rationale": "Screen is heavily bullish ...",# "candidates": [# { "ticker": "MTUM", "name": "iShares MSCI USA Momentum Factor", "thesis": "...", "expected_role": "factor_momentum" },# { "ticker": "QUAL", "name": "iShares MSCI USA Quality Factor", "thesis": "...", "expected_role": "factor_quality" },# { "ticker": "SPHQ", "name": "Invesco S&P 500 Quality", "thesis": "...", "expected_role": "alt_quality" }# ]# },# "filter": { "min_conviction": 0.5 },# "source": "warehouse:ensemble-scores",# "as_of_ts": "2026-07-02T21:05:00+00:00",# "book_age_sessions": 0,# "is_stale": false,# "stale_after_sessions": 2# }Example 3: Academic predictor vector for a ticker
Pull the compact academic characteristic vector for a name (13 replicated accounting anomalies) without reading the full characteristic panel, which factor_characteristics serves. The predictor panel is a lagged archive keyed by a security key; the exchange symbol is resolved automatically. Use latest=true for the single most-recent row.
curl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v3/factors/predictors/AAPL?latest=true" # Illustrative response structure, not a live sample:# {# "ok": true,# "timestamp": "2026-07-05T10:31:00Z",# "ticker": "AAPL",# "permno": 14593,# "mode": "latest",# "as_of": "2026-02-28",# "n_predictors": 13,# "predictors": {# "accruals": -0.031,# "asset_growth": 0.084,# "investment": 0.052,# "gross_profit_at": 0.412,# "oper_prof": 0.318,# "cash_at": 0.089,# "leverage_change": -0.014,# "earnings_consistency": 0.91,# "revenue_growth": 0.021,# "positive_ni": 1,# "positive_cfo": 1,# "current_ratio": 1.04,# "share_issuance": -0.008# },# "predictor_glossary": {# "accruals": "Sloan (1996) balance-sheet accruals anomaly",# "gross_profit_at": "Novy-Marx gross profitability (GP/assets)",# ...# },# "note": "this panel has NO ticker column ...",# "source": "warehouse:factor-predictors-archive"# }Example 4: Interval bounds at a chosen alpha
prediction_intervals returns the stored interval for the latest prediction. coverage_target is 1 - alpha, computed from your request; changing alpha does not recompute the bounds. Neither alpha nor coverage_target is a measured coverage rate, and the same calibration caveats as ml_prediction apply. When you need calibrated_and_coherent, stated_coverage and the capital-authorization fields, call ml_prediction instead.
curl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v2/predict/AAPL/intervals?alpha=0.2"Illustrative structure, not a live sample:
{ "ok": true, "timestamp": "2026-09-24T14:05:11Z", "ticker": "AAPL", "alpha": 0.2, "coverage_target": 0.8, "interval": { "lo": null, "hi": null, "half_width": null, "withheld": "uncalibrated_point_estimate" }, "as_of_ts": "2026-09-14T07:05:00+00:00", "data_freshness": {"data_as_of": "2026-09-14T07:05:00+00:00", "max_sessions_behind": 2, "is_stale": true, "...": "..."}, "magnitudes_withheld": { "fields": ["interval.lo", "interval.hi", "interval.half_width"], "reason": "...", "use_instead": ["rank", "percentile", "decile"], "...": "..." }}Since 2026-09-25 the bounds are withheld: the band is built around predicted_return_pct, which is blended_score × 100 and not a calibrated forecast, so lo, hi and half_width are null, interval.withheld says why, and magnitudes_withheld names the fields and what to use instead. Handle the other null cases too: interval is null when the ticker has no stored prediction (with warning: "no_ when no prediction table is available), and for a fund or ADR, which also carries excluded (see Funds and ADRs).
Example 5: Check model health before trusting a call
Three reads answer "is the model still working?": directional accuracy over a window, how fast each signal's edge is decaying, and whether the voters have drifted.
# Directional hit rate over the last 90 days of resolved predictionscurl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v3/learning/accuracy?window_days=90" # Per-signal decaycurl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v2/features/decay" # Drift over the last 30 dayscurl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v2/models/drift?window_days=30"Illustrative structures; the numbers are placeholders, not measured results:
{ "ok": true, "timestamp": "2026-09-24T14:06:02Z", "accuracy": { "n": 1200, "hit_rate": 0.5, "window_days": 90 }}{ "ok": true, "timestamp": "2026-09-24T14:06:03Z", "decay": [ { "factor": "options_flow", "live_ic": 0.012, "baseline_ic": 0.031, "retention": 0.387, "status": "yellow" } ], "as_of": "2026-09-23T19:01:35+00:00", "source": "voter_ic_drift_sidecar", "note": "retention = live_ic/baseline_ic; <1 means the signal is decaying", "report_freshness": { "voter_ic_drift": { "as_of": "2026-09-23T19:01:35+00:00", "as_of_field": "as_of", "provenance": "payload", "report_age_hours": 19.1, "max_age_hours": 80.0, "stale": false } }, "stale": false}{ "ok": true, "timestamp": "2026-09-24T14:06:04Z", "drift": [ // one row per voter, each with its own "stale" and "stale_at_build" ], "summary": { "as_of": "2026-09-23T19:01:35+00:00", "n_voters": 19, "n_red": 1, "n_yellow": 3, "n_green": 15, "worst_status": "red" }, "source": "voter_ic_drift_sidecar", "window_days": 30, "report_freshness": { "voter_ic_drift": { "...": "as in the decay example" } }, "stale": false}How to read them:
hit_rateis the share of resolved predictions in the window whose predicted and realized returns had the same sign. It says nothing about magnitude or interval coverage.accuracyis null when no resolved outcomes fall in the window.- When
sourceisvoter_ic_drift_sidecar,driftanddecaycome from the daily per-voter IC snapshot, sowindow_daysis echoed rather than applied. - A response served from a report snapshot grades it by the snapshot's own timestamp.
report_freshnessholds one entry per snapshot:as_of,as_of_field(the field it was read from),provenance,report_age_hours,max_age_hoursandstale; the top-levelstaleistruewhen any of them is.provenanceispayload, orunknown_generation_timewhen the snapshot carries no timestamp of its own, and thenstaleistrue: the time a file was copied or uploaded is not taken as its age. The voter-drift snapshot's limit is 80 hours, so a Friday snapshot lasts the weekend. A stale snapshot describes the voters on itsas_ofdate, not today. - In
drift, each voter'sstaleistruewhen its producer marked it stale or the snapshot is stale; the producer's own flag is kept asstale_at_build. - For per-voter detail with the staleness flag, call
/api/v3/intel/voter_.ic_ drift /api/lists the individual calls behind the hit rate. If the response carriesv3/ learning/ prediction_ tracker? limit=50 source: "ensemble_, the tracker table was unavailable and the rows are the latest stored predictions with no resolution state.predictions_ fallback"
Example 6: Regime forecast and history
curl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v2/regime/forecast?horizon_days=21" curl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v2/regime/history?days=180"Illustrative structures, not live samples:
{ "ok": true, "timestamp": "2026-09-24T14:07:00Z", "current_regime": "normal", "horizon_days": 21, "forecast": [ { "regime": "normal", "probability": 1.0 } ], "source": "empirical_transition_matrix", "fitted_at": "2026-06-12", "window_days": 26, "caveat": "Live regime has been constant over the observed window, so the empirical transition matrix is degenerate (P(stay)=1). ...", "current_regime_as_of": "...", "current_regime_source": "prediction_book", "as_of": "...", "book_age_sessions": 8, "stale_after_sessions": 2, "stale": true}{ "ok": true, "timestamp": "2026-09-24T14:07:01Z", "history": [ { "date": "2026-06-11", "regime": "normal" }, { "date": "2026-06-12", "regime": "normal" } ], "source": "regime_transition_config", "fitted_at": "2026-06-12", "coverage_end": "2026-06-12", "note": "daily max_by(regime) from warehouse:ensemble-scores", "window_days": 180, "as_of": "2026-06-12", "stale": true, "report_freshness": { "regime_transition": { "as_of": "2026-06-12", "as_of_field": "as_of", "provenance": "payload", "report_age_hours": 2510.1, "max_age_hours": 72.0, "stale": true } }}A days too short to reach coverage_end is refused. An excerpt of the refusal for days=30
on 2026-09-25: error_code, reason, coverage_end and window_days are the captured values
(with Retry-After: 3600), the other fields are shown as the route builds them, and
min_days_with_labels is the value it computes that day:
{ "ok": false, "error": "upstream_unavailable", "error_code": "dataset_unavailable", "dataset": "regime_history", "reason": "table_missing", "retryable": false, "coverage_end": "2026-06-12", "window_days": 30, "min_days_with_labels": 105}The forecast starts from current_regime, which is read from the prediction book. current_regime_as_of (also as_of) is the timestamp of the book row it came from, never the time of the request, and current_regime_source names where it was read (prediction_book, or regime_forecast_table when a stored forecast exists). stale is true when that book is more than stale_after_sessions (2) US equity sessions old, counted in book_age_sessions; the forecast then projects from a past regime. A server that has not loaded the book yet answers 503 with error: "upstream_, error_ (reason: "source_, cause: "snapshot_cold") and Retry-After: 5, not billed, instead of timing out or assuming a regime; retry after the wait.
When source is empirical_, the forecast is a Markov projection of the observed daily regime sequence, so it can only assign probability to regimes that appear in that sequence. Read fitted_at, window_days and caveat before quoting it: a sequence with one regime yields a forecast that the current regime persists, which says nothing about regime-change risk. History served from the same fitted sequence (source: "regime_) stops at coverage_end, the last date the sequence covers. days counts calendar days back from today, not entries, so it returns only the labels dated inside that window. A window that starts after coverage_end holds no label, and the call is refused: 503 with error: "upstream_ and error_, not billed (Retry-After: 3600). The body names coverage_end, the window_days you asked for, and min_days_with_labels, the smallest days that reaches coverage_end. Retrying with the same days will not help; pass at least min_days_with_labels. The recorded sequence currently ends on 2026-06-12, so on 2026-09-25 any days below 105 is refused, and the default of 180 will be refused from 2026-12-10.
A served history adds window_days, and grades the sequence by its newest label, on the 72-hour limit of a daily report: as_of is coverage_end, and stale and report_freshness (fields as in Example 5) say how old it is. While the sequence ends on 2026-06-12, every served history carries stale: true.
Example 7: Scan the universe, then read one verdict
curl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v3/decision/universe_scan?limit=25" curl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v3/decision/ultimate?ticker=AAPL"Illustrative structures, not live samples:
{ "ok": true, "timestamp": "2026-09-24T14:08:00Z", "universe": [ { "ticker": "AAPL", "blended_score": 0.642, "conviction": 0.642, "decile": 7, "adv_dollars": 11200000000.0, "in_earnings_blackout": false } ], "n": 25, "n_filtered_illiquid": 312, "n_filtered_unknown_adv": 1840, "liquidity_filter": "applied", "snapshot_age_s": 1840.0, "snapshot_stale": false, "min_adv_dollars": { "long": 5000000.0, "short": 10000000.0 }, "source": "warehouse:ensemble-scores"}{ "ok": true, "timestamp": "2026-09-24T14:08:01Z", "ticker": "AAPL", "decision": { "symbol": "AAPL", "direction": "LONG", "blended_score": 0.642, "predicted_return_pct": null, "conviction": 0.642, "decile": 7, "rank": 1850, "regime": "normal", "interval_lo": null, "interval_hi": null, "withheld": "uncalibrated_point_estimate", "as_of_ts": "2026-09-24T10:00:00+00:00", "source": "ensemble_predictions" }, "source": "ensemble_predictions"}Scan rows are stored prediction rows plus two annotations:
adv_dollars: average daily dollar volume, null when unknown.in_earnings_blackout:trueorfalsewhen the blackout calendar covers the name,nullwhen it does not. Blackout names are annotated, never removed.
When liquidity_filter is applied, rows below the default floors ($5M a day for long-signal rows, $10M for short-signal rows, which need borrow) are dropped and counted in n_filtered_illiquid. Rows with no volume data are dropped and counted separately in n_filtered_unknown_adv. If the volume data covers too little of the universe, the scan is served unfiltered with liquidity_.
The ultimate_decision verdict is the stored ensemble row in decision form. It is research output with the same authorization limits as ml_prediction, not an instruction to trade.
Withheld magnitudes. Every route that serves the prediction book withholds the same value: ml_prediction, ml_predict, ml_top_picks, trade_setups, ultimate_decision, universe_scan, signals/fusion, prediction_tracker and briefing/overnight, and the v1 routes (/api/predict/{ticker}, /api/predictions, /api/top-picks, /api/signals, /api/scan, /api/snapshot/{ticker}, /api/risk/{ticker} and /api/dashboard). On each, predicted_return_pct and the band built around it (interval_lo/interval_hi, plus interval_half_width on ml_predict and ml_top_picks, or lo/hi/half_width in conformal_interval) are null, and the row or block carries withheld: "uncalibrated_; prediction_intervals withholds lo, hi and half_width the same way. The value was blended_score × 100: a monotone rescaling of the ranking score, not a calibrated return forecast. Direction, conviction, decile, rank and the ordering of rows are unchanged, so rank on those. point_estimate describes the field and point_ is false. point_estimate.field names the key the rows carry, predicted_return_pct on ml_prediction, ml_predict and multi_horizon_batch. Until 2026-09-25 it always said expected_return_pct. The same rule withholds the Kelly size that kelly_uncertainty would derive from that value (see Kelly sizing). /api/risk/{ticker} serves kelly_fraction: null with kelly_withheld instead of a size of 0. /api/predictions/accuracy and /api/v2/models/calibration withhold the mean point estimate and the band's mean width; hit rate, IC and coverage are still served. /api/results withholds its predicted column and says why in track_record_note.
Example 8: Ensemble weights and signal overlap
curl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/ml/weights" curl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/ml/weights/history?days=90" curl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v2/signals/correlation" curl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v2/features/importance?top_n=10"ml_weights returns one stored weights row per regime under regimes, keyed by regime label, with as_of_ts; ml_weights_history returns the same rows over time under rows, newest first. Each row carries regime, as_of_ts, lookback_days, n_outcomes_used, and a w_<voter> weight (and an ic_<voter> information coefficient where the retuner measured one) per voter. These routes return the payload without the ok/timestamp envelope. Illustrative structure:
{ "regimes": { "normal": { "regime": "normal", "as_of_ts": "2026-09-23T00:00:00+00:00", "lookback_days": 60, "n_outcomes_used": 5200, "w_ml_ensemble": 0.14, "ic_ml_ensemble": 0.021 } }, "as_of_ts": "2026-09-23T00:00:00+00:00"}signal_correlation returns correlation[], one object per voter with its correlation to every other voter:
{ "ok": true, "timestamp": "2026-09-24T14:09:00Z", "correlation": [ { "voter": "options_flow", "options_flow": 1.0, "insider_flow": 0.08, "sentiment_news": 0.21 } ], "n_rows_sampled": 2000, "source": "computed_from_latest_snapshot_voter_scores"}When the latest snapshot cannot be used, the matrix comes from a fitted historical panel instead, with n_observations, fitted_at and source: "historical_.
feature_importance returns {features[], model, source}. Each item has feature, importance and kind (ff5_factor_loading or voter_posterior_weight when served from the factor-loading and voter-posterior snapshots). Served from those snapshots, importance is a factor beta or a voter's posterior weight, not a feature importance, and note says so. report_freshness grades each snapshot by its own timestamp (limit 14 days for the factor loadings, 72 hours for the voter weights; fields as in Example 5), and stale is true when either is stale. factor_importance answers the same way. model narrows the list only when per-model importances are available (source: "warehouse:feature-importance"); otherwise it is echoed.
Example 9: Firm characteristics and factor exposures
# Full characteristic vector as of a past monthcurl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v3/factors/characteristics/AAPL?as_of=2024-12-31" # One characteristic over time (book-to-market)curl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v3/factors/characteristics/AAPL/history?char=bm&start=2020-01-01" # Rolling factor betas, last 24 months of historycurl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v3/factors/exposures/AAPL?limit=24"Illustrative structures, not live samples:
{ "ok": true, "timestamp": "2026-09-24T14:10:00Z", "ticker": "AAPL", "permno": 14593, "characteristic": "bm", "n": 2, "series": [ { "date": "2025-11-30", "value": 0.045 }, { "date": "2025-10-31", "value": 0.05 } ], "start": "2020-01-01", "source": "warehouse:factor-panel-archive", "identity_verified": true}{ "ok": true, "timestamp": "2026-09-24T14:10:02Z", "ticker": "AAPL", "permno": 14593, "model": "FF5+UMD rolling OLS", "as_of": "2025-11-30", "exposures": { "beta_mkt": 1.21, "beta_smb": -0.35, "beta_hml": -0.52, "beta_rmw": 0.41, "beta_cma": -0.28, "beta_umd": 0.05 // plus alpha, volatility and R² columns }, "n": 24, "history": [ // one row per month, newest first ], "source": "warehouse:factor-betas-archive", "identity_verified": true}All three routes resolve the symbol to one security first. A symbol that cannot be pinned to exactly one current security returns error: "ticker_ with identity_ and no numbers, rather than another company's panel. These are lagged monthly archives: read as_of (or the newest series date) before treating a value as current. A name too young for the estimation window returns exposures: null with a warning.
Point-in-time use. ?as_of= picks the month. The panel also stores realized forward returns, its training labels: fwd_ret_1m is the following month's return and excess_ret_1m is that same forward return less the risk-free rate. Neither was known at as_of, and used as a feature either one puts look-ahead bias into a backtest. So the vector withholds every fwd_* column and excess_ret_1m, and outcome_labels_withheld names the ones it left out. /history?char= refuses them with 422 unknown_; the message says the column is a realized forward return. ret_crsp, which stays in the vector, is the return of the month the row is dated, realized by as_of.
Example 10: Price snapshot
curl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/snapshot/AAPL"Illustrative structure, not a live sample:
{ "ok": true, "timestamp": "2026-09-24T14:11:00Z", "ticker": "AAPL", "quote": { "price": 227.48, "prev_close": 225.9, "change": 1.58, "change_pct": 0.7, "volume": 41250000, "bid": 227.46, "ask": 227.5, "as_of_ts": "2026-09-24T14:10:58Z" }, "fundamentals": { "market_cap": 3400000000000.0, "pe_ratio": 34.6, "forward_pe": null, "pb_ratio": null, "ev_to_ebitda": null, "dividend_yield": null, "beta": 1.21, "sector": "Technology", "industry": "Consumer Electronics" }, "prediction": { "ticker": "AAPL", "as_of_ts": "2026-09-24T13:30:00+00:00", "blended_score": 0.18, "predicted_return_pct": 0.42, "conviction": 0.61, "decile": 8, "interval_lo": -1.9, "interval_hi": 2.7, "voters": { "ml_ensemble": 0.22, "fundamental": 0.09 } // plus prediction_id, knowledge_ts, tier, regime, rank, interval_half_width, // coverage, staleness and model fields, and the other seven voters }}quote and fundamentals can each be null, and any field inside them can be null when the source does not carry it. In quote:
volumeis the current session's volume only. It isnullbefore 04:00 ET, on weekends and exchange holidays, and whenever the source's daily bar is empty or from an earlier session, rather than the previous session's volume.as_of_tsis the source's last-trade time.- When
priceandprev_closeare both known,changeispriceminusprev_closeandchange_pctis that change as a percent ofprev_close. When the source sends a change but no previous close,prev_closeis derived aspriceminuschange.
prediction is the ticker's stored ensemble prediction row (blended score, return estimate, conviction, decile, interval bounds and per-voter scores) when one exists, and null otherwise; its data_staleness_days is computed from the row's as_of_ts when you call. For a fund or ADR, prediction is null and prediction_excluded says why; when the book guard cannot verify the book, prediction is null and prediction_unavailable says so, and the quote and fundamentals are still served (see Funds and ADRs). Call ml_prediction when you also need its calibration and research-use fields. The snapshot is billed as Quant Signals (Starter, 3 credits), so a Free key gets 402 plan_required; see the quickstart for how that differs from a plain quote.
Ticker collisions: crypto vs equity
Nine tickers name both a crypto asset and a US-listed equity: BTC, ETH, LINK, LTC, COMP, ARB, NEAR, APT, ATOM.
asset_class defaults to equity; pass it explicitly so the namespace is never inferred:
# the US-listed equity, not Bitcoincurl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v3/intel/ml_prediction/BTC?asset_class=equity"Responses for those nine carry a disambiguation note:
{ "ticker_collision": { "note": "This is the US-listed equity 'BTC'. For the crypto asset, pass asset_class=crypto.", "crypto_available": false, "asset_class": "equity" }}Requesting the crypto side fails closed with 404 crypto_ rather than silently returning the equity. That error never means "use the equity instead." Non-colliding tickers are unaffected.