# DFin API v1

Canonical HTML page: [/docs/api/v1/](/docs/api/v1/)

LLM-friendly Markdown page: [/docs/api/v1.md](/docs/api/v1.md)

Runtime base path: `/api/v1/`

Base URL: `https://www.dfin.pro`

The DFin API v1 provides filing search and equity screener endpoints. Runtime endpoint paths, request constraints, rate limits, and key choices are rendered from the same code that serves the API.

## 1.0 Overview

| Item | Value |
| --- | --- |
| Base URL | `https://www.dfin.pro` |
| API version | `v1` |
| Runtime base path | `/api/v1/` |
| Docs alternate | `/docs/api/v1.md` |
| Postman collection | `/docs/api/v1.postman_collection.json` |

## 2.0 Global Rules

### 2.1 Authentication

Set your API key before running the examples.

PowerShell:

```powershell
$env:DFIN_API_KEY = "paste-your-api-key-here"
```

Bash:

```bash
export DFIN_API_KEY="paste-your-api-key-here"
```

The curl and Python examples use this environment variable to send the HTTP authorization header.

### 2.2 Request Format

POST endpoints accept JSON request bodies with `Content-Type: application/json`. GET endpoints do not require a JSON body.

### 2.3 Rate Limits

| Window | Starter Tier Limit | Notes |
| --- | --- | --- |
| Per minute | 30 requests per minute | Authenticated requests that later fail validation can still count. |
| Rolling 24 hours | 1,000 requests per rolling 24 hours | Missing or invalid API keys fail before throttling. |

### 2.4 Screener Controls

Screener POST endpoints share pagination, sorting, field selection, and `result_format` behavior. Pagination defaults to `{"limit":100,"offset":0}`, `page.limit` must be between 1 and 1000, and `result_format` can be rows, tickers.

### 2.5 Common Errors

| Status | Meaning | Typical Fix |
| --- | --- | --- |
| 400 | Request validation failed. | Check parameter names, types, allowed values, and screener mode. |
| 401 | Missing, malformed, inactive, or invalid API key. | Set `DFIN_API_KEY` and use the generated Authorization header shown in the examples. |
| 429 | Rate limit exceeded. | Back off and retry after the throttle window resets. |
| 502 | Filing search backend did not return a usable result. | Retry later or narrow the search request. |

## 3.0 Endpoint Summary

| Area | Method | Path | Purpose |
| --- | --- | --- | --- |
| Filing Search | POST | `/api/v1/filings/search/` | Search structured company filings. |
| Screener | GET | `/api/v1/screener/options/` | Return compact request-building metadata for the current screener API contract. |
| Screener | POST | `/api/v1/screener/basic/` | Run the simple screener filter set. |
| Screener | POST | `/api/v1/screener/advanced/` | Run the advanced screener contract. |


## 4.0 Filing Search

Filing search endpoints query company filings with one or more natural-language queries and optional structured filters.

### 4.1 Search filings

POST `/api/v1/filings/search/`

Search structured company filings.

#### 4.1.1 Parameters

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `queries` | array of string | Yes | - | max length 12, max items 12, item max length 300 Submit one or more natural-language filing search queries. |
| `name` | string | No | - | max length 120 Optional company name. |
| `ticker` | string | No | - | max length 40 Optional ticker. |
| `fiscal_year` | integer | No | - | min 1900, max 2200 Fiscal year filter. |
| `fiscal_period` | string | No | - | choices: Q1, Q2, Q3, Q4, H1, H2, FY Fiscal period filter. |
| `filing_type` | string | No | - | max length 30 Optional filing type. |
| `results` | integer | No | `5` | min 1, max 25 Number of filing-search results to request. |
| `rerank` | boolean | No | `false` | Defaults to false for the time being. Reranking functionality will be enabled in the future. |
| `searchtype` | string | No | `&quot;hybrid&quot;` | choices: hybrid, vector, keyword Filing-search strategy. |


#### 4.1.2 curl

```bash
curl -X POST "https://www.dfin.pro/api/v1/filings/search/" \
  -H "Authorization: Bearer ${DFIN_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"queries":["revenue growth","margin expansion"],"ticker":"AAPL.US","fiscal_year":2024,"filing_type":"10-K","results":5,"rerank":false,"searchtype":"hybrid"}'
```

