DOCS

Read the docs.

Install, query, done. Everything you need to integrate xfinlink into a research pipeline, trading system, or weekend project.

Quick start

terminal
pip install -U xfinlink

Create a free account to get your API key, then:

quickstart.py
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")
fundamentals
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.

  1. Every Python call returns the last 12 months unless you pass start or period. period accepts "1w", "1mo", "3mo", "6mo", "1y" through "30y", "ytd", "max".
  2. Values are not all on the same scale. /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.

Authentication

Pass your key in the X-API-Key header. It is shown on your dashboard and can be regenerated at any time.

curl
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:

auth.py
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.

/v1/prices/{ticker}

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.

paramtypedefaultdescription
tickerstr | list[str]requiredTicker(s). 1 on Free, 100 on Pro, 500 on Max, 5,000 on Redistribution.
entity_idint | list[int]optionalEntity 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.
startstr (ISO date)optionalEarliest date, inclusive. Free keys are clamped to 12 months back.
endstr (ISO date)optionalLatest date, inclusive. Omit for the latest available.
intervalstr"1d"1d, 3d, 1w, 1mo, 3mo, 6mo, 1y. Bars aggregate within the bucket.
fieldslist[str]optionalSubset of columns. Default and fields=all both return the same 11; market_cap must be named explicitly.
adjuststr"split"split or none. none drops adj_close from the response.
limitint1000Rows per page, max 5000.
cursorstroptionalPage token from meta.next_cursor. The Python client pages for you.
request.py
df = xfl.prices("AAPL", start="2024-01-01", fields=["close", "volume"])
stdout
 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 62064040

/v1/fundamentals/{ticker}

Income statement, balance sheet and cash flow as reported. Money is in millions of USD, share counts are in millions, per-share figures are dollars.

paramtypedefaultdescription
tickerstr | list[str]requiredTicker(s). 1 on Free, 100 on Pro, 500 on Max, 5,000 on Redistribution.
entity_idint | list[int]optionalEntity 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_typestr"all"annual, quarterly, or all.
fieldslist[str]optionalSubset of the fields listed below.
versionstr"restated"restated merges near-duplicate rows for a period; original or all return raw rows.
includestroptional"segments" — adds revenue breakdowns by geography, product and business segment.
segment_membersstr"primary"primary returns segments that sum to revenue; all also includes subtotals.
startstr (ISO date)optionalEarliest period_end. Free keys are clamped to 12 months back.
endstr (ISO date)optionalLatest period_end.
fiscal_yearintoptionalExact match on fiscal_year, e.g. 2023.
period_endstr (ISO date)optionalExact match on period_end, e.g. 2023-09-30.
limitint1000Rows per page, max 5000.
cursorstroptionalPage 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.

request.py
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)
stdout
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

/v1/resolve/{ticker}

Every company that has ever used a ticker, with the dates it held it, its classifications, its index membership and its verified succession links.

paramtypedefaultdescription
tickerstrrequiredTicker, or comma-separated tickers, up to 10.
includestr"all"all, events, index, or classifications. Comma-separated.
request.py
info = xfl.resolve("GM")
for entity in info["data"]["GM"]["entities"]:
    print(entity["name"], entity["ticker_valid_from"], entity["ticker_valid_to"])
stdout
General Motors Corporation (pre-2009 bankruptcy) 1962-07-02 2009-06-01
General Motors Company 2010-11-18 None

Succession links

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:

succession.py
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())
stdout
14682 [{'entity_id': 7441, 'name': 'TIME WARNER INC', 'event_type': 'merger', 'event_date': '2001-01-11'}]
183 2000-12-31

/v1/index/{index_name}

Index members, today or on any past date. Four indices: sp500, ndx100, djia, russell2000. Returns entity_id, ticker, entity_name, added_date, removed_date.

paramtypedefaultdescription
index_namestrrequiredPath parameter. sp500, ndx100, djia, russell2000.
as_ofstr (ISO date)todayMembership on that date.
limitint1000Results per page, max 1000.
offsetint0Pagination offset.
request.py
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.

Membership changes — /v1/index/{index_name}/events

One 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.

paramtypedefaultdescription
index_namestrrequiredPath parameter. Same four indices.
startstr (ISO date)optionalEarliest effective_date. Free keys are clamped to 12 months back; the floor comes back in meta.data_floor.
endstr (ISO date)optionalLatest effective_date.
event_typestroptionaladded or removed. Omit for both.
limitint1000Results per page, max 1000.
offsetint0Pagination 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.

