?>
ESC to close

ETF & Mutual Fund Screener API

Maintained by Piyush Arora

Last updated:

Filter, sort and paginate 32,727 ETF and mutual-fund share classes across 102 metrics — fees, assets, returns, flows, portfolio concentration, sector and country exposure, look-through valuation and bond risk — in a single call. Filters are a checked grammar rather than a SQL fragment, so every field name is validated against the metric contract before the query runs and every value is bound. The response tells you not only what matched but how many funds were excluded for having no value on file, which is the difference between a screen you can trust and one that quietly drops half the universe.

Use this endpoint to:

Screen ETFs By Expense Ratio Rank Funds By AUM Or Flows Build A Fund Discovery Table
GET https://data.businessquant.com/funds/screener?filters={filters}&fields={fields}&api_key={api_key}

Related endpoints: Take any two to six tickers from a screen to the ETF & Mutual Fund Comparison API for a side-by-side that says which metrics actually differ. For one fund in depth, see the Fund Overview, Fund Holdings, Portfolio Exposure and Fund Flows APIs. For equities rather than funds, use the Stock Screener API. New here? Start at the API Overview.

1. Request Parameters

Every parameter is optional except api_key. With no filter at all the endpoint returns the whole universe ranked by assets, which is a reasonable default landing page for a fund browser.

ParameterDescription
api_key
Required
Your unique API key for authentication.
filters
Optional
One or more field:op:value clauses joined by a semicolon. Values that take more than one are comma-separated. Full grammar in section 2.
Example: filters=vehicle:ETF;net_expense_ratio_pct:lte:0.10
fields
Optional
Comma-separated columns to return. Omit for the default eight. The five identity fields ride on every row regardless of what you ask for, so a row is always addressable.
Example: fields=category,net_expense_ratio_pct,net_assets_usd
preset
Optional
A named column set, so a table built around cost or around holdings is one word rather than six field names. Valid keys: overview, costs, performance, total_return, calendar, flows, portfolio, asset_mix, sectors, market_cap, valuation, bonds, risk, geography. An explicit fields wins over a preset.
Example: preset=costs
sort
Optional
field:dir pairs, comma-separated. A bare field name means descending. Defaults to net_assets_usd:desc. Nulls sort last in both directions — a fund that does not disclose its fee is not the cheapest fund.
Example: sort=net_expense_ratio_pct:asc
search
Optional
Free text across ticker, fund name, fund family, share class and top holding. Ticker matches from the start — typing VO wants VOO, not every fund with "vo" inside a word — while the other four match anywhere.
Example: search=vanguard
limit
Optional
Rows per page, 1–500. Default 50.
offset
Optional
Rows to skip. Default 0. Results carry a ticker tie-break, so page 2 is the rows page 1 did not show rather than an arbitrary re-slice of the ties.
include_metrics
Optional
true embeds the full metric contract in metadata — all 102 fields with datatype, unit, description, filter shape, enum values and p05/p50/p95 distribution. This is what you build a filter rail from. Fetch it once and cache it; it changes at most once per pipeline run.
Example cURL Request
curl -G "https://data.businessquant.com/funds/screener" \
  --data-urlencode "filters=vehicle:ETF;net_expense_ratio_pct:lte:0.10;net_assets_usd:gte:1e10" \
  --data-urlencode "fields=className,category,net_expense_ratio_pct,net_assets_usd,return_1y_pct,net_flow_12m_pct,holdings_count" \
  --data-urlencode "sort=net_assets_usd:desc" \
  --data-urlencode "limit=8" \
  --data-urlencode "api_key=YOUR_API_KEY"
