Products
Research Datasets
Direct, row-level access to the licensed multi-decade research archives behind the API (fundamentals, estimates, credit, options, governance, ownership, and factor returns), plus the derived feature panels the platform computes for its own models. Where the product endpoints give you a curated answer, these give you the underlying table.
What's inside
Two families of routes:
| Method | Endpoint | Plan / credits | MCP tool | Description |
|---|---|---|---|---|
GET | /api/v2/datasets | Starter+ / 4 credits (funds) | research_datasets | List every servable licensed dataset slug. No parameters. |
GET | /api/v2/datasets/{dataset} | Varies by dataset, see below | research_read | Read rows from one licensed dataset. ticker (string, optional): on most datasets, an exact filter on the dataset's own symbol column, when it has one; financial_ratios and implied_vol_by_ticker resolve the symbol before reading (see below). limit (integer, 1 to 50000, default 1000). Reads without ticker are capped at 5,000 rows. |
GET | /api/v3/features/datasets | Starter+ / 3 (quant_signals) | features_catalogue | The feature-store catalogue: every derived panel with measured coverage and freshness. No parameters. |
GET | /api/v3/features/{dataset} | Starter+ / 3 (quant_signals) | features_dataset | Read one derived panel: one symbol's time series, or the latest cross-section. ticker (string, optional). limit (integer, 1 to 5000, default 500). since, until (YYYY-MM-DD, optional, inclusive). |
The licensed datasets take only ticker and limit. There are no dataset-specific filters: to narrow a table further, filter the rows you receive.
Every licensed-dataset response carries a note describing that dataset's coverage, keying and any quirks. Read it before building against a dataset.
{ "ok": true, "timestamp": "...", "dataset": "zacks_consensus", "n": 1, "rows": [ ... ], "source": "...", "note": "Daily sell-side EPS consensus (mean/std/count by reference period), 2.9M rows 2024-08 → present, ~5-week lag."}Access & cost
Listing the licensed catalogue requires Starter+ and costs 4 credits under funds. The feature-store routes bill as quant_signals: Starter+, 3 credits per call.
Each licensed dataset bills at the credit cost of the product group it belongs to, and is gated by that group's plan. The groups are:
| Billed as | Plan | Credits | Datasets |
|---|---|---|---|
funds | Starter+ | 4 | Every dataset not listed below |
news_sentiment | Starter+ | 2 | zacks_consensus, zacks_surprises |
alt_data | Pro+ | 3 | boardex_profile, boardex_composition, boardex_networks, audit_fees, audit_restatements, auditor_changes |
events_transcripts | Pro+ | 3 | ciq_keydev, ciq_transcripts |
credit | Pro+ | 4 | markit_cds, cds_composites, markit_credit_indices, ciq_ratings, dealscan_facility, dealscan_pricing, trace |
options_flow | Pro+ | 4 | markit_short |
The cost of each call is reported in the X-Credits-Cost response header. Read it rather than assuming. Failed requests, including an unknown slug (404), are not billed.
Recently added
zacks_consensus: Starter+ · 2 credits. Daily sell-side EPS consensus (mean / std / count by reference period), 2.9M rows, 2024-08 to present, ~5-week lag. Columns include ticker, est_type, reference_period, observation_date, est_mean, est_std, number_of_est.
zacks_surprises: Starter+ · 2 credits. Quarterly EPS actual-vs-consensus surprises (68.7k rows) with report dates. Columns include ticker, report_date, reference_period, actual_eps, consensus_eps, surprise_pct, number_of_est.
Not every surprise row has a consensus: consensus_eps and surprise_pct are null where no estimate existed (number_of_est: 0). Filter on number_of_est > 0 when computing surprise statistics.
markit_credit_indices: Pro+ · 4 credits. CDX + iTraxx (EU / Asia / SovX) daily composite spreads and prices by series / version / tenor, 2018 to T-2, 3.8M rows. Index-keyed (indexfamily, indexticker): there is no equity ticker column. For the curated view with on-the-run filtering, use /api/v3/credit/indices instead.
boardex_networks: Pro+ · 3 credits. Director interlock edges (which boards share directors), 5.0M rows, weekly refresh. Keyed by boardid pairs; resolve ids via boardex_profile. Columns include associationtype, boardname, companyname, directorname, overlapyearstart, overlapyearend, role.
Governance archives now refresh weekly
boardex_profile and boardex_composition were repointed on 2026-08-01 from a frozen 2026-03 archive to a weekly-refreshing pull. Columns now follow the licensed-research-native shape rather than the previous normalized one. If you built against the old column names, re-read the note and adjust. boardex_composition carries role spells keyed by boardid / directorid; resolve both via boardex_profile.
The licensed catalogue
GET /api/v2/datasets returns the servable slugs in datasets and their number in count. That response is authoritative, and an unknown slug returns 404. The table below is a guide to what the slugs hold and how ?ticker= behaves on each; if it and your listing ever disagree, trust the listing.
On most datasets ?ticker= is an exact match on the dataset's own symbol column. On a dataset keyed by an internal id instead, the response states that ticker was ignored. Two slugs work differently: implied_vol_by_ticker (below) and financial_ratios.
financial_ratios resolves ?ticker= to one security through the shared security resolver before reading, because symbols in this 1970 to 2025 archive were reused by different companies. It keys on the security's 8-character CUSIP and falls back to the symbol column, and the response carries an identity block (resolved_symbol, company_name, keyed_on, key_confidence) with identity_verified. A symbol that does not resolve returns no rows and error: "unresolved_ (for example unresolved_) rather than a guess; a resolved security with no rows returns warning: "no_.
Two neutral slugs are accepted alongside the archive names, and the listing may show the archive name instead:
cds_composites: the full 2005 to 2025 single-name CDS composite spread history. It reads the same table asmarkit_cdsand bills the same (credit, Pro+, 4 credits).implied_vol_by_ticker: symbol access to the daily implied-volatility aggregates, which are keyed by an internal id; the same data asoptionmetrics_by_ticker. It requires?ticker=, resolves the symbol to that id, and filters on it. Withouttickerthe response carrieserror: "ticker_and no rows.required"
| Category | Slugs | ?ticker= matches |
|---|---|---|
| Equity prices | crsp_monthly, crsp_daily, crsp_delisting, crsp_distributions | The symbol on crsp_monthly; the others are keyed by an internal security id |
| Mutual funds | mutual_fund_holdings, mutual_fund_returns | The held security's symbol on mutual_fund_holdings; mutual_fund_returns is keyed by fund id |
| Fundamentals | compustat_annual, compustat_quarterly, compustat_segments, segment_customers, supply_chain_links, financial_ratios | The symbol on annual and quarterly; the supplier's symbol on supply_chain_links; segments and customers are keyed by gvkey; financial_ratios resolves the symbol to a security (see above) |
| Analyst estimates | ibes_summary, ibes_detail, ibes_actuals, ibes_guidance, ibes_ptg, ibes_recommendations | The official symbol on summary, detail, price targets and recommendations; the estimate-feed ticker (usually, not always, the symbol) on actuals and guidance |
| Implied volatility | optionmetrics_daily, optionmetrics_, implied_vol_by_ticker (also served as optionmetrics_by_ticker) | Keyed by an internal id; use implied_vol_by_ticker for symbol access |
| Securities finance | markit_cds (also cds_composites), markit_short | The symbol on markit_cds; markit_short is keyed by gvkey / security id |
| Credit and loans | trace, markit_credit_indices, dealscan_facility, dealscan_pricing, ciq_ratings | The borrower symbol on dealscan_facility, where available; trace is keyed by bond id, markit_credit_indices by index, the others by internal ids |
| Transcripts, ratings and key developments | ciq_transcripts, ciq_keydev | Keyed by company id. ciq_transcripts holds metadata only; full text is served by /api/v3/transcripts/* |
| Governance and compensation | boardex_profile, boardex_composition, boardex_networks, execucomp | The symbol on boardex_profile and execucomp; composition and networks are keyed by board and director ids |
| Forensic audit | audit_fees, audit_restatements, auditor_changes | Keyed by company / CIK inside the row |
| ESG and crowd estimates | msci_esg, estimize_consensus, estimize_estimates | The issuer symbol on msci_esg; the symbol on both estimize datasets |
| Estimates, consensus feed | zacks_consensus, zacks_surprises | The symbol |
| Factors | fama_french_daily, fama_french_monthly | Market-level series, no ticker |
Known limits of these archives (read before a backtest). Values match the vendors'; the gaps are in coverage and in the knowledge dates, and they are being restated.
crsp_daily: from 2000 to 2024 it holds common stocks only (about 60% of the vendor's rows; ETFs, ADRs and other share classes are missing there), and rows before 2000 have nullcfacprandcfacshr. Its knowledge dates are not usable as point-in-time clocks.crsp_delisting,crsp_monthly(2026 rows): knowledge dates follow the vendor's release rule (month end plus 42 to 49 days), not the date Wealthnow first held the row.crsp_distributions: the legacy archive, last updated 2024-12-31; later distributions are not in it.ibes_actuals,ibes_recommendations: knowledge dates are the announcement date on recent rows and empty on older rows, so they can precede when the data was available. A few thousand recent vendor rows are missing and a few hundred values have since been revised.markit_credit_indices: see Credit; each composite is stamped at 00:00 UTC on its trade date, before that day's composite exists.
Feature store
The feature store serves the derived research panels the platform computes for its own models: price, return and liquidity features, monthly fundamentals, analyst-estimate dynamics, options and volatility-surface features, insider and institutional-ownership features, news-sentiment features, betas, and regime and macro features. Slugs are product names, not table names.
Call the catalogue first. GET /api/v3/features/datasets is the only place the slugs are published. Measured live on 2026-08-02 it listed 19 servable datasets totalling 17,062,613 rows, plus 5 withheld and 2 excluded. How many are current or archival changes with the data, so read coverage.datasets_, coverage.datasets_ and coverage.datasets_ in the response. Each entry carries:
dataset,title,summary,grain(daily, weekly, monthly or quarterly),market_wide,symbol_addressable,feature_columnsandpath. A panel that carries a per-row vintage also hasvintage_column, with itsnameandmeaning.coverage: measuredrows_total,symbols_total,observation_dates,history_firstandhistory_last, counted over observations only.rows_dated_after_todaycounts rows stamped with a date that has not happened yet. On a monthly panel,rows_counts rows dated on or before today that were written no later than the month end they are stamped with, such as a month in progress; it iswritten_ before_ month_ end nullon other grains. Neither kind of row is served.freshness:statusiscurrentonly when both clocks agree: the producer is inside twice its own declared cadence, and the data is on its own rhythm. Otherwise a genuine historical panel isarchival: still readable, and every read of it says so.advisorystates the result in one sentence.
How the data clock is read. stale_ is true when the newest observation (data_through) is more than three of the table's own typical gaps between observations old, so a panel rewritten nightly whose data still ends months ago is not current. A daily panel is counted in weekdays, so a weekend is not a missed observation: data_clock_counted_in is business_days, with data_lag_business_days and typical_ beside the calendar-day data_lag_days and typical_gap_days. Other grains are counted in calendar_days. A panel with a vintage clock is graded on it too: vintage_clock gives its column, through (the newest vintage), lag_days, typical_gap_days and stale, and a stale vintage makes the dataset stale.
not_current_reason says which clock is behind:
not_current_reason | status | Meaning |
|---|---|---|
no_scheduled_producer | archival | A fixed historical archive with no producer. |
producer_behind | archival | The producer has not written within twice its cadence. |
data_ | archival | The producer is on schedule, but the data it writes is behind its own rhythm. |
producer_ | withheld | The producer has stopped, and the dataset has no history to fall back on. |
data_ | withheld | The data is behind its own rhythm, and the dataset has no history to fall back on. |
The catalogue also lists withheld datasets (the producer has stopped and there is no history to fall back on; they have no route) and excluded ones (live but not sold here), each with the measurement behind the verdict. A servable dataset can also be measured into withheld: its catalogue entry then shows status: "withheld" with its not_current_reason, it is counted in datasets_withheld rather than datasets_servable, and reading it returns 404 with error: "dataset_, not billed, until it recovers.
Reading a panel. With ?ticker=, /api/v3/features/{dataset} returns that symbol's observations, newest first. Without it, it returns the latest cross-section: every symbol on the most recent observation date. since and until bound the window.
observation_dateis the date the row describes.since,untiland the latest cross-section all key on it.- A panel that carries a vintage serves it as its own column beside
observation_date, never in its place. Onequity_,fundamental_ features_ monthly ratio_vintageis the publication date of the valuation and profitability ratios on the row; a vintage is carried forward to later months until a newer one is published. Onbeta_features,returns_data_throughis the last month of the return history the beta was estimated on: a security whose returns stopped keeps its last estimate, so a row dated today can carry one from decades earlier. - A row is not served until it is an observation. A row dated after today is not one yet, and neither is a monthly row written on or before the month end it is stamped with, such as the month in progress on
equity_returns_monthly.coveragecounts both kinds. - The symbol is resolved to one security first. An ambiguous, historical (renamed or delisted) or unknown symbol is refused, never guessed, and
identity.identity_states whether the read was keyed on an authoritative identifier or on the resolved security's symbol.verified - When a symbol has been carried by more than one company and the panel is read by symbol, the series is bounded to the period the current company can have held it, when an earlier holder's end date is known, and
window.since_appliedissymbol_reuse_floor. When a reused symbol's series is not bounded this way, the rows are still returned,identity.identity_isverified false, andresultisrows_may_blend_issuers.identity.symbol_lists the earlier holders.reuse.prior_ holders_ of_ symbol - Market-wide datasets have one row per date, and a panel keyed by a non-symbol id cannot be read by symbol: passing
tickerto either returns422witherror_.code: "symbol_ not_ supported" - Every read carries the dataset's
coverage(withrows_returnedandtruncated) andfreshness, so an emptyrowsarray arrives beside the count of symbols that do have rows.resultis the machine-readable outcome:
result | Meaning |
|---|---|
rows | Rows returned. |
rows_may_blend_issuers | Rows returned, but the symbol was reused by more than one company and the series could not be bounded, so rows from an earlier issuer may be mixed in. Do not join the panel on it without checking identity.symbol_reuse. |
no_rows_for_symbol | The symbol resolved, but the dataset has no observations for it in the window. |
no_rows_in_window | A cross-section read found no observations in the window. |
symbol_ambiguous, symbol_historical, symbol_unknown | The symbol was refused: shared by several current securities, only matched a renamed or delisted security, or is not known. No rows. |
symbol_unavailable | The resolver could not run, so the symbol was not checked. No rows; retry later. |
symbol_not_keyable | The symbol resolved, but the security carries none of the identifiers this panel is keyed by. No rows. |
All of these arrive with HTTP 200.
- An unknown or withheld slug returns
404, including a servable dataset measured intowithheld(error: "dataset_), and an unreadable store returnswithheld" 503; neither is billed.
Servable slugs at the time of writing (the catalogue is authoritative):
| Area | Slugs |
|---|---|
| Price, return and liquidity | equity_, equity_returns_monthly |
| Fundamentals and accounting | equity_, forensic_ |
| Analyst estimates and actions | analyst_, analyst_action_signals |
| Options and volatility | implied_, options_, options_, vol_ |
| Ownership and flow | insider_, institutional_, legislative_ |
| Sentiment | news_sentiment_features |
| Risk | beta_features |
| Market-wide regime and macro | equity_regime_features, macro_regime_features, macro_features, futures_curve_features |
Examples
List every licensed dataset (4 credits)
curl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v2/datasets" | jq '.count, .datasets'Listing requires Starter+ and costs 4 credits. Cache the slug list to avoid paying for it on every query.
Read EPS surprises for one ticker (2 credits)
curl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v2/datasets/zacks_surprises?ticker=AAPL&limit=8" \ | jq '.note, .rows[0]'Without ticker, a read returns rows from across the whole table, capped at 5,000. For example, ?limit=5 without a ticker returned this first row:
{ "entry_date": "2026-06-29T00:00:00", "report_date": "2026-06-29T00:00:00", "ticker": "AIRT", "reference_period": "2026-03-01T00:00:00", "actual_eps": 28.75, "consensus_eps": null, "surprise_pct": null, "number_of_est": 0}Read implied volatility by symbol (4 credits)
curl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v2/datasets/implied_vol_by_ticker?ticker=AAPL&limit=250"Read director interlocks (3 credits, Pro and up)
curl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v2/datasets/boardex_networks?limit=5" | jq '.rows[0]'Browse the feature store, then read one panel (3 credits each)
# 1. The catalogue: slugs, coverage, freshnesscurl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v3/features/datasets" \ | jq '.coverage, [.datasets[] | {dataset, grain, status: .freshness.status}]' # 2. One symbol's daily price features since Junecurl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v3/features/equity_price_features_daily?ticker=NVDA&since=2026-06-01&limit=100"Notes for agents
- Always read
noteon a licensed dataset, andfreshnesson a feature panel. Coverage windows, lags, row counts and keying differ per dataset and change as archives are repointed. - Column names are archive-native. The licensed tables are raw research tables. Expect lowercase concatenated names (
indexfamily,overlapyearstart,number_of_est), not a normalized schema. - Not every dataset is ticker-keyed.
markit_credit_indicesis index-keyed;boardex_networksis board-id-keyed; several archives are keyed by internal security ids. Check thenotebefore joining onticker, and use Reference Data to translate identifiers. - Nulls are real. Research archives carry genuine gaps. A
nullmeans the source had no value, not that the API failed.