request.py
df = xfl.index_events("sp500", start="2024-01-01", end="2024-12-31",
                     event_type="added")
stdout
 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-02

/v1/insiders/{ticker} Paid

Insider 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.

paramtypedefaultdescription
tickerstr | list[str]requiredTicker(s). 100 on Pro, 500 on Max, 5,000 on Redistribution.
entity_idint | list[int]optionalEntity 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.
startstr (ISO date)optionalEarliest transaction_date. The Python client defaults it to 12 months back — pass period="5y" or a start for more.
endstr (ISO date)optionalLatest transaction_date.
transaction_typestroptionalDecoded type: open_market_buy, open_market_sell, grant_or_award, option_exercise, tax_withholding, gift and others. Comma-separated for several.
acquisition_or_dispositionstroptionalA or D.
ownership_typestroptionaldirect or indirect.
form_typestroptional3, 4, 5, or the amended forms 3/A, 4/A, 5/A.
insider_rolestroptionalSubstring match on role, e.g. CEO, CFO, Director.
insider_namestroptionalSubstring match on name.
min_valuenumoptionalMinimum transaction_value in dollars.
include_amendmentsboolfalseAmendment rows are excluded unless you ask for them.
fieldsstroptionalComma-separated. Use it to add the three optional fields below.
limitint1000Rows per page, max 5000.
cursorstroptionalPage 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.

request.py
df = xfl.insiders("NVDA", transaction_type="open_market_sell", period="2y")
stdout
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

/v1/holdings/{ticker} Paid

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.

paramtypedefaultdescription
tickerstr | list[str]requiredTicker(s). 100 on Pro, 500 on Max, 5,000 on Redistribution.
entity_idint | list[int]optionalEntity 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.
startstr (ISO date)optionalEarliest report_date. The Python client defaults it to 12 months back.
endstr (ISO date)optionalLatest report_date.
quarterstr (ISO date)optionalOne exact report_date, e.g. "2026-03-31".
managerstroptionalSubstring match on manager_name.
manager_idintoptionalExact manager id, from /v1/managers.
min_sharesnumoptionalMinimum share count.
min_valuenumoptionalMinimum value_usd in dollars.
security_classstroptionalCOM, ADR, UNIT, WARRANT or OTHER.
include_amendmentsbooltrueAmended figures replace the original, so they are included. Set false to exclude.
fieldsstroptionalComma-separated. Use it to add value_scale_corrected.
limitint1000Rows per page, max 5000.
cursorstroptionalPage 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.

request.py
# 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)
stdout
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

/v1/managers Paid

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.

paramtypedefaultdescription
searchstrrequiredCase-insensitive substring on manager_name, 2 to 100 characters.
limitint1000Rows per page, max 5000.
cursorstroptionalPage token from next_cursor.

Fields: manager_id, manager_name, manager_type, country, cik, first_quarter, last_quarter, id_regime.

Their portfolio — /v1/managers/{manager_id}/holdings

Same 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.

request.py
# 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")
stdout
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 17457364606

/v1/metrics/{ticker}

Ratios 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.

paramtypedefaultdescription
tickerstr | list[str]requiredTicker(s). 1 on Free, 100 on Pro, 500 on Max, 5,000 on Redistribution.
entity_idint | list[int]optionalEntity 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_typestr"annual"annual, quarterly, ttm (one trailing-12-month snapshot), or daily (one row per trading day).
fieldsstrallMetric names, or category names: valuation, profitability, leverage, liquidity, efficiency, per_share, dividends, scores, growth, volatility.
growth_basisstr"yoy"yoy, qoq, or ttm_yoy.
startstr (ISO date)optionalEarliest period_end. Daily mode defaults to 90 days back. Free keys are clamped to 12 months back.
endstr (ISO date)optionalLatest period_end.
limitint20Rows per page, max 100.
cursorstroptionalPage 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.

request.py
# 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"])
stdout — growth
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

Valuation