Sample Response
{
    "metadata": {
        "endpoint": "/funds/screener",
        "grain": "share class ticker",
        "total_matched": 111,
        "universe_total": 32727,
        "returned": 8,
        "offset": 0,
        "limit": 8,
        "page": 1,
        "pages": 14,
        "fields": [
            "ticker",
            "fund_name",
            "className",
            "category",
            "net_expense_ratio_pct",
            "net_assets_usd",
            "return_1y_pct",
            "net_flow_12m_pct",
            "holdings_count"
        ],
        "sort": [
            {
                "field": "net_assets_usd",
                "dir": "desc"
            }
        ],
        "filters_applied": [
            {
                "field": "vehicle",
                "op": "eq",
                "value": "ETF"
            },
            {
                "field": "net_expense_ratio_pct",
                "op": "lte",
                "value": "0.10"
            },
            {
                "field": "net_assets_usd",
                "op": "gte",
                "value": "1e10"
            }
        ],
        "nulls_excluded": {
            "net_expense_ratio_pct": 5,
            "net_assets_usd": 2
        },
        "data_asof": {
            "portfolio": "2026-08-28",
            "aum": "2026-06-30",
            "flows": "2026-06-30",
            "price": "2026-08-28",
            "total_return": "2026-06-30",
            "prospectus": "2026-08-27"
        }
    },
    "data": [
        {
            "ticker": "VTI",
            "fund_name": "Vanguard Total Stock Market Index Fund",
            "className": "ETF Shares",
            "vehicle": "ETF",
            "series_id": "S000002848",
            "category": "Equity",
            "net_expense_ratio_pct": 0.03,
            "net_assets_usd": 1991691212321.29,
            "return_1y_pct": 18.51,
            "net_flow_12m_pct": -0.0609,
            "holdings_count": 3487
        },
        {
            "ticker": "VOO",
            "fund_name": "Vanguard 500 Index Fund",
            "className": "ETF Shares",
            "vehicle": "ETF",
            "series_id": "S000002839",
            "category": "Equity",
            "net_expense_ratio_pct": 0.03,
            "net_assets_usd": 1421263311402.89,
            "return_1y_pct": 18.56,
            "net_flow_12m_pct": -7.8225,
            "holdings_count": 506
        },
        {
            "ticker": "IVV",
            "fund_name": "iShares Core S&P 500 ETF",
            "className": "iShares Core S&P 500 ETF",
            "vehicle": "ETF",
            "series_id": "S000004310",
            "category": "Equity",
            "net_expense_ratio_pct": 0.03,
            "net_assets_usd": 888128937468.17,
            "return_1y_pct": 18.53,
            "net_flow_12m_pct": 14.0797,
            "holdings_count": 508
        },
        {
            "ticker": "VXUS",
            "fund_name": "Vanguard Total International Stock Index Fund",
            "className": "ETF Shares",
            "vehicle": "ETF",
            "series_id": "S000002932",
            "category": "Equity",
            "net_expense_ratio_pct": 0.05,
            "net_assets_usd": 629120124704.15,
            "return_1y_pct": 22.08,
            "net_flow_12m_pct": 3.121,
            "holdings_count": 8841
        },
        {
            "ticker": "BND",
            "fund_name": "Vanguard Total Bond Market Index Fund",
            "className": "ETF Shares",
            "vehicle": "ETF",
            "series_id": "S000002564",
            "category": "Fixed Income",
            "net_expense_ratio_pct": 0.03,
            "net_assets_usd": 386757079103.08,
            "return_1y_pct": -2.14,
            "net_flow_12m_pct": 8.0875,
            "holdings_count": 10042
        },
        {
            "ticker": "VUG",
            "fund_name": "Vanguard Growth Index Fund",
            "className": "ETF Shares",
            "vehicle": "ETF",
            "series_id": "S000002842",
            "category": "Equity",
            "net_expense_ratio_pct": 0.03,
            "net_assets_usd": 317937095754.84,
            "return_1y_pct": 14.5,
            "net_flow_12m_pct": 3.4738,
            "holdings_count": 150
        },
        {
            "ticker": "VEA",
            "fund_name": "Vanguard Developed Markets Index Fund",
            "className": "ETF Shares",
            "vehicle": "ETF",
            "series_id": "S000004386",
            "category": "Equity",
            "net_expense_ratio_pct": 0.03,
            "net_assets_usd": 282135245330.19,
            "return_1y_pct": 23.81,
            "net_flow_12m_pct": 8.31,
            "holdings_count": 3934
        },
        {
            "ticker": "VTV",
            "fund_name": "Vanguard Value Index Fund",
            "className": "ETF Shares",
            "vehicle": "ETF",
            "series_id": "S000002840",
            "category": "Equity",
            "net_expense_ratio_pct": 0.03,
            "net_assets_usd": 225685961942,
            "return_1y_pct": 23.01,
            "net_flow_12m_pct": 3.9932,
            "holdings_count": 311
        }
    ]
}