#### 4.1.3 Python

```python
import os, requests

url = "https://www.dfin.pro/api/v1/filings/search/"
headers = {"Authorization": f"Bearer {os.environ['DFIN_API_KEY']}"}
payload = {'queries': ['revenue growth', 'margin expansion'],
 'ticker': 'AAPL.US',
 'fiscal_year': 2024,
 'filing_type': '10-K',
 'results': 5,
 'rerank': False,
 'searchtype': 'hybrid'}

response = requests.post(url, headers=headers, json=payload, timeout=30)
response.raise_for_status()
print(response.json())
```

#### 4.1.4 Response

The v1 response returns the current filing-search result payload from the filing search backend. Future response normalization will happen behind this endpoint without changing the request shape unless a new API version is introduced.

## 5.0 Screener

The screener endpoints expose the DFin equity screener through a contract-driven API. Use the options endpoint as the source of truth for current public filter, sort, field, shared-default, and accepted option metadata.

For step-by-step walkthroughs, start with the [API v1 examples library](/docs/api/v1/examples/), including the [starter-screen screener API examples](/docs/api/v1/examples/screener-starter-screens/) and the [advanced growth filter examples](/docs/api/v1/examples/advanced-growth-filters/).

### 5.1 Get screener options

GET `/api/v1/screener/options/`

Return compact request-building metadata for the current screener API contract.

The options response is compact for API and AI clients: `modes.basic` and `modes.advanced` list allowed filter keys directly, `sort.fields` lists sortable field names for the existing `sort.field` request property, and `filter_type_defaults` carries shared operators and rule limits. Growth-rule currency bases are derived from each metric's `value_fields[value_type]` keys.

#### 5.1.1 curl

```bash
curl -X GET "https://www.dfin.pro/api/v1/screener/options/" \
  -H "Authorization: Bearer ${DFIN_API_KEY}"
```

#### 5.1.2 Python

```python
import os, requests

url = "https://www.dfin.pro/api/v1/screener/options/"
headers = {"Authorization": f"Bearer {os.environ['DFIN_API_KEY']}"}

response = requests.get(url, headers=headers, timeout=30)
response.raise_for_status()
print(response.json())
```

#### 5.1.3 Example Response

```json
{
  "result_formats": [
    "rows",
    "tickers"
  ],
  "page": {
    "default_limit": 100,
    "default_offset": 0,
    "max_limit": 1000
  },
  "sort": {
    "fields": [
      "marketCap_usd",
      "return_1y"
    ]
  },
  "modes": {
    "basic": [
      "country",
      "sector",
      "marketCap_usd",
      "..."
    ],
    "advanced": [
      "country",
      "incomeStatementGrowth",
      "marketCap_usd",
      "..."
    ]
  },
  "filter_type_defaults": {
    "range": {
      "operators": [
        "min",
        "max"
      ]
    },
    "growth_rule_group": {
      "horizons": [
        1,
        2,
        3,
        5,
        7,
        8,
        10
      ],
      "value_types": [
        "cagr",
        "multiple"
      ],
      "value_type_formats": {
        "cagr": "percent",
        "multiple": "number"
      },
      "missing_data_policy": "exclude",
      "max_rules": 5
    }
  },
  "filters": {
    "country": {
      "label": "Country",
      "input_type": "multi_select",
      "operators": [
        "include",
        "exclude"
      ],
      "default_operator": "include",
      "options_source": "countries"
    }
  },
  "option_values": {
    "countries": [
      "Canada",
      "United States"
    ]
  },
  "fields": [
    "companyName",
    "full_ticker",
    "marketCap_usd"
  ]
}
```

### 5.2 Request Envelope

Both screener POST endpoints accept the same top-level envelope. The difference is which filter keys are allowed.

#### 5.2.1 Fields

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `filters` | object | No | `{}` | Filter payload keyed by screener filter name. |
| `sort` | object | No | `{}` | Supports the screener sort object, including field and descending direction. |
| `page` | object | No | `{}` | Supports limit and offset. The public maximum limit is code-defined. Maximum limit is 1000. |
| `fields` | array of string | No | - | item max length 120 Requested output fields for rows responses. |
| `result_format` | string | No | `&quot;rows&quot;` | choices: rows, tickers Rows returns row objects. Tickers returns a flat ticker list and ignores custom fields. |