fieldformula
market_capclose price x shares_outstanding
market_cap_dilutedclose price x weighted_avg_shares_diluted
enterprise_valuemarket_cap + total_debt - cash
pe_ratioclose price / diluted EPS (TTM)
ps_ratiomarket_cap / revenue (TTM)
pb_ratiomarket_cap / total_equity
price_to_cash_flowmarket_cap / operating_cash_flow (TTM)
ev_ebitdaenterprise_value / EBITDA (TTM)
ev_ebitenterprise_value / EBIT (TTM)
ev_revenueenterprise_value / revenue (TTM)
ev_gross_profitenterprise_value / gross_profit (TTM)
price_to_fcfmarket_cap / free_cash_flow (TTM)
price_to_tangible_bookmarket_cap / (total_equity - goodwill - intangibles)
price_to_cashmarket_cap / cash_and_short_term_investments
price_to_net_working_capitalmarket_cap / (current_assets - current_liabilities)
tobins_qmarket_cap / total_assets
grahams_numbersqrt(22.5 x EPS_diluted x book_value_per_share)
peg_ratioP/E ratio / EPS growth rate
earnings_yieldnet_income (TTM) / market_cap
dividend_yield(DPS x shares) TTM / market_cap

Profitability

fieldformula
gross_margingross_profit / revenue
operating_marginoperating_income / revenue
net_marginnet_income / revenue
ebitda_marginEBITDA / revenue
fcf_marginfree_cash_flow / revenue
pretax_marginpretax_income / revenue
roenet_income / total_equity
roanet_income / total_assets
roic(net_income + interest_expense x 0.79) / (total_equity + long_term_debt)
return_on_tangible_assetsnet_income / avg(total_assets - goodwill - intangibles)
return_on_tangible_equitynet_income / avg(total_equity - goodwill - intangibles)
return_on_common_equity(net_income - preferred_dividends) / avg(total_equity - preferred_equity)
roe_adjusted_to_bookROE / price_to_book
return_on_total_capitaloperating_income / avg(equity + debt + minority_interest)
return_on_capital_employedoperating_income / avg(total_assets - current_liabilities)
operating_earnings_yieldoperating_income (TTM) / market_cap

Leverage

fieldformula
debt_to_equitytotal_debt / total_equity
debt_to_assetstotal_debt / total_assets
long_term_debt_to_equitylong_term_debt / total_equity
long_term_debt_to_assetslong_term_debt / total_assets
debt_to_ebitdatotal_debt / EBITDA (TTM)
debt_to_revenuetotal_debt / revenue (TTM)
net_debt_to_ebitda(total_debt - cash) / EBITDA (TTM)
equity_to_assetstotal_equity / total_assets
assets_to_equitytotal_assets / total_equity
total_debt_to_capitaltotal_debt / (total_equity + total_debt)
cash_to_debtcash_and_short_term_investments / total_debt
effective_interest_rateinterest_expense / avg(total_debt)
interest_coverageEBIT / interest_expense
ebitda_interest_coverageEBITDA / interest_expense
ebitda_less_capex_interest_coverage(EBITDA - |capex|) / interest_expense
goodwill_to_assetsgoodwill / total_assets
tangible_common_equity_ratio(equity - goodwill - intangibles - preferred) / (assets - goodwill - intangibles)

Liquidity

fieldformula
current_ratiocurrent_assets / current_liabilities
quick_ratio(current_assets - inventory) / current_liabilities
cash_ratiocash_and_short_term_investments / current_liabilities
cash_conversion_cycledays_inventory + days_sales_outstanding - days_payable

Efficiency

fieldformula
asset_turnoverrevenue / total_assets
inventory_turnovercost_of_revenue / inventory
cogs_to_revenuecost_of_revenue / revenue
inventory_to_revenueinventory / revenue
days_inventoryinventory / cost_of_revenue x days_in_period
days_payableaccounts_payable / cost_of_revenue x days_in_period
days_sales_outstandingaccounts_receivable / revenue x days_in_period
total_receivables_turnoverrevenue / accounts_receivable
fixed_assets_turnoverrevenue / net_PP&E
rd_to_revenueresearch_and_development / revenue
sga_ratioselling_general_admin / revenue

Per-Share