The grain is the share class, not the fund. Each row is one ticker. VTSAX and VTI are two rows of one portfolio, and Class A, C and I are three more — they share holdings and an objective but not an expense ratio or a minimum. That is why className is in the default column set: without it, a screen for the cheapest large-cap fund looks like five duplicates of the same name. series_id is what tells you two rows are the same fund.

2. Filter Grammar

Clauses are field:op:value, joined by a semicolon. Semicolon rather than comma because comma is already spoken for inside a value — category:in:Equity,Fixed Income has to stay one clause. Clauses are combined with AND.

Grammar by example
net_expense_ratio_pct:lte:0.10          fee at or under 10 basis points
net_assets_usd:between:1e9,1e12         AUM inside a range
category:in:Equity,Fixed Income         any one of these categories
fund_tags:has:Index Fund                list column contains this tag
fund_name:contains:Vanguard             substring match on text
tracking_error_pct:not_null             has a value on file at all
vehicle:ETF                             shorthand for vehicle:eq:ETF

Operators

OperatorApplies toMeaning
eq, neqanyEqual / not equal. neq is null-safe, so it returns rows where the field is null rather than silently dropping them.
lt, lte, gt, gtenumber, dateMagnitude comparisons. These are the operators that exclude funds for having no value on file — see nulls_excluded.
betweennumber, dateInclusive range. Takes exactly two comma-separated values: field:between:min,max.
inanyMatches any of the comma-separated values. On an enum column this is the natural filter.
containstextCase-insensitive substring. Rejected on non-text fields rather than silently coerced.
has, has_anylistList membership, and the only meaningful operators on the three list columns. has with several values means all of them; has_any means any.
is_null, not_nullanyPresence tests. Take no value: write tracking_error_pct:not_null.

Filters are parsed, not interpolated. Every field name is checked against the metric contract before the query is built, every identifier is quoted, and every value you send becomes a bound parameter. A field that does not exist, an operator that does not apply to its datatype, or a value that will not coerce all return a 400 naming the problem — never a 500, and never a wider result set than you asked for.

3. Response Fields

The usual metadata / data envelope. data carries only the projected columns, because 102 columns across 50 rows is a 400 kB response to draw an eight-column table.

metadata

FieldTypeDescription
total_matchedintegerFunds matching the filters, before pagination.
universe_totalintegerShare classes in the screener universe — the denominator your match count is a fraction of.
returned, offset, limit, page, pagesintegerPagination state for this response.
fieldsarrayThe columns a caller is actually looking at — the two pinned fields plus whatever was requested. Distinct from the columns present on each row, which always include the five identity fields.
sortarrayThe sort actually applied, echoed back including the default when none was sent.
filters_appliedarrayThe filters that produced a predicate. An empty clause is dropped here rather than silently ignored, so you can tell what the server actually ran.
nulls_excludedobjectPer field, how many funds a magnitude filter dropped for having no value on file rather than for failing the test. See the note below.
data_asofobjectSix as-of dates — portfolio, aum, flows, price, total_return, prospectus. They legitimately disagree, so each belongs beside its own column group rather than being flattened into one page-level date.

data — one row per share class

FieldTypeDescription
tickerstringThe share-class ticker. Unique, and the key to every other /funds endpoint.
fund_namestringRegistered fund name. Shared by every class of one fund.
classNamestringThe share class — "ETF Shares", "Admiral Shares", "Class I". The column that explains why two rows with the same name have different fees.
vehiclestringETF or MF.
series_idstringSEC Series ID. Two rows sharing one are two wrappers on the same portfolio.
topholding_ticker, topholding_kindstringAdded when you request topholding_name, and only when the resolved security's name matches the one on the row. Absent for bonds, GNMA pools, repos and cash, which is the normal case rather than a gap — BND's largest position is a Fannie Mae pool with no ticker at all.
…requested fieldsmixedWhatever fields or preset asked for. Call with include_metrics=true for the full catalogue with types and descriptions.