#### 5.2.2 Building screener requests

The options endpoint is the contract an agent should inspect before composing a screen. It tells you which filters exist, which endpoint can accept each filter, which values can be submitted, which output fields can be requested, and which sort keys are valid.

| Step | What to do |
| --- | --- |
| Fetch options | Call GET /api/v1/screener/options/ first. Treat that response as the current source of valid filters, modes, option values, sort fields, output fields, and shared defaults. |
| Choose endpoint | Use POST /api/v1/screener/basic/ only when every selected filter is listed in options.modes.basic. Use POST /api/v1/screener/advanced/ when any selected filter is advanced-only. |
| Build filters | For each selected filter, inspect options.filters[filterName].input_type and use the matching payload grammar. When a definition omits common operators or limits, inherit them from options.filter_type_defaults[input_type]. |
| Select output | For rows responses, choose field names from options.fields. For compact ticker-only output, set result_format to tickers and omit custom fields. |
| Sort and page | Choose sort.field from options.sort.fields, submit sort as an object with field and desc, and keep page.limit between 1 and the documented maximum. |


#### 5.2.3 Filter payload grammar

Each entry in `filters` is keyed by a filter name from `options.filters`. Use that filter's `input_type` to choose the payload shape. When a filter definition omits common operators or limits, inherit them from `options.filter_type_defaults[input_type]`; values shown directly on the filter definition are more specific and take precedence.

| Input type | Payload shape | Notes | Example |
| --- | --- | --- | --- |
| `range` | `{&quot;filterName&quot;:{&quot;min&quot;:number,&quot;max&quot;:number}}` | Use min, max, or both to set numeric thresholds. The available range keys come from filter_type_defaults.range. | `{&quot;marketCap_usd&quot;:{&quot;min&quot;:10000000000}}` |
| `multi_select` | `{&quot;filterName&quot;:{&quot;operator&quot;:&quot;include&quot;,&quot;values&quot;:[&quot;accepted option&quot;]}}` | Use values from option_values or from an inline options list. If the user does not ask to exclude values, use the filter default_operator. | `{&quot;country&quot;:{&quot;operator&quot;:&quot;include&quot;,&quot;values&quot;:[&quot;United States&quot;]}}` |
| `text` | `{&quot;description_keywords&quot;:{&quot;fts&quot;:&quot;search terms&quot;}}` | Use this for full-text searches against company business descriptions. | `{&quot;description_keywords&quot;:{&quot;fts&quot;:&quot;cloud software&quot;}}` |
| `metric_rule_group` | `{&quot;filterName&quot;:{&quot;missing_policy&quot;:&quot;strict&quot;,&quot;rules&quot;:[...]}}` | Use range rules for thresholds and compare rules when the screen needs TTM, a fiscal year, or a trailing fiscal-year average. | `{&quot;returnOnInvestedCapital&quot;:{&quot;rules&quot;:[{&quot;kind&quot;:&quot;range&quot;,&quot;operand&quot;:{&quot;kind&quot;:&quot;average&quot;,&quot;years&quot;:3,&quot;anchor&quot;:{&quot;scope&quot;:&quot;fy&quot;,&quot;offset&quot;:0}},&quot;min&quot;:0.15}]}}` |
| `growth_rule_group` | `{&quot;filterName&quot;:{&quot;rules&quot;:[{&quot;metric&quot;:&quot;revenue&quot;,&quot;horizon_years&quot;:5,&quot;value_type&quot;:&quot;cagr&quot;,&quot;currency_basis&quot;:&quot;reported&quot;,&quot;min&quot;:0.10}]}}` | Choose the metric, horizon, value type, currency basis, and threshold. Valid currency bases come from metric.value_fields[value_type]. | `{&quot;incomeStatementGrowth&quot;:{&quot;rules&quot;:[{&quot;metric&quot;:&quot;revenue&quot;,&quot;horizon_years&quot;:5,&quot;value_type&quot;:&quot;cagr&quot;,&quot;currency_basis&quot;:&quot;reported&quot;,&quot;min&quot;:0.10}]}}` |
| `volatility_rule_group` | `{&quot;volatility&quot;:{&quot;rules&quot;:[{&quot;kind&quot;:&quot;range&quot;,&quot;field&quot;:&quot;daily_return_vol_1y_annualized&quot;,&quot;max&quot;:0.40}]}}` | Use member fields from filters.volatility.members. Range rules test one member; compare rules compare two members. | `{&quot;volatility&quot;:{&quot;rules&quot;:[{&quot;kind&quot;:&quot;compare&quot;,&quot;left_field&quot;:&quot;daily_return_vol_1y_annualized&quot;,&quot;operator&quot;:&quot;lt&quot;,&quot;right_field&quot;:&quot;daily_return_vol_5y_annualized&quot;}]}}` |
| `field_compare` | `{&quot;volatility_compare&quot;:{&quot;left_field&quot;:&quot;field&quot;,&quot;operator&quot;:&quot;lt&quot;,&quot;right_field&quot;:&quot;field&quot;}}` | Legacy/simple field comparison form for volatility fields. The canonical volatility rule group is preferred for new advanced requests. | `{&quot;volatility_compare&quot;:{&quot;left_field&quot;:&quot;daily_return_vol_1y_annualized&quot;,&quot;operator&quot;:&quot;lt&quot;,&quot;right_field&quot;:&quot;daily_return_vol_5y_annualized&quot;}}` |