fieldformula
revenue_per_sharerevenue / shares_outstanding
book_value_per_sharetotal_equity / shares_outstanding
tangible_book_value_per_share(total_equity - goodwill - intangibles) / shares_outstanding
cash_per_sharecash_and_equivalents / shares_outstanding
debt_per_sharetotal_debt / shares_outstanding
ocf_per_shareoperating_cash_flow / shares_outstanding
fcf_per_sharefree_cash_flow / shares_outstanding
ebit_per_shareEBIT / shares_outstanding
ebitda_per_shareEBITDA / shares_outstanding
capex_per_sharecapital_expenditures / shares_outstanding
working_capital_per_share(current_assets - current_liabilities) / shares_outstanding
ncavps(current_assets - total_liabilities - preferred_equity) / shares_outstanding

Dividends

fieldformula
dividend_payout_ratiodividends_per_share / EPS_diluted
buyback_yield(share_repurchases - share_issuance) / market_cap
shares_buyback_ratio(prior_shares - current_shares) / prior_shares
sustainable_growth_rateROE x (1 - dividend_payout_ratio)
cash_dividend_coverageoperating_cash_flow / |dividends_paid|

Scores

fieldformula
accrualsnet_income - operating_cash_flow
quality_ratiogross_profit / total_assets
gross_profit_to_assetsgross_profit / avg(total_assets)
sloan_ratio(net_income - OCF - investing_CF) / total_assets
altman_z_score1.2(WC/A) + 1.4(RE/A) + 3.3(EBIT/A) + 0.6(MC/L) + 1.0(Rev/A)
piotroski_f_scoreSum of 9 binary tests: profitability (4), leverage (3), efficiency (2)
beneish_m_score8-ratio model (DSRI, GMI, AQI, SGI, DEPI, SGAI, LVGI, TATA)
springate_score1.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_factor9-variable model including log(tangible assets), log(EBIT/interest)
kz_index5-variable model of cash flow, Q, leverage, dividends, cash

Growth

fieldformula
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|

Volatility

fieldformula
realized_volatility_21dstddev of trailing 21 daily returns x sqrt(252)
realized_volatility_63dstddev of trailing 63 daily returns x sqrt(252)
realized_volatility_252dstddev 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.

Field reference

• /v1/fundamentals

INCOME STATEMENT (38)

revenuecost_of_revenuecost_of_goods_soldgross_profitresearch_and_developmentselling_general_adminselling_and_marketinggeneral_and_adminstock_based_compensationdepreciation_amortizationdepreciationamortization_intangiblesdepletionrestructuring_chargesimpairment_chargesprovision_for_credit_lossesgain_loss_on_sale_of_assetsother_operating_expensesoperating_expenses_totaloperating_incomeinterest_expenseinterest_incomeincome_from_equity_method_investmentsother_non_operating_incomepretax_incomeincome_tax_expenseincome_from_discontinued_operationsnet_incomeminority_interest_incomenet_income_attributable_to_parentpreferred_dividendsnet_income_available_to_commoncomprehensive_incomeebitebitdaeps_basiceps_diluteddividends_per_share

BALANCE SHEET — ASSETS (23)

cash_and_equivalentsrestricted_cashmarketable_securitiesshort_term_investmentscash_and_short_term_investmentsaccounts_receivablecontract_assetsinventoryprepaid_expensesother_current_assetscurrent_assets_totalproperty_plant_equipment_grossaccumulated_depreciationproperty_plant_equipment_netoperating_lease_assetsfinance_lease_assetslong_term_investmentsequity_method_investmentsgoodwillintangible_assetsdeferred_tax_assetsother_noncurrent_assetstotal_assets

BALANCE SHEET — LIABILITIES (19)

accounts_payableshort_term_debtcurrent_portion_long_term_debtoperating_lease_liabilities_currentfinance_lease_liabilities_currentdeferred_revenue_currentaccrued_liabilitiesother_current_liabilitiescurrent_liabilities_totallong_term_debtoperating_lease_liabilities_noncurrentfinance_lease_liabilities_noncurrentdeferred_revenue_noncurrentdeferred_tax_liabilitiespension_liabilitiesinsurance_loss_reservesother_noncurrent_liabilitiestotal_liabilitiestotal_debt

BALANCE SHEET — EQUITY (10)

common_stock_valuepreferred_equityadditional_paid_in_capitalretained_earningstreasury_stockaccumulated_other_comprehensive_incometemporary_equitytotal_equityminority_interesttotal_equity_including_minority

CASH FLOW (33)

