Install, query, done. Everything you need to integrate xfinlink into a research pipeline, trading system, or weekend project.
pip install -U xfinlink
Create a free account to get your API key, then:
import xfinlink as xfl xfl.set_api_key("YOUR_API_KEY") # or set XFINLINK_API_KEY in your environment prices = xfl.prices("AAPL", start="2024-01-01") fundamentals = xfl.fundamentals("AAPL", period_type="annual", period="5y") ratios = xfl.metrics("AAPL", period_type="ttm") members = xfl.index("sp500")
ticker period_end revenue net_income AAPL 2021-09-25 365817 94680 AAPL 2022-09-24 394328 99803 AAPL 2023-09-30 383285 96995 AAPL 2024-09-28 391035 93736 AAPL 2025-09-27 416161 112010
Two things to know before your first call.
start or period. period accepts "1w", "1mo", "3mo", "6mo", "1y" through "30y", "ytd", "max"./v1/fundamentals and /v1/metrics report money in millions of USD; /v1/prices reports whole dollars and whole share counts. Per-share figures are dollars everywhere.Pass your key in the X-API-Key header. It is shown on your dashboard and can be regenerated at any time.
curl -H "X-API-Key: YOUR_API_KEY" \ "https://api.xfinlink.com/v1/prices/AAPL?start=2024-01-01"
The Python client sends the header for you. Set the key either way:
import xfinlink as xfl xfl.set_api_key("YOUR_API_KEY") # in code # export XFINLINK_API_KEY=... # or in the environment
On a free key: 1 ticker per request, history starts 12 months back, 100 requests a day (max 40 in any hour). Insiders, holdings and managers return 402.
Without a key: only /v1/search and /v1/resolve answer, at 60 requests an hour per IP. Resolve then omits ticker validity dates, classifications and index membership.
Daily OHLCV with split-adjusted close, total return, and dividend and split events. Prices are dollars, shares_outstanding is a whole share count, market_cap is whole dollars.
| param | type | default | description |
|---|---|---|---|
| ticker | str | list[str] | required | Ticker(s). 1 on Free, 100 on Pro, 500 on Max, 5,000 on Redistribution. |
| entity_id | int | list[int] | optional | Entity id(s) in place of ticker, from /v1/resolve or /v1/search. Reaches an entity a ticker cannot, such as a past holder of a recycled ticker. Cannot be combined with ticker; N ids count as N tickers. |
| start | str (ISO date) | optional | Earliest date, inclusive. Free keys are clamped to 12 months back. |
| end | str (ISO date) | optional | Latest date, inclusive. Omit for the latest available. |
| interval | str | "1d" | 1d, 3d, 1w, 1mo, 3mo, 6mo, 1y. Bars aggregate within the bucket. |
| fields | list[str] | optional | Subset of columns. Default and fields=all both return the same 11; market_cap must be named explicitly. |
| adjust | str | "split" | split or none. none drops adj_close from the response. |
| limit | int | 1000 | Rows per page, max 5000. |
| cursor | str | optional | Page token from meta.next_cursor. The Python client pages for you. |
df = xfl.prices("AAPL", start="2024-01-01", fields=["close", "volume"])
entity_id ticker entity_name gics_sector date close volume
1 AAPL Apple Inc Information Technology 2024-01-02 185.64000 81752737
1 AAPL Apple Inc Information Technology 2024-01-03 184.25000 58136569
1 AAPL Apple Inc Information Technology 2024-01-04 181.91000 71280275
1 AAPL Apple Inc Information Technology 2024-01-05 181.17999 62064040Income statement, balance sheet and cash flow as reported. Money is in millions of USD, share counts are in millions, per-share figures are dollars.
| param | type | default | description |
|---|---|---|---|
| ticker | str | list[str] | required | Ticker(s). 1 on Free, 100 on Pro, 500 on Max, 5,000 on Redistribution. |
| entity_id | int | list[int] | optional | Entity id(s) in place of ticker, from /v1/resolve or /v1/search. Reaches an entity a ticker cannot, such as a past holder of a recycled ticker. Cannot be combined with ticker; N ids count as N tickers. Served as itself, with no class-to-issuer aliasing, so a share-class id returns no rows — statements are filed by the issuer. |
| period_type | str | "all" | annual, quarterly, or all. |
| fields | list[str] | optional | Subset of the fields listed below. |
| version | str | "restated" | restated merges near-duplicate rows for a period; original or all return raw rows. |
| include | str | optional | "segments" — adds revenue breakdowns by geography, product and business segment. |
| segment_members | str | "primary" | primary returns segments that sum to revenue; all also includes subtotals. |
| start | str (ISO date) | optional | Earliest period_end. Free keys are clamped to 12 months back. |
| end | str (ISO date) | optional | Latest period_end. |
| fiscal_year | int | optional | Exact match on fiscal_year, e.g. 2023. |
| period_end | str (ISO date) | optional | Exact match on period_end, e.g. 2023-09-30. |
| limit | int | 1000 | Rows per page, max 5000. |
| cursor | str | optional | Page token from meta.next_cursor. |
A bad fiscal_year or period_end is ignored with a warning in meta rather than failing the request.
By default you get one row per fiscal period. Where the preferred filing left a field empty and a sibling filing for the same period had it, the value is filled in and the field name is listed in filled_from_historical on that row.
df = xfl.fundamentals("AAPL", period_type="annual", period="5y", fields=["revenue", "net_income", "eps_diluted"]) # With revenue segments df = xfl.fundamentals("AAPL", include_segments=True)
ticker period_end revenue net_income eps_diluted AAPL 2021-09-25 365817 94680 5.61 AAPL 2022-09-24 394328 99803 6.11 AAPL 2023-09-30 383285 96995 6.13 AAPL 2024-09-28 391035 93736 6.08 AAPL 2025-09-27 416161 112010 7.46
Every company that has ever used a ticker, with the dates it held it, its classifications, its index membership and its verified succession links.
| param | type | default | description |
|---|---|---|---|
| ticker | str | required | Ticker, or comma-separated tickers, up to 10. |
| include | str | "all" | all, events, index, or classifications. Comma-separated. |
info = xfl.resolve("GM") for entity in info["data"]["GM"]["entities"]: print(entity["name"], entity["ticker_valid_from"], entity["ticker_valid_to"])
General Motors Corporation (pre-2009 bankruptcy) 1962-07-02 2009-06-01 General Motors Company 2010-11-18 None
Where a verified link exists between two entity records, resolve returns it on the entity as succession: predecessors are records whose history runs into this one, successors are records this one's history runs into. Each link carries entity_id, name, event_type and event_date, and is verified against SEC filings.
Links are direct and one hop: a succession running through three companies reads as two links. The block is omitted for an entity with no verified links; when present, both arrays are present and either can be empty. It is returned with or without an API key, is part of the default include="all" response, and can be requested alone with include="events".
Pass a link's entity_id to reach a predecessor's own data, which the ticker no longer reaches:
info = xfl.resolve("TWX") entity = info["data"]["TWX"]["entities"][0] print(entity["entity_id"], entity["succession"]["predecessors"]) df = xfl.fundamentals(entity_id=7441, period="max") # the predecessor's own statements print(len(df), df["period_end"].max().date())
14682 [{'entity_id': 7441, 'name': 'TIME WARNER INC', 'event_type': 'merger', 'event_date': '2001-01-11'}]
183 2000-12-31Find entities by name, ticker, sector, type, or classification code. Returns entity_id, ticker, entity_name, entity_type, gics_sector, gics_sub_industry, sic, naics, country.
| param | type | default | description |
|---|---|---|---|
| q | str | optional | Substring match on name or ticker, 100 characters or fewer. |
| gics_sector | str | optional | Exact GICS sector name, e.g. "Consumer Staples". |
| entity_type | str | optional | Entity type, e.g. "corporation". |
| sic | str | optional | Exact SIC code. |
| naics | str | optional | Exact NAICS code. |
| country | str | "US" | Country filter. Always applied. |
| limit | int | 50 | Results per page, max 500. |
| offset | int | 0 | Pagination offset. meta.total gives the full count. |
df = xfl.search(q="apple", limit=5) df = xfl.search(gics_sector="Consumer Staples")
ticker entity_name gics_sector 7076B APPLE BANCORP INC Financials AAPL Apple Inc Information Technology APPB.1 APPLEBEES INTERNATIONAL INC Consumer Discretionary MLP MAUI LAND & PINEAPPLE CO INC Real Estate TAVI.1 THORN APPLE VALLEY INC Consumer Staples
Index members, today or on any past date. Four indices: sp500, ndx100, djia, russell2000. Returns entity_id, ticker, entity_name, added_date, removed_date.
| param | type | default | description |
|---|---|---|---|
| index_name | str | required | Path parameter. sp500, ndx100, djia, russell2000. |
| as_of | str (ISO date) | today | Membership on that date. |
| limit | int | 1000 | Results per page, max 1000. |
| offset | int | 0 | Pagination offset. |
df = xfl.index("sp500") # 499 members today df = xfl.index("sp500", as_of="2000-01-01") # 454 members then
An as_of roster carries the company name and ticker recorded on each membership spell — what the member traded under on that date: a 2000-01-01 S&P 500 snapshot shows WORLDCOM INC GA NEW under WCOM. The live roster (no as_of) carries today's names and tickers, so the same company can appear under different labels in the two; entity_id is the stable key across both.
Rows with entity_id: null are membership spells with no linked entity record; they carry the point-in-time ticker and entity_name from the membership record. ticker is null on the earliest members, whose spells all ended by 1962; entity_name is always present. Rows without an entity_id cannot be joined to prices, fundamentals or metrics — drop them before merging.
/v1/index/{index_name}/eventsOne row per addition and per removal, oldest first. Returns entity_id, ticker, entity_name, index, event_type, effective_date. Each row carries the name and ticker recorded for that membership spell, not a modern one. entity_id is null on events from an unlinked membership spell, on the same terms as the roster above.
| param | type | default | description |
|---|---|---|---|
| index_name | str | required | Path parameter. Same four indices. |
| start | str (ISO date) | optional | Earliest effective_date. Free keys are clamped to 12 months back; the floor comes back in meta.data_floor. |
| end | str (ISO date) | optional | Latest effective_date. |
| event_type | str | optional | added or removed. Omit for both. |
| limit | int | 1000 | Results per page, max 1000. |
| offset | int | 0 | Pagination offset. |
meta.event_coverage_start is the date each index's record opens. Members already in the index on that date are the founding roster, not additions, so they are not returned as events. Removals always are.
df = xfl.index_events("sp500", start="2024-01-01", end="2024-12-31", event_type="added")
entity_id ticker entity_name index event_type effective_date
16667 DECK DECKERS OUTDOOR CORP SP500 added 2024-03-18
3143 GE GENERAL ELECTRIC CO SP500 added 2024-03-18
32621 SMCI SUPER MICRO COMPUTER INC SP500 added 2024-03-18
23642 SOLV SOLVENTUM CORP SP500 added 2024-04-01
23644 GEV G E VERNOVA INC SP500 added 2024-04-02Insider transactions from SEC Form 3, 4 and 5 filings, one row per transaction, 1996 to today. Shares and prices are split-adjusted. Paid plans only; free keys get 402.
| param | type | default | description |
|---|---|---|---|
| ticker | str | list[str] | required | Ticker(s). 100 on Pro, 500 on Max, 5,000 on Redistribution. |
| entity_id | int | list[int] | optional | Entity id(s) in place of ticker, from /v1/resolve or /v1/search. Reaches an entity a ticker cannot, such as a past holder of a recycled ticker. Cannot be combined with ticker; N ids count as N tickers. Served as itself, with no class-to-issuer aliasing, so a share-class id returns no rows — the forms are filed against the issuer. |
| start | str (ISO date) | optional | Earliest transaction_date. The Python client defaults it to 12 months back — pass period="5y" or a start for more. |
| end | str (ISO date) | optional | Latest transaction_date. |
| transaction_type | str | optional | Decoded type: open_market_buy, open_market_sell, grant_or_award, option_exercise, tax_withholding, gift and others. Comma-separated for several. |
| acquisition_or_disposition | str | optional | A or D. |
| ownership_type | str | optional | direct or indirect. |
| form_type | str | optional | 3, 4, 5, or the amended forms 3/A, 4/A, 5/A. |
| insider_role | str | optional | Substring match on role, e.g. CEO, CFO, Director. |
| insider_name | str | optional | Substring match on name. |
| min_value | num | optional | Minimum transaction_value in dollars. |
| include_amendments | bool | false | Amendment rows are excluded unless you ask for them. |
| fields | str | optional | Comma-separated. Use it to add the three optional fields below. |
| limit | int | 1000 | Rows per page, max 5000. |
| cursor | str | optional | Page token from next_cursor. |
Always returned: entity_id, ticker, entity_name, transaction_date, filing_date, form_type, insider_name, insider_role, transaction_code, transaction_type, acquisition_or_disposition, shares, shares_held_after, transaction_price, transaction_value, ownership_type, is_amendment, document_id. Add with fields=: insider_role_other, sequence_in_filing, data_quality.
df = xfl.insiders("NVDA", transaction_type="open_market_sell", period="2y")
ticker transaction_date insider_name insider_role shares transaction_price NVDA 2026-06-18 STEVENS MARK A Director 565615 210.4372 NVDA 2026-06-18 STEVENS MARK A Director 319385 209.6952 NVDA 2026-06-03 Neal Stephen C Director 15500 215.7331 NVDA 2026-03-20 KRESS COLETTE M. CFO 180 171.9951
Who holds a security, quarter by quarter, from SEC Form 13F. One row per manager, security and quarter, biggest positions first. Coverage starts 1978 and refreshes weekly. Paid plans only; free keys get 402.
| param | type | default | description |
|---|---|---|---|
| ticker | str | list[str] | required | Ticker(s). 100 on Pro, 500 on Max, 5,000 on Redistribution. |
| entity_id | int | list[int] | optional | Entity id(s) in place of ticker, from /v1/resolve or /v1/search. Reaches an entity a ticker cannot, such as a past holder of a recycled ticker. Cannot be combined with ticker; N ids count as N tickers. |
| start | str (ISO date) | optional | Earliest report_date. The Python client defaults it to 12 months back. |
| end | str (ISO date) | optional | Latest report_date. |
| quarter | str (ISO date) | optional | One exact report_date, e.g. "2026-03-31". |
| manager | str | optional | Substring match on manager_name. |
| manager_id | int | optional | Exact manager id, from /v1/managers. |
| min_shares | num | optional | Minimum share count. |
| min_value | num | optional | Minimum value_usd in dollars. |
| security_class | str | optional | COM, ADR, UNIT, WARRANT or OTHER. |
| include_amendments | bool | true | Amended figures replace the original, so they are included. Set false to exclude. |
| fields | str | optional | Comma-separated. Use it to add value_scale_corrected. |
| limit | int | 1000 | Rows per page, max 5000. |
| cursor | str | optional | Page token from next_cursor. |
Fields: entity_id, ticker, entity_name, report_date, shares, manager_id, manager_name, manager_type, filing_date, value_usd, sole_voting, shared_voting, no_voting, security_class, put_call, source, is_amendment.
A new quarter fills in over roughly 45 days as filers submit; data_through gives the latest quarter in your response. Positions before 2000 resolve less completely than later ones. Join on entity_id, not ticker, when combining with other endpoints.
# Who held Apple at the end of 2026 Q1 df = xfl.holdings("AAPL", quarter="2026-03-31") # Positions worth at least $1bn df = xfl.holdings("MSFT", quarter="2026-03-31", min_value=1_000_000_000)
ticker report_date manager_name shares value_usd AAPL 2026-03-31 BlackRock, Inc. 1144695425 290512251859 AAPL 2026-03-31 VANGUARD CAPITAL MANAGEMENT LLC 953847648 242076924860 AAPL 2026-03-31 STATE STREET CORP 602341409 152868226190
Find an institutional manager by name, then pull their portfolio. Only managers with at least one position are returned. Paid plans only; free keys get 402.
| param | type | default | description |
|---|---|---|---|
| search | str | required | Case-insensitive substring on manager_name, 2 to 100 characters. |
| limit | int | 1000 | Rows per page, max 5000. |
| cursor | str | optional | Page token from next_cursor. |
Fields: manager_id, manager_name, manager_type, country, cik, first_quarter, last_quarter, id_regime.
/v1/managers/{manager_id}/holdingsSame rows and fields as /v1/holdings, sorted by position value, with the manager in the envelope instead of on every row. Takes start, end, quarter, min_shares, min_value, fields, limit and cursor — not the manager or security filters.
# Find Berkshire, then pull their 2026 Q1 portfolio mgrs = xfl.managers("berkshire hathaway") mid = mgrs.loc[mgrs["manager_name"] == "Berkshire Hathaway Inc", "manager_id"].iloc[0] df = xfl.manager_holdings(mid, quarter="2026-03-31")
ticker entity_name shares value_usd
AAPL Apple Inc 227917808 57843260493
AXP AMERICAN EXPRESS CO 151610700 45859204536
KO COCA COLA CO 400000000 30420000000
BAC BANK OF AMERICA CORP 513624165 25039178044
CVX CHEVRON CORP 84375856 17457364606Ratios and scores, already computed. Ratios are decimals (0.46 = 46%). Market cap and enterprise value are in millions of USD. Per-share values are dollars.
| param | type | default | description |
|---|---|---|---|
| ticker | str | list[str] | required | Ticker(s). 1 on Free, 100 on Pro, 500 on Max, 5,000 on Redistribution. |
| entity_id | int | list[int] | optional | Entity id(s) in place of ticker, from /v1/resolve or /v1/search. Reaches an entity a ticker cannot, such as a past holder of a recycled ticker. Cannot be combined with ticker; N ids count as N tickers. Served as itself, with no class-to-issuer aliasing, so a share-class id returns no rows — the underlying statements are filed by the issuer. |
| period_type | str | "annual" | annual, quarterly, ttm (one trailing-12-month snapshot), or daily (one row per trading day). |
| fields | str | all | Metric names, or category names: valuation, profitability, leverage, liquidity, efficiency, per_share, dividends, scores, growth, volatility. |
| growth_basis | str | "yoy" | yoy, qoq, or ttm_yoy. |
| start | str (ISO date) | optional | Earliest period_end. Daily mode defaults to 90 days back. Free keys are clamped to 12 months back. |
| end | str (ISO date) | optional | Latest period_end. |
| limit | int | 20 | Rows per page, max 100. |
| cursor | str | optional | Page token from meta.next_cursor. |
Growth needs a prior period to compare against, so ask for more than one: period="5y" or an explicit start.
# Trailing-12-month valuation snapshot df = xfl.metrics("AAPL", period_type="ttm", fields=["valuation"]) # Year-over-year growth, five years df = xfl.metrics("AAPL", period_type="annual", period="5y", fields=["growth"]) # One row per trading day for the last 90 days df = xfl.metrics("AAPL", period_type="daily", fields=["pe_ratio", "realized_volatility_21d"])
ticker period_end revenue_growth eps_diluted_growth AAPL 2021-09-25 NaN NaN AAPL 2022-09-24 0.077938 0.089127 AAPL 2023-09-30 -0.028005 0.003273 AAPL 2024-09-28 0.020220 -0.008157 AAPL 2025-09-27 0.064255 0.226974
| field | formula |
|---|---|
| market_cap | close price x shares_outstanding |
| market_cap_diluted | close price x weighted_avg_shares_diluted |
| enterprise_value | market_cap + total_debt - cash |
| pe_ratio | close price / diluted EPS (TTM) |
| ps_ratio | market_cap / revenue (TTM) |
| pb_ratio | market_cap / total_equity |
| price_to_cash_flow | market_cap / operating_cash_flow (TTM) |
| ev_ebitda | enterprise_value / EBITDA (TTM) |
| ev_ebit | enterprise_value / EBIT (TTM) |
| ev_revenue | enterprise_value / revenue (TTM) |
| ev_gross_profit | enterprise_value / gross_profit (TTM) |
| price_to_fcf | market_cap / free_cash_flow (TTM) |
| price_to_tangible_book | market_cap / (total_equity - goodwill - intangibles) |
| price_to_cash | market_cap / cash_and_short_term_investments |
| price_to_net_working_capital | market_cap / (current_assets - current_liabilities) |
| tobins_q | market_cap / total_assets |
| grahams_number | sqrt(22.5 x EPS_diluted x book_value_per_share) |
| peg_ratio | P/E ratio / EPS growth rate |
| earnings_yield | net_income (TTM) / market_cap |
| dividend_yield | (DPS x shares) TTM / market_cap |
| field | formula |
|---|---|
| gross_margin | gross_profit / revenue |
| operating_margin | operating_income / revenue |
| net_margin | net_income / revenue |
| ebitda_margin | EBITDA / revenue |
| fcf_margin | free_cash_flow / revenue |
| pretax_margin | pretax_income / revenue |
| roe | net_income / total_equity |
| roa | net_income / total_assets |
| roic | (net_income + interest_expense x 0.79) / (total_equity + long_term_debt) |
| return_on_tangible_assets | net_income / avg(total_assets - goodwill - intangibles) |
| return_on_tangible_equity | net_income / avg(total_equity - goodwill - intangibles) |
| return_on_common_equity | (net_income - preferred_dividends) / avg(total_equity - preferred_equity) |
| roe_adjusted_to_book | ROE / price_to_book |
| return_on_total_capital | operating_income / avg(equity + debt + minority_interest) |
| return_on_capital_employed | operating_income / avg(total_assets - current_liabilities) |
| operating_earnings_yield | operating_income (TTM) / market_cap |
| field | formula |
|---|---|
| debt_to_equity | total_debt / total_equity |
| debt_to_assets | total_debt / total_assets |
| long_term_debt_to_equity | long_term_debt / total_equity |
| long_term_debt_to_assets | long_term_debt / total_assets |
| debt_to_ebitda | total_debt / EBITDA (TTM) |
| debt_to_revenue | total_debt / revenue (TTM) |
| net_debt_to_ebitda | (total_debt - cash) / EBITDA (TTM) |
| equity_to_assets | total_equity / total_assets |
| assets_to_equity | total_assets / total_equity |
| total_debt_to_capital | total_debt / (total_equity + total_debt) |
| cash_to_debt | cash_and_short_term_investments / total_debt |
| effective_interest_rate | interest_expense / avg(total_debt) |
| interest_coverage | EBIT / interest_expense |
| ebitda_interest_coverage | EBITDA / interest_expense |
| ebitda_less_capex_interest_coverage | (EBITDA - |capex|) / interest_expense |
| goodwill_to_assets | goodwill / total_assets |
| tangible_common_equity_ratio | (equity - goodwill - intangibles - preferred) / (assets - goodwill - intangibles) |
| field | formula |
|---|---|
| current_ratio | current_assets / current_liabilities |
| quick_ratio | (current_assets - inventory) / current_liabilities |
| cash_ratio | cash_and_short_term_investments / current_liabilities |
| cash_conversion_cycle | days_inventory + days_sales_outstanding - days_payable |
| field | formula |
|---|---|
| asset_turnover | revenue / total_assets |
| inventory_turnover | cost_of_revenue / inventory |
| cogs_to_revenue | cost_of_revenue / revenue |
| inventory_to_revenue | inventory / revenue |
| days_inventory | inventory / cost_of_revenue x days_in_period |
| days_payable | accounts_payable / cost_of_revenue x days_in_period |
| days_sales_outstanding | accounts_receivable / revenue x days_in_period |
| total_receivables_turnover | revenue / accounts_receivable |
| fixed_assets_turnover | revenue / net_PP&E |
| rd_to_revenue | research_and_development / revenue |
| sga_ratio | selling_general_admin / revenue |
| field | formula |
|---|---|
| revenue_per_share | revenue / shares_outstanding |
| book_value_per_share | total_equity / shares_outstanding |
| tangible_book_value_per_share | (total_equity - goodwill - intangibles) / shares_outstanding |
| cash_per_share | cash_and_equivalents / shares_outstanding |
| debt_per_share | total_debt / shares_outstanding |
| ocf_per_share | operating_cash_flow / shares_outstanding |
| fcf_per_share | free_cash_flow / shares_outstanding |
| ebit_per_share | EBIT / shares_outstanding |
| ebitda_per_share | EBITDA / shares_outstanding |
| capex_per_share | capital_expenditures / shares_outstanding |
| working_capital_per_share | (current_assets - current_liabilities) / shares_outstanding |
| ncavps | (current_assets - total_liabilities - preferred_equity) / shares_outstanding |
| field | formula |
|---|---|
| dividend_payout_ratio | dividends_per_share / EPS_diluted |
| buyback_yield | (share_repurchases - share_issuance) / market_cap |
| shares_buyback_ratio | (prior_shares - current_shares) / prior_shares |
| sustainable_growth_rate | ROE x (1 - dividend_payout_ratio) |
| cash_dividend_coverage | operating_cash_flow / |dividends_paid| |
| field | formula |
|---|---|
| accruals | net_income - operating_cash_flow |
| quality_ratio | gross_profit / total_assets |
| gross_profit_to_assets | gross_profit / avg(total_assets) |
| sloan_ratio | (net_income - OCF - investing_CF) / total_assets |
| altman_z_score | 1.2(WC/A) + 1.4(RE/A) + 3.3(EBIT/A) + 0.6(MC/L) + 1.0(Rev/A) |
| piotroski_f_score | Sum of 9 binary tests: profitability (4), leverage (3), efficiency (2) |
| beneish_m_score | 8-ratio model (DSRI, GMI, AQI, SGI, DEPI, SGAI, LVGI, TATA) |
| springate_score | 1.03A + 3.07B + 0.66C + 0.4D |
| zmijewski_score | -4.336 - 4.513(NI/A) + 5.679(TL/A) + 0.004(CA/CL) |
| fulmer_h_factor | 9-variable model including log(tangible assets), log(EBIT/interest) |
| kz_index | 5-variable model of cash flow, Q, leverage, dividends, cash |
| field | formula |
|---|---|
| revenue_growth | (current - prior) / |prior|. TTM-to-TTM in ttm/daily modes |
| net_income_growth | (current - prior) / |prior| |
| eps_diluted_growth | (current - prior) / |prior| |
| eps_basic_growth | (current - prior) / |prior| |
| ebitda_growth | (current - prior) / |prior| |
| gross_profit_growth | (current - prior) / |prior| |
| fcf_growth | (current - prior) / |prior| |
| total_assets_growth | (current - prior) / |prior| (balance sheet) |
| total_debt_growth | (current - prior) / |prior| (balance sheet) |
| capex_growth | (current - prior) / |prior| |
| dps_growth | (current - prior) / |prior| |
| market_cap_performance | (current_mcap - prior_mcap) / |prior_mcap| |
| field | formula |
|---|---|
| realized_volatility_21d | stddev of trailing 21 daily returns x sqrt(252) |
| realized_volatility_63d | stddev of trailing 63 daily returns x sqrt(252) |
| realized_volatility_252d | stddev of trailing 252 daily returns x sqrt(252) |
Annualized, as a decimal (0.2628 = 26.28% a year). A value needs the window's full count of trading days, spanning no more than about 1.5x the normal calendar time (46 / 137 / 548 days). Otherwise it is null, meaning not measurable here. Do not read that as zero.
INCOME STATEMENT (38)
BALANCE SHEET — ASSETS (23)
BALANCE SHEET — LIABILITIES (19)
BALANCE SHEET — EQUITY (10)
CASH FLOW (33)
SHARES & CLASSIFICATION (7)
Share counts are as-filed and in millions. They are what the company reported on the filing date, not restated for later splits. To put a period's share count on today's basis, multiply by the cumulative product of split_ratio from /v1/prices for every split after period_end.
INDUSTRY — BANKING (9) • INSURANCE (5) • REIT (3)
The first 11 are returned by default. market_cap is opt-in: name it in fields=.
VALUATION (20)
PROFITABILITY (16)
LEVERAGE (17)
LIQUIDITY (4)
EFFICIENCY (11)
PER-SHARE (12)
DIVIDENDS (5)
SCORES (11)
GROWTH (12)
VOLATILITY (3)
insider_role_other, sequence_in_filing and data_quality are opt-in: name them in fields=.
value_scale_corrected is opt-in: name it in fields=. On /v1/managers/{manager_id}/holdings the three manager columns move into the envelope and are not repeated on each row.
| field | description |
|---|---|
| close | Unadjusted closing price — what actually traded that day. |
| adj_close | Close adjusted for splits only, not dividends. Comparable across split events. |
| return_daily | Total daily return, dividends included. Use this for performance and backtests. |
| dividend | Cash dividend on the ex-date. Null on other days. |
| split_ratio | Split ratio on the split date, e.g. 4.0 for a 4-for-1. Null on other days. |
adj_close is adjusted backwards onto today's share basis. To adjust forwards from a date instead, take raw close and split_ratio and compound the ratios forward:
import xfinlink as xfl df = xfl.prices("AAPL", start="2020-08-27", end="2020-09-02", fields=["close", "split_ratio"], adjust="none") df = df.sort_values("date") factor = df["split_ratio"].fillna(1.0).cumprod() df["forward_adj_close"] = df["close"] * factor
date ticker close split_ratio forward_adj_close 2020-08-27 AAPL 500.04001 NaN 500.04001 2020-08-28 AAPL 499.23001 NaN 499.23001 2020-08-31 AAPL 129.03999 4.0 516.15996 2020-09-01 AAPL 134.17999 NaN 536.71996 2020-09-02 AAPL 131.39999 NaN 525.59996
There is no dividend-adjusted price field: that series rewrites its own history at every dividend. Stable values plus total returns are stored instead — build it yourself:
import xfinlink as xfl df = xfl.prices("AAPL", period="5y", fields=["adj_close", "return_daily"]) df["total_return_index"] = (1 + df["return_daily"]).cumprod() df["div_adj_close"] = (df["adj_close"].iloc[-1] * df["total_return_index"] / df["total_return_index"].iloc[-1])
Price fields and fundamentals fields sit on different bases. Do not mix them by hand.
adj_close is on today's split basis. Fundamentals per-share fields (eps_diluted, dividends_per_share) are as filed, with no split adjustment. Dividing one by the other gives a wrong P/E whenever a split happened between the reporting date and today. Use /v1/metrics, which handles the bases for you, or adjust the fundamentals side first.
Money is in millions of USD on /v1/fundamentals and /v1/metrics, and in whole dollars on /v1/prices and /v1/holdings. market_cap exists on both prices and metrics, on those two different scales.
import xfinlink as xfl df = xfl.fundamentals( ["AAPL", "MSFT", "GOOGL"], period_type="annual", fields=["revenue", "net_income"], start="2024-01-01", ) print(df[["ticker", "period_end", "revenue", "net_income"]])
import xfinlink as xfl df = xfl.prices("AAPL", start="2024-01-01", end="2024-12-31") df["cumulative_return"] = (1 + df["return_daily"]).cumprod() - 1 print(f"AAPL 2024 total return: {df['cumulative_return'].iloc[-1]:.1%}") # AAPL 2024 total return: 30.7%
import xfinlink as xfl sp = xfl.index("sp500") df = xfl.metrics(sp["ticker"].head(50).tolist(), period_type="ttm", fields=["pe_ratio", "roe"]) df = df[df["pe_ratio"] > 0] # negative P/E means losses, not cheapness print(df.nsmallest(5, "pe_ratio")[["ticker", "pe_ratio", "roe"]])
import xfinlink as xfl info = xfl.resolve("GM") for entity in info["data"]["GM"]["entities"]: print(entity["entity_id"], f"{entity['name']}: {entity['ticker_valid_from']} → {entity['ticker_valid_to']}") # 4 General Motors Corporation (pre-2009 bankruptcy): 1962-07-02 → 2009-06-01 # 5 General Motors Company: 2010-11-18 → None # With no dates the ticker serves the current holder. An explicit end date that # predates that holder resolves to the company that held GM back then. old = xfl.prices("GM", start="2000-01-01", end="2008-12-31") # pre-2009 General Motors; the response reports the switch in historical_resolution # Or name the entity outright — works with or without dates. old = xfl.prices(entity_id=4, period="max") print(len(old), old["date"].min().date(), old["date"].max().date()) # 3377 1996-01-02 2009-06-01 # Same parameters on fundamentals, metrics, insiders and holdings.
pip install -U xfinlinkLoading llms.txt…
https://api.xfinlink.com/mcp?api_key=YOUR_API_KEY
Tools: get_prices, get_fundamentals, get_metrics, resolve_ticker, search, get_index, get_index_events, get_insiders, get_holdings, get_manager_holdings, search_managers
Web (claude.ai) — Free, Pro, Max, Team and Enterprise plans.
xfinlink, URL https://api.xfinlink.com/mcp?api_key=YOUR_API_KEYClaude Desktop — Settings → Developer → Edit Config, add this, restart:
{
"mcpServers": {
"xfinlink": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://api.xfinlink.com/mcp?api_key=YOUR_API_KEY"]
}
}
}Needs Plus, Pro, Team, Enterprise or Edu.
xfinlink, URL https://api.xfinlink.com/mcp?api_key=YOUR_API_KEY, Authentication NoneNeeds a paid Grok account.
xfinlink, URL https://api.xfinlink.com/mcp?api_key=YOUR_API_KEYThrough the xAI API instead:
from openai import OpenAI client = OpenAI( api_key="your-xai-api-key", base_url="https://api.x.ai/v1", ) response = client.responses.create( model="grok-4.20-reasoning", input=[{"role": "user", "content": "What's AAPL's quarterly revenue trend?"}], tools=[{ "type": "mcp", "server_url": "https://api.xfinlink.com/mcp?api_key=YOUR_API_KEY", "server_label": "xfinlink", }], )
Needs Pro, Max or Enterprise.
xfinlink, URL https://api.xfinlink.com/mcp?api_key=YOUR_API_KEY, Authentication None, Transport Streamable HTTPAdd to .cursor/mcp.json:
{
"mcpServers": {
"xfinlink": {
"url": "https://api.xfinlink.com/mcp?api_key=YOUR_API_KEY"
}
}
}| plan | requests / day | tickers / request | history |
|---|---|---|---|
| Free | 100 (max 40 per hour) | 1 | 12 months |
| Pro | 10,000 | 100 | full |
| Max | 50,000 | 500 | full |
| Redistribution | 500,000 | 5,000 | full |
| No key | 60 per hour, per IP | — | search and resolve only |
Sign up for a key. See pricing for higher limits.
Errors return JSON with an error code. The Python client raises XfinlinkError with the same message.
| status | error | meaning |
|---|---|---|
| 400 | bad_request | Invalid parameter. detail says which. |
| 401 | unauthorized | Missing or invalid API key. |
| 402 | upgrade_required | Endpoint needs a paid plan. |
| 404 | not_found | No entity matches the ticker. |
| 429 | — | Daily limit reached. retry_after_seconds says when to retry. |
{"error":"bad_request","status":400,"detail":"Invalid start date format (YYYY-MM-DD)"}
{"error":"unauthorized","message":"API key required. Get one free at https://xfinlink.com/signup"}
{"error":"not_found","status":404,"detail":"No matching entities for: ZZZZZZ"}
{"error":"Daily limit reached (100 requests/day). Resets at midnight UTC.","retry_after_seconds":28800}