#### 5.2.4 Using option values

For filters with `options_source`, submit values from the matching list in `options.option_values`. These are accepted request values, not browser display labels. For filters with inline `options`, submit one of those listed values. For growth metrics, derive valid `currency_basis` values from the keys under `metric.value_fields[value_type]`; for example, if a metric has `value_fields.cagr.reported` and `value_fields.cagr.usd`, both `reported` and `usd` are valid for CAGR.

#### 5.2.5 Units and thresholds

| Topic | Rule |
| --- | --- |
| Percentages and ratios | Use decimal values: 0.10 means 10%, 0.5 means 50%, and 1.0 means 100%. |
| USD amounts | Fields ending in _usd, such as marketCap_usd, use raw U.S. dollars, not millions or billions. |
| CAGR | Growth value_type cagr uses decimal annualized rates, so min 0.10 means at least 10% annualized growth. |
| Growth multiple | Growth value_type multiple uses numeric multiples, so min 2.0 means the metric at least doubled over the horizon. |
| TTM | TTM means trailing twelve months and is used by simple shorthand fields such as dividendYieldTTM. |
| FY offsets | In metric-rule operands, FY offset 0 means the latest eligible fiscal year. Larger offsets move backward one fiscal year at a time. |


#### 5.2.6 Request snippets

These snippets show only the relevant part of the request body. Put filter examples under the top-level `filters` object when sending a screener POST request.


##### Multi-select filter

Country, sector, industry, currency, and database coverage filters use an operator plus accepted values.

```json
{
  "country": {
    "operator": "include",
    "values": [
      "United States"
    ]
  }
}
```


##### Range filter

Range filters accept min, max, or both.

```json
{
  "marketCap_usd": {
    "min": 10000000000
  }
}
```


##### Text filter

Business-description search uses the fts key.

```json
{
  "description_keywords": {
    "fts": "cloud software"
  }
}
```


##### Metric rule group

Historical metric rules can target TTM, one fiscal year, or a trailing fiscal-year average.

```json
{
  "returnOnInvestedCapital": {
    "missing_policy": "strict",
    "rules": [
      {
        "kind": "range",
        "operand": {
          "kind": "average",
          "years": 3,
          "anchor": {
            "scope": "fy",
            "offset": 0
          }
        },
        "min": 0.15
      }
    ]
  }
}
```


##### Growth rule group

Growth filters combine metric, horizon, value type, currency basis, and min/max thresholds.

```json
{
  "incomeStatementGrowth": {
    "rules": [
      {
        "metric": "revenue",
        "horizon_years": 5,
        "value_type": "cagr",
        "currency_basis": "reported",
        "min": 0.1
      }
    ]
  }
}
```


##### Volatility rule group

Volatility rules can filter one member field or compare two member fields.

```json
{
  "volatility": {
    "rules": [
      {
        "kind": "range",
        "field": "daily_return_vol_1y_annualized",
        "max": 0.4
      }
    ]
  }
}
```


##### Sort and fields

Sort with a supported field and request row fields from options.fields.