A null is never a zero. A fund that does not disclose an expense ratio is not a free fund, so nulls sort last in both directions and are returned as null rather than 0. And when a magnitude filter drops a fund for having nothing on file, the response says so: in the sample above, nulls_excluded reports 5 funds excluded on net_expense_ratio_pct and 2 on net_assets_usd. A screen that narrows to 200 funds should be able to tell you how many more were excluded for not reporting, rather than presenting the survivors as the whole population.

4. Response Viewer

A real response for vehicle:ETF with a fee ceiling of 10 bp and an AUM floor of $10 B, ranked by assets — 111 share classes matched out of 32,727.

Live Endpoint https://data.businessquant.com/funds/screener

API Response — Fund Screener

Cheap, large ETFs by assets under management

Ticker Fund Share Class Net Expense AUM 1Y Return Flow 12M Holdings
VTI Vanguard Total Stock Market Index Fund ETF Shares 0.03% $1.99T 18.51% -0.06% 3,487
VOO Vanguard 500 Index Fund ETF Shares 0.03% $1.42T 18.56% -7.82% 506
IVV iShares Core S&P 500 ETF iShares Core S&P 500 ETF 0.03% $888.13B 18.53% 14.08% 508
VXUS Vanguard Total International Stock Index Fund ETF Shares 0.05% $629.12B 22.08% 3.12% 8,841
BND Vanguard Total Bond Market Index Fund ETF Shares 0.03% $386.76B -2.14% 8.09% 10,042
VUG Vanguard Growth Index Fund ETF Shares 0.03% $317.94B 14.50% 3.47% 150
VEA Vanguard Developed Markets Index Fund ETF Shares 0.03% $282.14B 23.81% 8.31% 3,934
VTV Vanguard Value Index Fund ETF Shares 0.03% $225.69B 23.01% 3.99% 311

5. The Metric Contract

Call with include_metrics=true and metadata gains the full catalogue — every filterable field with the information needed to render a control for it without hard-coding anything.