net_income_cfdepreciation_amortization_cfstock_based_compensation_cfdeferred_income_taxeschange_in_accounts_receivablechange_in_inventorychange_in_accounts_payablechange_in_working_capitalother_operating_activitiesoperating_cash_flowcapital_expendituresacquisitions_netproceeds_from_divestiturespurchases_of_investmentssales_of_investmentsother_investing_activitiesinvesting_cash_flowlong_term_debt_issuancelong_term_debt_repaymentshort_term_debt_proceedsshort_term_debt_repaymentsfinance_lease_principal_paymentsshare_issuanceshare_repurchasesdividends_paiddividends_paid_commonother_financing_activitiesfinancing_cash_flowcash_taxes_paidcash_interest_paideffect_of_exchange_rate_on_cashnet_change_in_cashfree_cash_flow

SHARES & CLASSIFICATION (7)

shares_outstandingcommon_shares_issuedweighted_avg_shares_basicweighted_avg_shares_dilutedtreasury_sharessicnaics

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)

bank_interest_incomebank_interest_expensebank_net_interest_incomebank_noninterest_incomebank_noninterest_expensebank_loans_netbank_depositsbank_allowance_for_credit_lossesbank_net_charge_offsins_premiums_earnedins_losses_incurredins_underwriting_expenseins_net_investment_incomeins_policy_benefitsreit_rental_revenuereit_fforeit_affo
• /v1/prices
openhighlowcloseadj_closevolumereturn_dailyshares_outstandingexchange_codesplit_ratiodividendmarket_cap

The first 11 are returned by default. market_cap is opt-in: name it in fields=.

• /v1/metrics

VALUATION (20)

market_capmarket_cap_dilutedenterprise_valuepe_ratiops_ratiopb_ratioprice_to_cash_flowev_ebitdaev_ebitev_revenueev_gross_profitprice_to_fcfprice_to_tangible_bookprice_to_cashprice_to_net_working_capitaltobins_qgrahams_numberpeg_ratioearnings_yielddividend_yield

PROFITABILITY (16)

gross_marginoperating_marginnet_marginebitda_marginfcf_marginpretax_marginroeroaroicreturn_on_tangible_assetsreturn_on_tangible_equityreturn_on_common_equityroe_adjusted_to_bookreturn_on_total_capitalreturn_on_capital_employedoperating_earnings_yield

LEVERAGE (17)

debt_to_equitydebt_to_assetslong_term_debt_to_equitylong_term_debt_to_assetsdebt_to_ebitdadebt_to_revenuenet_debt_to_ebitdaequity_to_assetsassets_to_equitytotal_debt_to_capitalcash_to_debteffective_interest_rateinterest_coverageebitda_interest_coverageebitda_less_capex_interest_coveragegoodwill_to_assetstangible_common_equity_ratio

LIQUIDITY (4)

current_ratioquick_ratiocash_ratiocash_conversion_cycle

EFFICIENCY (11)

asset_turnoverinventory_turnovercogs_to_revenueinventory_to_revenuedays_inventorydays_payabledays_sales_outstandingtotal_receivables_turnoverfixed_assets_turnoverrd_to_revenuesga_ratio

PER-SHARE (12)

revenue_per_sharebook_value_per_sharetangible_book_value_per_sharecash_per_sharedebt_per_shareocf_per_sharefcf_per_shareebit_per_shareebitda_per_sharecapex_per_shareworking_capital_per_sharencavps

DIVIDENDS (5)

dividend_payout_ratiobuyback_yieldshares_buyback_ratiosustainable_growth_ratecash_dividend_coverage

SCORES (11)

accrualsquality_ratiogross_profit_to_assetssloan_ratioaltman_z_scorepiotroski_f_scorebeneish_m_scorespringate_scorezmijewski_scorefulmer_h_factorkz_index

GROWTH (12)

revenue_growthnet_income_growtheps_diluted_growtheps_basic_growthebitda_growthgross_profit_growthfcf_growthtotal_assets_growthtotal_debt_growthcapex_growthdps_growthmarket_cap_performance

VOLATILITY (3)

realized_volatility_21drealized_volatility_63drealized_volatility_252d
• /v1/resolve
entity_idnameentity_typecountryfigicikticker_valid_fromticker_valid_tosic_codenaics_codegics_sectorgics_groupgics_industrygics_subindustryindex_membership
• /v1/insiders (paid plans)
entity_identity_nametickertransaction_datefiling_dateform_typeinsider_nameinsider_roleinsider_role_othertransaction_codetransaction_typeacquisition_or_dispositionsharesshares_held_aftertransaction_pricetransaction_valueownership_typeis_amendmentdocument_idsequence_in_filingdata_quality

