Stock Screener API Starter Examples - Build DFin Requests

How to Build Screener API Requests - Starter Examples

Illustration of a laptop and notebook showing screener filters flowing into results

Most API docs hand you a request shape and leave you to figure out the rest. This article works the other way: it starts with five real investing screens, the same ones built into the DFin stock screener, and shows how each one becomes an API call you can run, modify, and actually use.

Each example includes the request body, an explanation of why the filters are set the way they are, and a suggestion for where to take it next. By the end, you will have a working starting point for five different investing workflows, plus enough understanding of the request structure to adapt them to your own needs.

Setup

All five examples use the advanced screener endpoint, which supports the full screener contract. The request structure is simple: filters defines what you are looking for, page controls how many rows come back, fields picks your columns, and result_format shapes the response.

Item Value
Endpoint POST /api/v1/screener/advanced/
API key variable DFIN_API_KEY
Default output result_format: rows

Set your API key as an environment variable, then send the JSON body from whichever example fits your workflow.

For endpoint-level details, parameter rules, and the generated Postman collection, see the API v1 screener reference.

1.0 Large profitable companies

The goal

Build a quality-first starting list of established U.S. businesses.

This starts with large-cap U.S. companies above $10B, then filters for a strong 3-year average return on invested capital. It is not a narrow niche screen. It is a wide net with a quality filter draped over it.

Why ROIC?

ROIC is one of the more honest measures of how well a business uses the money entrusted to it. A company that consistently earns high returns on reinvested capital tends to compound value over time, which is usually the whole point.

Where to take it next

This is a natural foundation for a watchlist. Once you have your results, add a sector filter to compare within industries, pull in valuation fields like P/E or EV/EBITDA, or sort by market cap descending so the biggest names float to the top.

Filters

Filter API field Payload setting What it does
Country country operator: "include", values: ["United States"] Keeps only companies whose country value is United States.
Market Cap (USD) marketCap_usd min: 10,000,000,000 Filters companies by U.S. dollar market capitalization. The request sets a minimum market cap, so companies below $10B are excluded.
ROIC returnOnInvestedCapital 3-year average, min: 0.15 Requires the company's 3-year average ROIC to be at least 15%.

Request body

Use this body with /api/v1/screener/advanced/. The example includes the starter-screen filters, a manageable first page of results, and a small set of useful columns.

{
  "filters": {
    "country": {
      "operator": "include",
      "values": [
        "United States"
      ]
    },
    "marketCap_usd": {
      "min": 10000000000
    },
    "returnOnInvestedCapital": {
      "missing_policy": "strict",
      "rules": [
        {
          "kind": "range",
          "operand": {
            "kind": "average",
            "anchor": {
              "scope": "fy",
              "offset": 0
            },
            "years": 3
          },
          "min": 0.15
        }
      ]
    }
  },
  "sort": {},
  "page": {
    "limit": 100,
    "offset": 0
  },
  "fields": [
    "full_ticker",
    "companyName",
    "marketCap_usd"
  ],
  "result_format": "rows"
}

Code

POST /api/v1/screener/advanced/
curl -X POST "https://www.dfin.pro/api/v1/screener/advanced/" \
  -H "Authorization: Bearer ${DFIN_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"filters":{"country":{"operator":"include","values":["United States"]},"marketCap_usd":{"min":10000000000},"returnOnInvestedCapital":{"missing_policy":"strict","rules":[{"kind":"range","operand":{"kind":"average","anchor":{"scope":"fy","offset":0},"years":3},"min":0.15}]}},"sort":{},"page":{"limit":100,"offset":0},"fields":["full_ticker","companyName","marketCap_usd"],"result_format":"rows"}'

2.0 Improving margins

The goal

Find companies where the economics are quietly getting better.

This screen does not care whether margins are high in absolute terms. It cares whether they are improving. Specifically, it compares the recent 2-year average EBITDA margin against the 5-year average and keeps only the companies trending in the right direction.