Contract fieldTypeWhat it is for
field, label, shortstringThe name you filter on, and the human labels to print.
datatype, unitstringdatatype is the storage type; unit is what the number isusd, percent, count, years, id. The estate types both a dollar figure and a P/E as int, which is why the two are separate.
filter_shapestringWhat kind of control the field wants — a range slider, an enum picker, a text box.
filterable, sortable, is_listbooleanWhether to offer the field as a filter at all. A column under the discriminating threshold is not a filter, it is a slider with nothing to travel over.
p05, p50, p95, min, maxfloatDistribution, so a range slider can open on a sensible span instead of on the outliers.
enum_valuesarrayThe values an enum column actually takes, for a picker.
fill_pct, discriminating_pctfloatHow much of the universe has this field, and how much of it the field actually separates.
descriptionstringWhat the metric means, for a tooltip.
statement, statement_orderstring, intThe group a field belongs to — Fees, Returns, Flows, Portfolio, Sectors, Identity — and its position, for arranging a rail.
Sample Contract Response (abridged)
{
    "metadata": {
        "endpoint": "/funds/screener",
        "grain": "share class ticker",
        "universe_total": 32727,
        "ops": [
            "between",
            "contains",
            "eq",
            "gt",
            "gte",
            "has",
            "has_any",
            "in",
            "is_null",
            "lt",
            "lte",
            "neq",
            "not_null"
        ],
        "identity_fields": [
            "ticker",
            "fund_name",
            "className",
            "vehicle",
            "series_id"
        ],
        "pinned_fields": [
            "ticker",
            "fund_name"
        ],
        "list_fields": [
            "data_available",
            "fund_tags",
            "structure_tags"
        ],
        "min_discriminating_pct": 5,
        "statements": [
            "Identity",
            "Profile",
            "Fees",
            "Size",
            "Returns",
            "Market Cap",
            "Valuation",
            "Asset Mix",
            "Sector",
            "Bond Character",
            "Geography",
            "Portfolio",
            "Concentration",
            "Flows",
            "Cost of Ownership",
            "Risk"
        ],
        "defaults": {
            "fields": [
                "ticker",
                "fund_name",
                "className",
                "category",
                "net_expense_ratio_pct",
                "net_assets_usd",
                "return_1y_pct",
                "net_flow_12m_pct",
                "holdings_count"
            ],
            "rail": [
                "category",
                "vehicle",
                "net_expense_ratio_pct",
                "net_assets_usd",
                "return_1y_pct",
                "fund_tags",
                "geography_focus",
                "top_sector"
            ],
            "sort": [
                {
                    "field": "net_assets_usd",
                    "dir": "desc"
                }
            ],
            "limit": 50
        },
        "column_presets": [
            {
                "key": "overview",
                "label": "Overview",
                "fields": [
                    "className",
                    "category",
                    "net_expense_ratio_pct",
                    "net_assets_usd",
                    "return_1y_pct",
                    "net_flow_12m_pct",
                    "holdings_count"
                ]
            },
            {
                "key": "costs",
                "label": "Costs",
                "fields": [
                    "net_expense_ratio_pct",
                    "gross_expense_ratio_pct",
                    "acquired_fund_fees_pct",
                    "portfolio_turnover_pct",
                    "min_initial_investment_usd",
                    "fund_age_years"
                ]
            },
            {
                "key": "performance",
                "label": "Performance",
                "fields": [
                    "return_3m_pct",
                    "return_ytd_pct",
                    "return_1y_pct",
                    "return_3y_ann_pct",
                    "return_5y_ann_pct",
                    "pro_return_10y_pct"
                ]
            },
            {
                "key": "total_return",
                "label": "Total Return",
                "fields": [
                    "total_return_1m_pct",
                    "total_return_3m_pct",
                    "total_return_6m_pct",
                    "total_return_12m_pct",
                    "pro_return_incep_pct",
                    "total_return_asof"
                ]
            },
            {
                "key": "calendar",
                "label": "By Year",
                "fields": [
                    "cal_return_2021_pct",
                    "cal_return_2022_pct",
                    "cal_return_2023_pct",
                    "cal_return_2024_pct",
                    "cal_return_2025_pct",
                    "pro_worst_quarter_pct"
                ]
            },
            {
                "key": "flows",
                "label": "Flows",
                "fields": [
                    "net_flow_1m_pct",
                    "net_flow_3m_pct",
                    "net_flow_12m_pct",
                    "net_flow_12m_usd",
                    "flow_positive_months_pct",
                    "net_assets_usd"
                ]
            },
            {
                "key": "portfolio",
                "label": "Portfolio",
                "fields": [
                    "holdings_count",
                    "effective_holdings",
                    "top10_weight_pct",
                    "top1_weight_pct",
                    "topholding_name",
                    "short_pct"
                ]
            },
            {
                "key": "asset_mix",
                "label": "Asset Mix",
                "fields": [
                    "equity_pct",
                    "fixed_income_pct",
                    "fund_cash_pct",
                    "derivative_pct",
                    "other_asset_pct",
                    "liabilities_pct_net_assets"
                ]
            },
            {
                "key": "sectors",
                "label": "Sectors",
                "fields": [
                    "top_sector",
                    "top_sector_pct",
                    "sector_technology_pct",
                    "sector_financial_services_pct",
                    "sector_healthcare_pct",
                    "sector_industrials_pct"
                ]
            },
            {
                "key": "market_cap",
                "label": "Market Cap",
                "fields": [
                    "size_style",
                    "wavg_market_cap_usd",
                    "mega_cap_pct",
                    "large_cap_pct",
                    "mid_cap_pct",
                    "small_cap_pct"
                ]
            },
            {
                "key": "valuation",
                "label": "Valuation",
                "fields": [
                    "lookthrough_pe",
                    "lookthrough_ps",
                    "lookthrough_ev_ebitda",
                    "lookthrough_dividend_yield_pct",
                    "wavg_market_cap_usd",
                    "market_cap_coverage_pct"
                ]
            },
            {
                "key": "bonds",
                "label": "Bonds",
                "fields": [
                    "us_treasury_pct",
                    "corporate_pct",
                    "municipal_pct",
                    "us_agency_gse_pct",
                    "non_us_sovereign_pct",
                    "dv01_10y_bp_per_10k"
                ]
            },
            {
                "key": "risk",
                "label": "Risk",
                "fields": [
                    "tracking_error_pct",
                    "tracking_difference_pct",
                    "pro_worst_quarter_pct",
                    "liabilities_pct_net_assets",
                    "short_pct",
                    "portfolio_turnover_pct"
                ]
            },
            {
                "key": "geography",
                "label": "Geography",
                "fields": [
                    "geography_focus",
                    "top_country",
                    "country_us_pct",
                    "category",
                    "net_assets_usd",
                    "net_expense_ratio_pct"
                ]
            }
        ],
        "metrics": [
            {
                "field": "category",
                "label": "Category",
                "short": "category",
                "datatype": "text",
                "unit": "text",
                "statement": "Profile",
                "statement_order": 1,
                "filter_shape": "enum",
                "description": "Coarse bucket derived from what the fund HOLDS, not from its name, measured on classified exposure only. Other when under half the portfolio is classified.",
                "fill_pct": 92.87,
                "discriminating_pct": 41.5,
                "n_distinct": 8,
                "p05": null,
                "p50": null,
                "p95": null,
                "min": null,
                "max": null,
                "enum_values": [
                    "Equity",
                    "Fixed Income",
                    "Target Date",
                    "Other",
                    "Money Market",
                    "Allocation",
                    "Derivative / Alternative",
                    "Fund of Funds / Cash"
                ],
                "filterable": true,
                "sortable": true,
                "is_list": false
            },
            {
                "field": "fund_tags",
                "label": "Tags",
                "short": "Tags",
                "datatype": "list",
                "unit": "list",
                "statement": "Profile",
                "statement_order": 1,
                "filter_shape": "list",
                "description": "Fund types as filed in N-CEN: Index Fund, Fund of Funds, Target Date, Money Market, Inverse, Underlying Fund, Interval Fund, Exchange-Traded Managed. A fund can carry several.",
                "fill_pct": 100,
                "discriminating_pct": 29.17,
                "n_distinct": 8,
                "p05": null,
                "p50": null,
                "p95": null,
                "min": null,
                "max": null,
                "enum_values": [
                    "Fund of Funds",
                    "Index Fund",
                    "Target Date",
                    "Underlying Fund",
                    "Money Market",
                    "Inverse",
                    "Exchange-Traded Managed",
                    "Interval Fund"
                ],
                "filterable": true,
                "sortable": true,
                "is_list": true
            },
            {
                "field": "vehicle",
                "label": "Vehicle",
                "short": "vehicle",
                "datatype": "text",
                "unit": "text",
                "statement": "Profile",
                "statement_order": 1,
                "filter_shape": "enum",
                "description": "ETF, Mutual Fund, UIT (unit investment trust, e.g. SPY) or Closed-End.",
                "fill_pct": 86.25,
                "discriminating_pct": 14.05,
                "n_distinct": 4,
                "p05": null,
                "p50": null,
                "p95": null,
                "min": null,
                "max": null,
                "enum_values": [
                    "Mutual Fund",
                    "ETF",
                    "Closed-End",
                    "UIT"
                ],
                "filterable": true,
                "sortable": true,
                "is_list": false
            },
            {
                "field": "net_expense_ratio_pct",
                "label": "Net Expense",
                "short": "Net Expense",
                "datatype": "%",
                "unit": "percent",
                "statement": "Fees",
                "statement_order": 2,
                "filter_shape": "range",
                "description": "What the investor actually pays after fee waivers. THE expense ratio. 0.03 means three basis points, not 3%.",
                "fill_pct": 87.23,
                "discriminating_pct": 86.37,
                "n_distinct": null,
                "p05": "0.15",
                "p50": "0.83",
                "p95": "1.94",
                "min": "0.0",
                "max": "12.44",
                "enum_values": null,
                "filterable": true,
                "sortable": true,
                "is_list": false
            },
            {
                "field": "net_assets_usd",
                "label": "AUM",
                "short": "AUM",
                "datatype": "int",
                "unit": "usd",
                "statement": "Size",
                "statement_order": 3,
                "filter_shape": "range",
                "description": "Fund-level assets under management from the latest N-PORT. Fund grain, so every share class of a fund shows the same figure.",
                "fill_pct": 89.41,
                "discriminating_pct": 89.4,
                "n_distinct": null,
                "p05": "7237432.68",
                "p50": "497156998.61",
                "p95": "21107380477.22639",
                "min": "0.0",
                "max": "1991691212321.29",
                "enum_values": null,
                "filterable": true,
                "sortable": true,
                "is_list": false
            },
            {
                "field": "return_1y_pct",
                "label": "1Y",
                "short": "1Y",
                "datatype": "%",
                "unit": "percent",
                "statement": "Returns",
                "statement_order": 4,
                "filter_shape": "range",
                "description": "Trailing one-year PRICE return, measured back from Price As Of (the latest trading day). The primary performance filter — current, daily, ~77% of the universe. NOT comparable with Total Return 12M, whose window ends at the last N-PORT filing up to five months earlier.",
                "fill_pct": 76.59,
                "discriminating_pct": 76.24,
                "n_distinct": null,
                "p05": "-10.04",
                "p50": "5.25",
                "p95": "30.65",
                "min": "-91.73999786376953",
                "max": "2442.5",
                "enum_values": null,
                "filterable": true,
                "sortable": true,
                "is_list": false
            },
            {
                "field": "top_sector",
                "label": "Top Sector",
                "short": "top_sector",
                "datatype": "text",
                "unit": "text",
                "statement": "Sector",
                "statement_order": 8,
                "filter_shape": "enum",
                "description": "Largest classified sector, excluding the unclassified bucket.",
                "fill_pct": 72.79,
                "discriminating_pct": 46.71,
                "n_distinct": 11,
                "p05": null,
                "p50": null,
                "p95": null,
                "min": null,
                "max": null,
                "enum_values": [
                    "Technology",
                    "Financial Services",
                    "Industrials",
                    "Communication Services",
                    "Basic Materials",
                    "Healthcare",
                    "Energy",
                    "Real Estate",
                    "Consumer Discretionary",
                    "Utilities",
                    "Consumer Staples"
                ],
                "filterable": true,
                "sortable": true,
                "is_list": false
            },
            {
                "field": "holdings_count",
                "label": "Holdings",
                "short": "holdings_count",
                "datatype": "int",
                "unit": "count",
                "statement": "Portfolio",
                "statement_order": 11,
                "filter_shape": "range",
                "description": "Number of positions in the latest snapshot. Median 87, maximum 18,136.",
                "fill_pct": 90.2,
                "discriminating_pct": 90.2,
                "n_distinct": null,
                "p05": "6.0",
                "p50": "87.0",
                "p95": "1515.0",
                "min": "1",
                "max": "18150",
                "enum_values": null,
                "filterable": true,
                "sortable": true,
                "is_list": false
            }
        ],
        "_note": "Abridged for the docs: `metrics` carries all 102 entries in a live response."
    },
    "data": []
}

