# xfinlink Python client for US equity data: prices, financial statements, computed metrics, entity resolution, search, and index constituents. All US-listed stocks and ETFs. Free API key required. ## Install and authenticate pip install xfinlink import xfinlink as xfl xfl.set_api_key("YOUR_API_KEY") # get a free key at https://xfinlink.com/signup Always call set_api_key() before any other xfl function. Do not use os.environ or environment variables. ## Quick example import xfinlink as xfl xfl.set_api_key("YOUR_API_KEY") df = xfl.prices("AAPL", start="2025-01-01", end="2025-03-31") print(df[["date", "close", "adj_close", "volume"]].head()) ## Full API surface ### xfl.prices(ticker, start, end, period, fields, adjust, limit, max_rows) -> pd.DataFrame def prices( ticker: str | list[str] | tuple, start: str | date | None = None, # defaults to 1 year ago end: str | date | None = None, period: str | None = None, # "1w","1mo","3mo","6mo","1y","2y","3y","5y","10y","ytd","max" fields: list[str] | str | None = None, adjust: str = "split", # "split" or "none" limit: int = 5000, max_rows: int = 10000, ) -> pd.DataFrame Historical end-of-day prices. Max 50 tickers per call. Columns returned (structural, always present): entity_id, ticker, entity_name, gics_sector, date Columns returned (data, selectable via fields=): open, high, low, close, adj_close, volume, return_daily, shares_outstanding, exchange_code, split_ratio, dividend, market_cap Default when fields is omitted: all except market_cap. When adjust="none", adj_close is excluded. Examples: df = xfl.prices("AAPL", start="2025-01-01", end="2025-06-30") df = xfl.prices(["AAPL", "MSFT"], start="2025-01-01") df = xfl.prices("AAPL MSFT GOOG", period="1y") df = xfl.prices("AAPL", start=date(2025, 1, 1)) df = xfl.prices("AAPL", fields=["close", "volume"]) ### xfl.fundamentals(ticker, start, end, period, period_type, version, fields, period_only, limit, max_rows) -> pd.DataFrame def fundamentals( ticker: str | list[str] | tuple, start: str | date | None = None, # defaults to 1 year ago end: str | date | None = None, period: str | None = None, period_type: str = "all", # "annual", "quarterly", or "all" version: str = "restated", # "restated", "original", or "all" fields: list[str] | str | None = None, period_only: bool = False, # de-cumulate YTD cash flows to single-quarter limit: int = 5000, max_rows: int = 10000, ) -> pd.DataFrame Financial statement data (income, balance sheet, cash flow). 129 fields. Max 10 tickers per call. All monetary values in millions USD. EPS and dividends_per_share in dollars. shares_outstanding in millions. Columns returned (structural, always present): entity_id, ticker, entity_name, gics_sector, period_end, period_type, fiscal_year, fiscal_period, filing_date, version, source Columns returned (data): 129 financial fields. See "Fundamentals fields" section below. Examples: df = xfl.fundamentals("AAPL", period_type="annual", period="5y") df = xfl.fundamentals("AAPL", period_type="quarterly", fields=["revenue", "net_income", "eps_diluted"]) df = xfl.fundamentals("AAPL", period_type="quarterly", period_only=True) ### xfl.metrics(ticker, period_type, fields, start, end, period, limit, max_rows) -> pd.DataFrame def metrics( ticker: str | list[str] | tuple, period_type: str = "annual", # "annual" or "quarterly" only fields: list[str] | str | None = None, start: str | date | None = None, # defaults to 1 year ago end: str | date | None = None, period: str | None = None, limit: int = 20, max_rows: int = 10000, ) -> pd.DataFrame 36 computed financial metrics. Max 50 tickers per call. Computed at query time from fundamentals + prices. Ratios are decimals (0.46 = 46%). market_cap and enterprise_value in millions USD. Per-share values in dollars. Columns returned (structural, always present): entity_id, ticker, entity_name, period_end, period_type Columns returned (data): 36 metrics. See "Metrics fields" section below. Note: metrics period_type only accepts "annual" or "quarterly" (not "all"). Examples: df = xfl.metrics("AAPL", period_type="annual", period="5y") df = xfl.metrics("AAPL", fields=["pe_ratio", "roe", "market_cap"]) df = xfl.metrics(["AAPL", "MSFT", "GOOGL"], fields=["pe_ratio", "market_cap"]) ### xfl.resolve(ticker, include) -> dict def resolve( ticker: str | list[str] | tuple, include: list[str] | None = None, # ["events","index","classifications"] or None for all ) -> dict Entity resolution. Shows every company that has used a ticker, with validity dates. Max 10 tickers per call. Returns dict with structure: result["data"]["TICKER"]["entities"] -> list of: { entity_id, name, entity_type, country, ticker_valid_from, ticker_valid_to, classifications: { sic_code, naics_code, gics_sector, gics_group, gics_industry, gics_subindustry }, events: [{ type, date, description, predecessor_entity_id, successor_entity_id }], index_membership: [{ index, added, removed }] } result["meta"] -> { tickers_requested, tickers_resolved, tickers_unresolved, include } Event types: bankruptcy, merger, spinoff, name_change, ticker_change, delisting, relisting. Examples: info = xfl.resolve("GM") entities = info["data"]["GM"]["entities"] info = xfl.resolve("AAPL", include=["classifications"]) ### xfl.search(q, gics_sector, entity_type, sic, naics, country, limit, offset) -> pd.DataFrame def search( q: str | None = None, gics_sector: str | None = None, entity_type: str | None = None, sic: str | None = None, naics: str | None = None, country: str = "US", limit: int = 50, offset: int = 0, ) -> pd.DataFrame Search for entities. Max limit: 500. Columns returned: entity_id, ticker, entity_name, entity_type, gics_sector, gics_sub_industry, sic, naics, country Examples: df = xfl.search(q="apple") df = xfl.search(gics_sector="Energy", limit=100) df = xfl.search(entity_type="etf") ### xfl.index(index_name, as_of, limit, offset) -> pd.DataFrame def index( index_name: str = "sp500", as_of: str | date | None = None, limit: int = 500, offset: int = 0, ) -> pd.DataFrame Index constituents, current or historical. Currently only "sp500" is available. Max limit: 1000. Columns returned: entity_id, ticker, entity_name, added_date, removed_date Examples: df = xfl.index("sp500") df = xfl.index("sp500", as_of="2020-01-01") ### Utility functions xfl.status() -> dict # API health check xfl.usage() -> dict # rate limit status (requires API key) xfl.llm_context() -> str # print and return this file xfl.set_api_base(url: str) # override base URL (for testing) ## Ticker input formats All ticker-accepting functions accept: "AAPL" # single string ["AAPL", "MSFT"] # list ("AAPL", "MSFT") # tuple "AAPL,MSFT" # comma-separated string "AAPL MSFT GOOG" # space-separated string ## Date input formats start and end accept: "2025-01-01" # string, YYYY-MM-DD date(2025, 1, 1) # datetime.date datetime(2025, 1, 1) # datetime.datetime pd.Timestamp("2025-01-01") # pandas Timestamp period shorthand (use period OR start/end, not both): "1w", "1mo", "3mo", "6mo", "1y", "2y", "3y", "5y", "10y", "ytd", "max" ## Worked examples ### 1. Quarterly income statement for one company import xfinlink as xfl xfl.set_api_key("YOUR_API_KEY") # period_type is case-insensitive: "annual", "quarterly", "Annual" all work df = xfl.fundamentals( "AAPL", period_type="quarterly", fields=["revenue", "gross_profit", "operating_income", "net_income", "eps_diluted"], period="2y", ) print(df[["period_end", "revenue", "net_income", "eps_diluted"]]) ### 2. Rank a sector by market cap using search + metrics import xfinlink as xfl xfl.set_api_key("YOUR_API_KEY") tech = xfl.search(gics_sector="Information Technology", limit=50) tickers = tech["ticker"].dropna().tolist() # metrics already returns entity_name — no need to merge with search results # metrics accepts up to 50 tickers per call (prices and fundamentals: max 10) m = xfl.metrics(tickers, fields=["market_cap", "pe_ratio", "roe"]) latest = m.sort_values("period_end").groupby("ticker").tail(1).reset_index(drop=True) print(latest.sort_values("market_cap", ascending=False).head(20)) ### 3. Resolve an ambiguous ticker (GM pre/post bankruptcy) import xfinlink as xfl xfl.set_api_key("YOUR_API_KEY") info = xfl.resolve("GM") for entity in info["data"]["GM"]["entities"]: print(f"{entity['name']} (id={entity['entity_id']})") print(f" ticker valid: {entity['ticker_valid_from']} to {entity['ticker_valid_to']}") for event in entity.get("events", []): print(f" event: {event['type']} on {event['date']}") ### 4. Price series with cumulative return calculation import xfinlink as xfl xfl.set_api_key("YOUR_API_KEY") # adj_close is split-only adjusted (stable, no dividend component) # return_daily is total return including dividends (use this for performance) # There is no dividend-adjusted price column — compute from return_daily if needed df = xfl.prices("AAPL", period="1y", fields=["close", "adj_close", "return_daily"]) df["cum_return"] = (1 + df["return_daily"]).cumprod() - 1 print(f"Total return: {df['cum_return'].iloc[-1]:.2%}") ### 5. S&P 500 constituents with fundamentals import xfinlink as xfl xfl.set_api_key("YOUR_API_KEY") sp = xfl.index("sp500") tickers = sp["ticker"].dropna().tolist() # fundamentals: max 10 tickers per call — batch in groups of 10 # prices: max 50 tickers per call — fewer batches needed # metrics: max 50 tickers per call — fewer batches needed import pandas as pd frames = [] for i in range(0, len(tickers), 10): batch = tickers[i:i+10] df = xfl.fundamentals(batch, period_type="annual", fields=["revenue", "net_income"], period="1y") frames.append(df) result = pd.concat(frames, ignore_index=True) print(result.sort_values("revenue", ascending=False).head(20)) ### 6. Single-quarter cash flow using period_only import xfinlink as xfl xfl.set_api_key("YOUR_API_KEY") # SEC quarterly cash flows are YTD cumulative (Q3 = Jan-Sep, not Jul-Sep) # period_only=True de-cumulates to single-quarter values # Use period="2y" or wider date range — de-cumulation needs the prior quarter df = xfl.fundamentals( "AAPL", period_type="quarterly", period_only=True, fields=["operating_cash_flow", "free_cash_flow", "capital_expenditures", "net_income_cf"], period="2y", ) # Filter to a specific quarter q3 = df[df["fiscal_period"] == "Q3"].iloc[-1] print(f"AAPL {q3['fiscal_period']} FY{int(q3['fiscal_year'])}:") print(f" Operating cash flow: ${q3['operating_cash_flow']:,.0f}M") print(f" Free cash flow: ${q3['free_cash_flow']:,.0f}M") print(f" Capital expenditures: ${q3['capital_expenditures']:,.0f}M") ## Entity Resolution & Recycled Tickers xfinlink uses permanent entity IDs. Each company has one entity_id regardless of ticker changes. Data endpoints (prices, fundamentals, metrics) resolve any ticker to its single current holder. The resolve endpoint shows the full entity lineage including all historical holders. Example: Travelers Companies has been STPL, SPK, SPC, TAP, C, STA, and currently TRV. - /v1/fundamentals/TRV returns Travelers' full history across all ticker changes - /v1/prices/C returns Citigroup (current holder of C), not Travelers - /v1/resolve/C shows both Citigroup (current) and Travelers (1998-2002) To get a specific historical entity's data, use its most recent ticker. To discover which entity held a ticker at a specific time, use /v1/resolve/{ticker}. Common recycled tickers: - C: Citigroup (current) vs Travelers (1998-2002) - GM: General Motors post-bankruptcy (current) vs pre-bankruptcy (1920-2009) - T: AT&T (current) vs SBC Medical (historical) - CVX: Chevron (current) vs SandRidge Permian Trust (historical) ## Gotchas - Default start date is 1 year ago. To get all history, pass period="max" or start="1900-01-01". - period="1y" and start="2025-01-01" cannot be combined. Use one or the other. Combining raises XfinlinkError. - adj_close is split-adjusted only. It does NOT include dividends. For total returns, use return_daily. - shares_outstanding in prices is a raw count (e.g. 15115823000). In fundamentals it is in millions (e.g. 15115.823). Different scales. - Quarterly cash flow values from SEC filings are YTD cumulative. Q3 operating_cash_flow is Jan-Sep, not Jul-Sep. Use period_only=True to de-cumulate to single-quarter values. - When period_only=True and no explicit start is set, the client auto-widens lookback by 6 months so the prior quarter is available. If you set start manually, ensure it includes at least one quarter before your target period — otherwise de-cumulated values will be null. - metrics() only accepts period_type="annual" or "quarterly". Passing "all" will cause a server error. - Empty results return an empty DataFrame plus a UserWarning. No exception is raised. - Banks and REITs have null for cost_of_goods_sold, inventory, and accounts_receivable. These fields do not exist in financial institution filings. - Fiscal years vary: Apple ends September, Microsoft ends June, most companies end December. - All fundamentals monetary values are in millions USD. EPS and dividends_per_share are in dollars. - fundamentals max tickers: 10. prices max tickers: 50. metrics max tickers: 50. - search limit max: 500. index limit max: 1000. - Rate limits: 5,000 requests/day, 500 requests/hour burst. Hitting the limit returns HTTP 429 with retry_after_seconds in the response body. ## What xfinlink does NOT do - No intraday or real-time data. End-of-day prices only. - No options, futures, or derivatives. - No non-US equities. All data is US-listed stocks and ETFs. - No dividend-adjusted prices stored. Compute from return_daily (see example 4). - No analyst estimates, consensus, or forward-looking data. - No ETF holdings or compositions. - No indices beyond S&P 500 currently. ## Error handling import xfinlink as xfl xfl.set_api_key("YOUR_API_KEY") try: df = xfl.prices("INVALIDTICKER") except xfl.XfinlinkError as e: print(e) # "xfinlink: Ticker 'INVALIDTICKER' not found. Fix: ..." All xfinlink errors raise xfl.XfinlinkError. Common causes: - 401/403: bad API key -> "xfinlink: API key invalid or missing. Fix: call xfl.set_api_key(...)" - 404: unknown ticker -> "xfinlink: Ticker '...' not found. Fix: use xfl.search(q='...')" - 429: rate limit -> "xfinlink: Rate limit reached. Fix: wait Ns" ## Price fields (12) open, high, low, close, adj_close, volume, shares_outstanding, return_daily, market_cap, exchange_code, split_ratio, dividend Price convention: close = raw unadjusted price (what actually traded) adj_close = split-only adjusted (no dividend adjustment, values are stable) return_daily = total daily return including dividends (decimal, 0.05 = 5%) dividend = cash dividend on ex-date, null otherwise split_ratio = split factor on split date (4.0 for 4:1), null otherwise market_cap = close * shares_outstanding (opt-in, not in default response) exchange_code = primary exchange: NYSE, NASDAQ, AMEX (nullable) ## Fundamentals fields (129) Income Statement (38): revenue, cost_of_revenue, cost_of_goods_sold, gross_profit, research_and_development, selling_general_admin, selling_and_marketing, general_and_admin, stock_based_compensation, depreciation_amortization, depreciation, amortization_intangibles, depletion, restructuring_charges, impairment_charges, provision_for_credit_losses, gain_loss_on_sale_of_assets, other_operating_expenses, operating_expenses_total, operating_income, interest_expense, interest_income, income_from_equity_method_investments, other_non_operating_income, pretax_income, income_tax_expense, income_from_discontinued_operations, net_income, minority_interest_income, net_income_attributable_to_parent, preferred_dividends, net_income_available_to_common, comprehensive_income, ebit, ebitda, eps_basic, eps_diluted, dividends_per_share Balance Sheet Assets (23): cash_and_equivalents, restricted_cash, marketable_securities, short_term_investments, cash_and_short_term_investments, accounts_receivable, contract_assets, inventory, prepaid_expenses, other_current_assets, current_assets_total, property_plant_equipment_gross, accumulated_depreciation, property_plant_equipment_net, operating_lease_assets, finance_lease_assets, long_term_investments, equity_method_investments, goodwill, intangible_assets, deferred_tax_assets, other_noncurrent_assets, total_assets Balance Sheet Liabilities (19): accounts_payable, short_term_debt, current_portion_long_term_debt, operating_lease_liabilities_current, finance_lease_liabilities_current, deferred_revenue_current, accrued_liabilities, other_current_liabilities, current_liabilities_total, long_term_debt, operating_lease_liabilities_noncurrent, finance_lease_liabilities_noncurrent, deferred_revenue_noncurrent, deferred_tax_liabilities, pension_liabilities, insurance_loss_reserves, other_noncurrent_liabilities, total_liabilities, total_debt Balance Sheet Equity (9): common_stock_value, preferred_equity, additional_paid_in_capital, retained_earnings, treasury_stock, accumulated_other_comprehensive_income, total_equity, minority_interest, total_equity_including_minority Cash Flow Operating (10): net_income_cf, depreciation_amortization_cf, stock_based_compensation_cf, deferred_income_taxes, change_in_accounts_receivable, change_in_inventory, change_in_accounts_payable, change_in_working_capital, other_operating_activities, operating_cash_flow Cash Flow Investing (7): capital_expenditures, acquisitions_net, proceeds_from_divestitures, purchases_of_investments, sales_of_investments, other_investing_activities, investing_cash_flow Cash Flow Financing (11): long_term_debt_issuance, long_term_debt_repayment, short_term_debt_proceeds, short_term_debt_repayments, finance_lease_principal_payments, share_issuance, share_repurchases, dividends_paid, dividends_paid_common, other_financing_activities, financing_cash_flow Cash Flow Supplemental (5): cash_taxes_paid, cash_interest_paid, effect_of_exchange_rate_on_cash, net_change_in_cash, free_cash_flow Shares (5): shares_outstanding, common_shares_issued, weighted_avg_shares_basic, weighted_avg_shares_diluted, treasury_shares Classification (2): sic, naics Aliases (accepted in fields= param): inventories->inventory, current_assets->current_assets_total, current_liabilities->current_liabilities_total, deferred_revenue->deferred_revenue_current, accumulated_other_ci->accumulated_other_comprehensive_income, property_plant_equipment->property_plant_equipment_net ## Metrics fields (36) Valuation (9): market_cap, enterprise_value, pe_ratio, ps_ratio, pb_ratio, price_to_cash_flow, ev_ebitda, earnings_yield, dividend_yield Profitability (7): gross_margin, operating_margin, net_margin, ebitda_margin, roe, roa, roic Leverage (7): debt_to_equity, debt_to_assets, long_term_debt_to_equity, long_term_debt_to_assets, current_ratio, quick_ratio, interest_coverage Efficiency (2): asset_turnover, inventory_turnover Per-share (11): revenue_per_share, book_value_per_share, tangible_book_value_per_share, cash_per_share, debt_per_share, ocf_per_share, fcf_per_share, ebit_per_share, ebitda_per_share, capex_per_share, working_capital_per_share ## MCP Server URL: https://api.xfinlink.com/mcp?api_key=YOUR_KEY Tools: get_prices, get_fundamentals, get_metrics, resolve_ticker, search, get_index ## Detailed field references https://xfinlink.com/llms-prices.txt https://xfinlink.com/llms-fundamentals.txt https://xfinlink.com/llms-metrics.txt https://xfinlink.com/llms-resolve.txt https://xfinlink.com/llms-search.txt https://xfinlink.com/llms-index.txt