Why this matters

Improving margins can mean a lot of things: pricing power gaining traction, fixed costs spreading over more revenue, or a product mix shifting toward higher-value work. Whatever the cause, it often shows up in margins before it shows up anywhere else. It is a useful early signal.

Where to take it next

Pair this with a size or sector filter to get more meaningful comparisons. A 2-point margin improvement means something different in software than it does in retail.

Filters

Filter API field Payload setting What it does
EBITDA Margin ebitdaMargin 2-year average > 5-year average Compares recent EBITDA margin with the longer-term average and keeps companies where the recent period is better.

Request body

Use this body with /api/v1/screener/advanced/. The example includes the starter-screen filters, a manageable first page of results, and a small set of useful columns.

{
  "filters": {
    "ebitdaMargin": {
      "missing_policy": "strict",
      "rules": [
        {
          "kind": "compare",
          "left": {
            "kind": "average",
            "anchor": {
              "scope": "fy",
              "offset": 0
            },
            "years": 2
          },
          "operator": "gt",
          "right": {
            "kind": "average",
            "anchor": {
              "scope": "fy",
              "offset": 0
            },
            "years": 5
          }
        }
      ]
    }
  },
  "sort": {},
  "page": {
    "limit": 100,
    "offset": 0
  },
  "fields": [
    "full_ticker",
    "companyName",
    "marketCap_usd"
  ],
  "result_format": "rows"
}

Code

POST /api/v1/screener/advanced/
curl -X POST "https://www.dfin.pro/api/v1/screener/advanced/" \
  -H "Authorization: Bearer ${DFIN_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"filters":{"ebitdaMargin":{"missing_policy":"strict","rules":[{"kind":"compare","left":{"kind":"average","anchor":{"scope":"fy","offset":0},"years":2},"operator":"gt","right":{"kind":"average","anchor":{"scope":"fy","offset":0},"years":5}}]}},"sort":{},"page":{"limit":100,"offset":0},"fields":["full_ticker","companyName","marketCap_usd"],"result_format":"rows"}'

3.0 Lower leverage

The goal

Screen out companies where the balance sheet is doing the heavy lifting.

Two filters: net debt below 2.5x EBITDA, and total debt below 50% of assets. Not zero debt. Just a level that leaves room for things to go wrong without the equity story unraveling.

Why it matters

Leverage is quiet until it is not. Companies that carry too much debt can look fine during good years and then face real problems when demand softens, rates rise, or a refinancing window closes at the wrong time. This screen is a simple way to avoid that exposure before you start thinking about growth or valuation.

Where to take it next

Use this as a filter before you evaluate anything else. If a company passes here, the rest of your analysis can focus on upside, not on whether the balance sheet is a hidden risk.

Filters

Filter API field Payload setting What it does
Net Debt/EBITDA netDebtToEBITDA TTM, max: 2.5 Filters companies by trailing-twelve-month net debt relative to EBITDA. The request excludes companies above 2.5x.
Debt/Assets debtToAssetsRatio TTM, max: 0.5 Filters companies by trailing-twelve-month debt as a share of assets. The request excludes companies above 50%.

Request body

Use this body with /api/v1/screener/advanced/. The example includes the starter-screen filters, a manageable first page of results, and a small set of useful columns.

{
  "filters": {
    "netDebtToEBITDA": {
      "missing_policy": "strict",
      "rules": [
        {
          "kind": "range",
          "operand": {
            "kind": "single",
            "scope": "ttm"
          },
          "max": 2.5
        }
      ]
    },
    "debtToAssetsRatio": {
      "missing_policy": "strict",
      "rules": [
        {
          "kind": "range",
          "operand": {
            "kind": "single",
            "scope": "ttm"
          },
          "max": 0.5
        }
      ]
    }
  },
  "sort": {},
  "page": {
    "limit": 100,
    "offset": 0
  },
  "fields": [
    "full_ticker",
    "companyName",
    "marketCap_usd"
  ],
  "result_format": "rows"
}