6. More Request Examples

Screens that answer a real question rather than demonstrating a parameter.

The cheapest index funds in a category
GET /funds/screener
  ?filters=category:in:Equity;fund_tags:has:Index Fund;net_assets_usd:gte:1e9
  &preset=costs
  &sort=net_expense_ratio_pct:asc
  &api_key=YOUR_API_KEY
Where money went this year
GET /funds/screener
  ?filters=net_assets_usd:gte:5e8
  &preset=flows
  &sort=net_flow_12m_usd:desc
  &limit=25
  &api_key=YOUR_API_KEY
Concentrated equity funds — few holdings, heavy top ten
GET /funds/screener
  ?filters=category:in:Equity;holdings_count:lte:40;top10_weight_pct:gte:50
  &preset=portfolio
  &sort=top10_weight_pct:desc
  &api_key=YOUR_API_KEY
Index trackers by how closely they actually track
GET /funds/screener
  ?filters=tracking_error_pct:not_null;vehicle:ETF
  &preset=risk
  &sort=tracking_error_pct:asc
  &api_key=YOUR_API_KEY
Python — page through a whole screen
import requests

BASE = "https://data.businessquant.com/funds/screener"
params = {
    "filters": "vehicle:ETF;net_expense_ratio_pct:lte:0.10",
    "fields":  "className,net_expense_ratio_pct,net_assets_usd,return_1y_pct",
    "sort":    "net_assets_usd:desc",
    "limit":   500,
    "api_key": "YOUR_API_KEY",
}