```json
{
  "sort": {
    "field": "marketCap_usd",
    "desc": true
  },
  "fields": [
    "full_ticker",
    "companyName",
    "marketCap_usd"
  ]
}
```



### 5.3 Run a basic screener

POST `/api/v1/screener/basic/`

The basic endpoint accepts business profile filters, database coverage, market and valuation ranges, returns, volatility ranges, common TTM shorthand ranges, and net income.

#### 5.3.1 curl

```bash
curl -X POST "https://www.dfin.pro/api/v1/screener/basic/" \
  -H "Authorization: Bearer ${DFIN_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"filters":{"country":{"operator":"include","values":["United States"]},"sector":{"operator":"include","values":["Information Technology"]},"marketCap_usd":{"min":1000000000},"return_1y":{"min":0.1}},"sort":{"field":"marketCap_usd","desc":true},"page":{"limit":100,"offset":0},"fields":["full_ticker","companyName","marketCap_usd"],"result_format":"rows"}'
```

#### 5.3.2 Python

```python
import os, requests

url = "https://www.dfin.pro/api/v1/screener/basic/"
headers = {"Authorization": f"Bearer {os.environ['DFIN_API_KEY']}"}
payload = {'filters': {'country': {'operator': 'include', 'values': ['United States']},
             'sector': {'operator': 'include', 'values': ['Information Technology']},
             'marketCap_usd': {'min': 1000000000},
             'return_1y': {'min': 0.1}},
 'sort': {'field': 'marketCap_usd', 'desc': True},
 'page': {'limit': 100, 'offset': 0},
 'fields': ['full_ticker', 'companyName', 'marketCap_usd'],
 'result_format': 'rows'}

response = requests.post(url, headers=headers, json=payload, timeout=30)
response.raise_for_status()
print(response.json())
```

#### 5.3.3 Example Response

```json
{
  "total": 1,
  "limit": 100,
  "offset": 0,
  "loaded_fields": [
    "full_ticker",
    "companyName",
    "marketCap_usd"
  ],
  "temporary_columns": [],
  "rows": [
    {
      "full_ticker": "AAPL.US",
      "companyName": "Apple",
      "marketCap_usd": 3000000000000
    }
  ]
}
```

### 5.4 Run an advanced screener

POST `/api/v1/screener/advanced/`

The advanced endpoint accepts every basic filter plus the canonical advanced screener contract, including metric rule groups, growth/CAGR/multiple filters, composite metric member filters, and canonical volatility rule groups.

#### 5.4.1 curl

```bash
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"]},"incomeStatementGrowth":{"rules":[{"metric":"revenue","horizon_years":5,"value_type":"cagr","currency_basis":"reported","min":0.1}]}},"result_format":"tickers","page":{"limit":100,"offset":0}}'
```

#### 5.4.2 Python

```python
import os, requests

url = "https://www.dfin.pro/api/v1/screener/advanced/"
headers = {"Authorization": f"Bearer {os.environ['DFIN_API_KEY']}"}
payload = {'filters': {'country': {'operator': 'include', 'values': ['United States']},
             'incomeStatementGrowth': {'rules': [{'metric': 'revenue',
                                                  'horizon_years': 5,
                                                  'value_type': 'cagr',
                                                  'currency_basis': 'reported',
                                                  'min': 0.1}]}},
 'result_format': 'tickers',
 'page': {'limit': 100, 'offset': 0}}

response = requests.post(url, headers=headers, json=payload, timeout=30)
response.raise_for_status()
print(response.json())
```

#### 5.4.3 Example Response

```json
{
  "total": 2,
  "limit": 100,
  "offset": 0,
  "tickers": [
    "AAPL.US",
    "MSFT.US"
  ]
}
```

## 6.0 Confusing Cases

- Basic vs advanced: basic rejects advanced-only filter keys. Use `/api/v1/screener/options/` to inspect `modes.basic` and `modes.advanced`.
- Rows vs tickers: `result_format="tickers"` returns a flat `tickers` array and forces the backend to request only `full_ticker`.
- Pagination: `page.limit` defaults to 100 and must be between 1 and 1000.
- Filter metadata: the current public screener filter, sort, field, and option metadata comes from `/api/v1/screener/options/`, not from a static docs table.

Browser reference: [DFin stock screener](/equity/screener/).