Code

POST /api/v1/screener/advanced/
curl -X POST "https://www.dfin.pro/api/v1/screener/advanced/" \
  -H "Authorization: Bearer ${DFIN_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"filters":{"netDebtToEBITDA":{"missing_policy":"strict","rules":[{"kind":"range","operand":{"kind":"single","scope":"ttm"},"max":2.5}]},"debtToAssetsRatio":{"missing_policy":"strict","rules":[{"kind":"range","operand":{"kind":"single","scope":"ttm"},"max":0.5}]}},"sort":{},"page":{"limit":100,"offset":0},"fields":["full_ticker","companyName","marketCap_usd"],"result_format":"rows"}'

4.0 Growing businesses

The goal

A simple first pass for companies with real top-line momentum.

This screen asks for at least 10% revenue CAGR over five years. That is it. The five-year window matters because it filters out companies that had one great year and makes you focus on businesses that have been consistently expanding.

Why revenue?

It is usually the clearest signal that something real is happening: demand is growing, market share is being won, or a business is genuinely scaling. It does not tell you whether the growth is profitable or sustainable, which is exactly why this screen works best as a starting filter, not a final one.

Where to take it next

Layer in profitability or balance-sheet filters to separate healthy growers from companies that are growing by spending heavily or taking on debt. Growth is great context. It is not a thesis on its own.

Filters

Filter API field Payload setting What it does
Income Statement Growth incomeStatementGrowth revenue, 5-year CAGR, min: 0.1 Filters for companies whose reported revenue has compounded at 10% or better over five years.

Request body

Use this body with /api/v1/screener/advanced/. The example includes the starter-screen filters, a manageable first page of results, and a small set of useful columns.

{
  "filters": {
    "incomeStatementGrowth": {
      "rules": [
        {
          "metric": "revenue",
          "horizon_years": 5,
          "value_type": "cagr",
          "currency_basis": "reported",
          "min": 0.1
        }
      ]
    }
  },
  "sort": {},
  "page": {
    "limit": 100,
    "offset": 0
  },
  "fields": [
    "full_ticker",
    "companyName",
    "marketCap_usd"
  ],
  "result_format": "rows"
}

Code

POST /api/v1/screener/advanced/
curl -X POST "https://www.dfin.pro/api/v1/screener/advanced/" \
  -H "Authorization: Bearer ${DFIN_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"filters":{"incomeStatementGrowth":{"rules":[{"metric":"revenue","horizon_years":5,"value_type":"cagr","currency_basis":"reported","min":0.1}]}},"sort":{},"page":{"limit":100,"offset":0},"fields":["full_ticker","companyName","marketCap_usd"],"result_format":"rows"}'

5.0 Shareholder-friendly companies

The goal

Find companies that are doing the right things for their owners, not just the flashy things.

Shareholder-friendly gets thrown around loosely, but this screen tries to pin it down. A company passes if it is profitable, has kept leverage flat or improved it, has not diluted shareholders over the past five years, and has grown book value per share. It needs all four.

Why all four?

Buybacks without profits are financial engineering. Low debt means nothing if earnings are shrinking. This screen rewards the combination: businesses that are actually generating value and being careful about how they deploy and distribute it.

Where to take it next

From here, add valuation fields to look for ones trading at reasonable prices, or dividend data if income matters to you. This list tends to skew toward mature, well-run businesses, which is exactly the kind of universe you want for longer-term research.

Filters