rows, offset = [], 0
while True:
    params["offset"] = offset
    body = requests.get(BASE, params=params, timeout=60).json()
    rows += body["data"]
    md = body["metadata"]
    # nulls_excluded is the disclosure that makes the count honest --
    # log it rather than presenting the survivors as the whole universe.
    if offset == 0 and md["nulls_excluded"]:
        print("excluded for no value on file:", md["nulls_excluded"])
    offset += md["limit"]
    if offset >= md["total_matched"]:
        break

print(f"{len(rows)} of {md['universe_total']} share classes")

7. Use Cases

What this endpoint is actually built for.

Fund Discovery Tables

Back a sortable, filterable fund browser with one call per page. preset gives a reader a cost view or a portfolio view in one tap without a 102-row column chooser.

Lowest-Cost Share Class

Filter to a series_id and sort by net_expense_ratio_pct ascending to find the cheapest wrapper on a portfolio a client already owns. The share-class grain is what makes this answerable at all.

Flow-Led Research

Rank by net_flow_12m_usd or net_flow_1m_pct to see where money is actually going. Pair with the Fund Flows API for the month-by-month series behind a name that stands out.

Filter Rail Generation

include_metrics=true returns datatype, filter shape, enum values and p05/p50/p95 for every field, so a filter UI can be generated rather than hand-maintained against a moving schema.