insider_role_other, sequence_in_filing and data_quality are opt-in: name them in fields=.

• /v1/holdings (paid plans)
entity_idtickerentity_namereport_datesharesmanager_idmanager_namemanager_typefiling_datevalue_usdsole_votingshared_votingno_votingsecurity_classput_callsourceis_amendmentvalue_scale_corrected

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.

• /v1/managers (paid plans)
manager_idmanager_namemanager_typecountrycikfirst_quarterlast_quarterid_regime

Price fields

fielddescription
closeUnadjusted closing price — what actually traded that day.
adj_closeClose adjusted for splits only, not dividends. Comparable across split events.
return_dailyTotal daily return, dividends included. Use this for performance and backtests.
dividendCash dividend on the ex-date. Null on other days.
split_ratioSplit 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:

forward_adjusted.py
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
stdout
      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:

div_adjusted.py
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])

Data caveats

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.

Recipes

— Compare revenue across big tech

compare.py
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"]])

— Total return for a year

backtest.py
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%

— Screen an index on cheapness

screen.py
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"]])

— Ticker recycling: the two General Motors

resolve.py
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.

Vibe coding

Path 1 — let an LLM write the code

  1. Get your API key (free)
  2. pip install -U xfinlink
  3. Copy the block below into Claude, ChatGPT or Cursor as context
  4. Describe what you want
context.md
Loading llms.txt…

Path 2 — connect the MCP server, no code

  1. Get your API key (free)
  2. Add the server URL below to your AI platform
  3. Ask anything — “What’s AAPL’s P/E ratio?”
MCP server URL
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

Claude (Anthropic)

Web (claude.ai) — Free, Pro, Max, Team and Enterprise plans.

  1. Profile icon → SettingsConnectors
  2. +Add custom connector
  3. Name xfinlink, URL https://api.xfinlink.com/mcp?api_key=YOUR_API_KEY
  4. Create, then start a new chat

Claude Desktop — Settings → Developer → Edit Config, add this, restart:

claude_desktop_config.json
{
  "mcpServers": {
    "xfinlink": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://api.xfinlink.com/mcp?api_key=YOUR_API_KEY"]
    }
  }
}

ChatGPT (OpenAI)

Needs Plus, Pro, Team, Enterprise or Edu.

  1. SettingsApps & ConnectorsAdvanced Settings → turn on Developer Mode
  2. ConnectorsCreate. Name xfinlink, URL https://api.xfinlink.com/mcp?api_key=YOUR_API_KEY, Authentication None
  3. New chat → +MoreDeveloper ModeAdd sources → enable xfinlink

Grok (xAI)

Needs a paid Grok account.

  1. MenuConnectorsAdd custom connector
  2. Name xfinlink, URL https://api.xfinlink.com/mcp?api_key=YOUR_API_KEY
  3. Enable it per conversation

Through the xAI API instead:

python
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",
    }],
)

Perplexity

Needs Pro, Max or Enterprise.

  1. Account SettingsConnectors+ Custom connectorRemote
  2. Name xfinlink, URL https://api.xfinlink.com/mcp?api_key=YOUR_API_KEY, Authentication None, Transport Streamable HTTP
  3. Tick the acknowledgement, Add, then click the card to enable it
  4. In a new search, toggle xfinlink under Sources

Cursor

Add to .cursor/mcp.json:

.cursor/mcp.json
{
  "mcpServers": {
    "xfinlink": {
      "url": "https://api.xfinlink.com/mcp?api_key=YOUR_API_KEY"
    }
  }
}

Rate limits

planrequests / daytickers / requesthistory
Free100 (max 40 per hour)112 months
Pro10,000100full
Max50,000500full
Redistribution500,0005,000full
No key60 per hour, per IPsearch and resolve only

Sign up for a key. See pricing for higher limits.

Errors

Errors return JSON with an error code. The Python client raises XfinlinkError with the same message.

statuserrormeaning
400bad_requestInvalid parameter. detail says which.
401unauthorizedMissing or invalid API key.
402upgrade_requiredEndpoint needs a paid plan.
404not_foundNo entity matches the ticker.
429Daily limit reached. retry_after_seconds says when to retry.
examples
{"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}