Filter API field Payload setting What it does
Market Cap (USD) marketCap_usd min: 1,000,000,000 Filters companies by U.S. dollar market capitalization. The request sets a $1B minimum to avoid very small companies.
Net Debt/EBITDA netDebtToEBITDA 2-year average <= 5-year average Keeps companies where recent leverage is no worse than the longer-term average.
Net Income netIncome min: 1 Requires positive net income, so the screen is not rewarding capital returns from unprofitable companies.
Share Count Growth shareCountGrowth weightedAverageShsOutDil, 5-year multiple, max: 1 Keeps companies whose diluted share count has not increased over the last five years.
Balance Sheet Growth balanceSheetGrowth bookValuePerShare, 5-year CAGR, min: 0.001 Requires book value per share to have grown over the last five years.

Request body

Use this body with /api/v1/screener/advanced/. The example includes the starter-screen filters, a manageable first page of results, and a small set of useful columns.

{
  "filters": {
    "marketCap_usd": {
      "min": 1000000000
    },
    "netDebtToEBITDA": {
      "missing_policy": "strict",
      "rules": [
        {
          "kind": "compare",
          "left": {
            "kind": "average",
            "anchor": {
              "scope": "fy",
              "offset": 0
            },
            "years": 2
          },
          "operator": "lte",
          "right": {
            "kind": "average",
            "anchor": {
              "scope": "fy",
              "offset": 0
            },
            "years": 5
          }
        }
      ]
    },
    "netIncome": {
      "min": 1
    },
    "shareCountGrowth": {
      "rules": [
        {
          "metric": "weightedAverageShsOutDil",
          "horizon_years": 5,
          "value_type": "multiple",
          "currency_basis": "reported",
          "max": 1.0
        }
      ]
    },
    "balanceSheetGrowth": {
      "rules": [
        {
          "metric": "bookValuePerShare",
          "horizon_years": 5,
          "value_type": "cagr",
          "currency_basis": "reported",
          "min": 0.001
        }
      ]
    }
  },
  "sort": {},
  "page": {
    "limit": 100,
    "offset": 0
  },
  "fields": [
    "full_ticker",
    "companyName",
    "marketCap_usd"
  ],
  "result_format": "rows"
}

Code

POST /api/v1/screener/advanced/
curl -X POST "https://www.dfin.pro/api/v1/screener/advanced/" \
  -H "Authorization: Bearer ${DFIN_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"filters":{"marketCap_usd":{"min":1000000000},"netDebtToEBITDA":{"missing_policy":"strict","rules":[{"kind":"compare","left":{"kind":"average","anchor":{"scope":"fy","offset":0},"years":2},"operator":"lte","right":{"kind":"average","anchor":{"scope":"fy","offset":0},"years":5}}]},"netIncome":{"min":1},"shareCountGrowth":{"rules":[{"metric":"weightedAverageShsOutDil","horizon_years":5,"value_type":"multiple","currency_basis":"reported","max":1.0}]},"balanceSheetGrowth":{"rules":[{"metric":"bookValuePerShare","horizon_years":5,"value_type":"cagr","currency_basis":"reported","min":0.001}]}},"sort":{},"page":{"limit":100,"offset":0},"fields":["full_ticker","companyName","marketCap_usd"],"result_format":"rows"}'

6.0 Modify these examples

The fastest way to learn the screener API is to take one of these examples, run it exactly as written, and then change a single thing. See what happens. Then change something else.

Return only tickers

Set result_format to tickers when you only need a clean list of matching symbols. This is useful when the API result is feeding another workflow, such as a watchlist, a scheduled job, or a second data request.

Request more fields

When you use result_format: rows, the fields array controls the columns returned with each company. Add fields like gics_sector, return_1y, or netDebtToEBITDATTM when you want the response to include enough context for ranking, sorting, or quick review.

Adjust the result count

If a screen returns too many companies, tighten one filter threshold at a time. If it returns too few, loosen one threshold. Changing one value at a time makes it much easier to understand which filter is doing the real work.

Check available filters and fields

Call /api/v1/screener/options/ before building a new screen from scratch. It lists the current filters and output fields, so you can extend these examples without guessing at field names.

Ready to build more complex growth rules? See the advanced growth filter examples.

Pic
dbot