Look-Through Screening

Screen on what a fund holds, not just what it charges — lookthrough_pe, wavg_market_cap_usd, sector_technology_pct. These are derived from filed N-PORT portfolios, not from a fund's own marketing category.

Comparison Shortlists

Take the top rows of a screen straight to the ETF & Mutual Fund Comparison API, which will tell you which of the 99 comparable metrics actually differ between them.

Frequently Asked Questions

The questions developers ask most often about this endpoint.

Why do I get several rows with the same fund name?

Because the grain is the share class, not the fund. VTSAX, VTI, VITSX and VSMPX are four wrappers on one Vanguard portfolio — they hold identical securities and charge different fees. They are separate rows because a buyer picks between them on price and minimum. Read className to see which class each row is, and series_id to see that they are the same fund. If you want one row per fund, group your results by series_id and keep the class you care about.

What is nulls_excluded and why does it matter?

When you filter on a magnitude — lt, lte, gt, gte, between — a fund with no value on file cannot satisfy the test and drops out. That is correct, but it is not the same as failing the test, and a screen that does not distinguish the two overstates how complete it is. nulls_excluded reports the count per field so you can say "412 funds matched, and 1,204 more were excluded because they do not disclose an expense ratio". Equality and enum filters do not report this: nobody expects "category is Equity" to also return the funds with no category.

How do I get the list of valid field names?

Call the endpoint with include_metrics=true. metadata.metrics then carries all 102 entries with datatype, unit, description, filter shape, enum values and distribution percentiles. Fetch it once and cache it — it changes at most once per pipeline run. A field name that is not in that list returns a 400 naming it rather than being silently ignored.

Why is the filter a grammar rather than a SQL fragment?

Because a SQL fragment sent by a caller has to be either trusted or parsed, and trusting it is not an option on a public endpoint. The grammar here is small enough to validate completely: field names are checked against the contract, operators against the field's datatype, and every value is bound as a parameter rather than concatenated. You lose the ability to write arbitrary expressions and gain a filter that fails with a useful 400 instead of a 500 — or worse, a query that runs when it should not have.

Why do the as-of dates disagree with each other?

Because the things they date are filed on different schedules. A portfolio comes from a quarterly N-PORT, a price is daily, net assets are month-end, and a prospectus figure can be up to a year old. Flattening them into one page-level "as of" would make five of the six wrong. data_asof returns all six so each can be shown beside the column group it actually describes.