Python client reference
Call every function of the xfinlink Python client 0.13.1, with its arguments, return shapes, errors, caching and command-line tool.
The xfinlink package is a Python client for the HTTP API. Each data function sends one or more GET requests to https://api.xfinlink.com/v1/... and returns a pandas DataFrame, except resolve(), status() and usage(), which return the JSON body as a dict. Values keep the units of the endpoint behind them: prices in USD per share, fundamentals money in millions of USD, metrics ratios as decimals, holdings value_usd in whole USD. See Units and scales.
The samples on this page read the key from the XFINLINK_API_KEY environment variable.
Install the client
Install from PyPI. The package needs Python 3.9 or later and installs httpx, pandas, typer and rich.
pip install -U xfinlinkimport xfinlink as xfl
print(xfl.__version__)0.13.1API key
The client looks for a key in this order, on every request:
- The value passed to
xfl.set_api_key(). - The
XFINLINK_API_KEYenvironment variable. - The
XFL_API_KEYenvironment variable.
It sends the key in the X-API-Key header. There is no configuration file. With no key set, a call that needs a key raises XfinlinkError without sending a request; search(), resolve(), status() and llm_context() run without one. A key with characters outside ASCII, such as the masked key text on the dashboard, raises XfinlinkError without sending a request. A key the API does not accept raises XfinlinkError on the first request, with the API's message for the cause.
import xfinlink as xfl
xfl.set_api_key("YOUR_API_KEY")
df = xfl.prices("MSFT", start="2026-09-18", end="2026-09-18")
print(df[["ticker", "date", "close"]]) ticker date close
0 MSFT 2026-09-18 493.78export XFINLINK_API_KEY=YOUR_API_KEYA value set with set_api_key() takes precedence over the environment. With XFINLINK_API_KEY set to a valid key, this script still fails, because the explicit value wins:
import xfinlink as xfl
xfl.set_api_key("not-a-valid-key")
try:
xfl.prices("MSFT", start="2026-09-18", end="2026-09-18")
except xfl.XfinlinkError as e:
print(e)xfinlink: The API key sent is not a complete xfinlink key: a key is xfl_ followed by 32 characters, with no quotes, spaces or Bearer prefix. Copy it with the copy button at https://xfinlink.com/dashboardstatus() and llm_context() never send the key. See Authentication for getting and rotating keys.
Configure the client
set_api_key(key)strRequiredSets a process-wide key for every later request.
set_api_base(url)strDefault: "https://api.xfinlink.com"Replaces the base URL for every later request, including llm_context(). A trailing / is removed.
set_timeout(seconds)floatDefault: 120.0HTTP timeout in seconds for every request except llm_context(), which always uses 10 seconds.
import xfinlink as xfl
xfl.set_timeout(0.001)
try:
xfl.status()
except xfl.XfinlinkError as e:
print(e)xfinlink: Request timed out. Fix: try again in a few seconds.Response caching
The client keeps an in-memory cache for the life of the Python process. A repeated call with the same endpoint and the same query parameters returns the stored result without a request. The cache has no expiry and no size limit, and it is not shared between processes. Call xfl.clear_cache() to empty it.
Cached: prices, fundamentals, metrics, resolve, index, index_events, insiders, holdings, manager_holdings. Not cached: search, managers, changes, status, usage, llm_context.
The cache key is the endpoint and its parameters. It does not include the API key or the base URL, so call clear_cache() after set_api_key() or set_api_base() if you need results fetched under the new setting.
import time
import xfinlink as xfl
def timed(label):
t0 = time.perf_counter()
xfl.prices("MSFT", start="2026-09-01", end="2026-09-18")
print(f"{label}: {time.perf_counter() - t0:.3f}s")
timed("first call")
timed("same call again")
xfl.clear_cache()
timed("after clear_cache()")first call: 1.775s
same call again: 0.003s
after clear_cache(): 0.527sThe three calls made two HTTP requests.
Input formats
These rules apply to every function that takes the argument.
tickerstr | list[str] | tupleDefault: see each function"AAPL", ["AAPL", "MSFT"], ("AAPL",), "AAPL,MSFT" or "AAPL MSFT". Items are stripped and joined with commas into the URL path, for example /v1/prices/AAPL,MSFT. An empty value raises XfinlinkError before any request.
tickersstr | list[str] | tupleDefault: NoneKeyword-only alias for ticker, used when ticker is not given.
entity_idint | list[int] | strDefault: NoneKeyword-only. 10001629, [10001629, 10001879] or "10001629,10001879". Each id must be a positive integer. Sent as the entity_id query parameter with no ticker in the path. Cannot be combined with ticker; passing both raises XfinlinkError locally. N ids count as N tickers against your plan's per-request cap. Get ids from resolve() or search().
start, end, as_of, quarter, period_end, date_str | dateDefault: see each functionA string in the form YYYY-MM-DD, or a datetime.date. For start, end and quarter, a string of any other shape raises XfinlinkError before any request, and a datetime or pandas.Timestamp is cut to its date.
periodstrDefault: NoneShorthand for start, counted back from today: "1w" (7 days), "1mo" (30), "3mo" (90), "6mo" (182), "1y" to "30y" (N × 365 days), "ytd" (1 January this year), "max" (1900-01-01). Case-insensitive. Combining it with start or end raises XfinlinkError.
fieldsstr | list[str]Default: NoneJoined with commas and sent as fields.
Where a function defaults start, the value is 365 days before end when end is in the past, otherwise 365 days before today. On 2026-09-27, xfl.prices("AAPL") sends start=2025-09-27, and xfl.prices(["AAPL", "MSFT"], end="2020-12-31") sends start=2020-01-01.
Tickers resolve by date: each endpoint picks the company that held the ticker in your window. entity_id pins one entity explicitly. See Tickers and entities.
Each signature lists every argument in positional order with its default. Pass every argument after the first by keyword, as the samples do. period_only, include_segments and segment_members on fundamentals(), and security_class on holdings(), are accepted and do not change the rows returned; the parameter tables leave them out.
Prices: prices()
Daily or aggregated OHLCV bars with adjusted close, total return, dividends and splits. Calls GET /v1/prices/{ticker}. Prices are USD per share, volume and shares_outstanding are share counts, and market_cap is whole USD. entity_id names one share class (security).
xfl.prices(ticker=None, start=None, end=None, period=None, interval="1d",
fields=None, adjust="split", limit=None, max_rows=50000, *, tickers=None,
entity_id=None) -> pd.DataFrameParameters
tickerstr | list[str] | tupleRequired unless tickers or entity_id is givenUp to 1 ticker per request on Free, 100 on Pro, 500 on Max and 5,000 on Redistribution. A list longer than 100 is sent as batches of 100, three at a time, and the rows are combined.
tickersstr | list[str] | tupleDefault: NoneKeyword-only alias for ticker.
entity_idint | list[int] | strDefault: NoneKeyword-only. Explicit share-class ids instead of a ticker. Never batched.
startstr | dateDefault: None (365 days back)First date, inclusive. Omitted: the client sends a default start (see Input formats). On Free, the API raises start to one year before today.
endstr | dateDefault: NoneLast date, inclusive. Omitted: not sent, and the API returns through the latest bar.
periodstrDefault: NoneShorthand for start.
intervalstrDefault: "1d""1d", "3d", "1w", "1mo", "3mo", "6mo", "1y". "1d" is not sent. Any other value raises XfinlinkError locally.
fieldsstr | list[str]Default: NoneSubset of price fields. Omitted: not sent, and the API returns every field except market_cap. Name market_cap to get it.
adjuststrDefault: "split""split" or "none", case-insensitive, always sent. "none" leaves out adj_close. close is the as-traded price either way.
limitintDefault: NoneMaximum rows in total. Also the page size, up to 5,000.
max_rowsintDefault: 50000Hard cap on rows. When the cap cuts a longer result and limit is not set lower, the client emits a UserWarning.
Returns
A DataFrame with one row per ticker and bar. Columns are the API row fields in API order; date is datetime64; rows are sorted by ticker then date; the index is a fresh RangeIndex. An empty result returns an empty DataFrame and a UserWarning. Field meanings are on Price fields.
import xfinlink as xfl
df = xfl.prices("AAPL", start="2026-09-14", end="2026-09-18")
print(df[["ticker", "date", "open", "high", "low", "close", "adj_close", "volume"]])
print(list(df.columns)) ticker date open high low close adj_close volume
0 AAPL 2026-09-14 334.790 335.50 331.34 333.08 333.08 39269148
1 AAPL 2026-09-15 330.135 331.78 328.35 331.34 331.34 31748184
2 AAPL 2026-09-16 332.530 335.48 330.70 332.41 332.41 35981000
3 AAPL 2026-09-17 334.770 338.34 330.18 337.00 337.00 36700200
4 AAPL 2026-09-18 337.910 338.49 332.53 336.13 336.13 86588200
['entity_id', 'ticker', 'entity_name', 'gics_sector', 'date', 'open', 'high', 'low', 'close', 'adj_close', 'volume', 'return_daily', 'exchange_code', 'price_basis', 'shares_outstanding', 'shares_as_of', 'shares_source', 'shares_stale', 'split_ratio', 'split_ratio_suspect', 'dividend', 'dividend_type', 'ca_source']Monthly bars, a named field list and adjust="none":
import xfinlink as xfl
df = xfl.prices("AAPL", start="2026-01-01", end="2026-06-30", interval="1mo",
fields=["close", "market_cap"], adjust="none")
print(df[["ticker", "date", "close", "market_cap"]])
print(list(df.columns)) ticker date close market_cap
0 AAPL 2026-01-02 259.48 3.809462e+12
1 AAPL 2026-02-02 264.18 3.878464e+12
2 AAPL 2026-03-02 253.79 3.725927e+12
3 AAPL 2026-04-01 271.35 3.985414e+12
4 AAPL 2026-05-01 312.06 4.583336e+12
5 AAPL 2026-06-01 289.36 4.249933e+12
['entity_id', 'ticker', 'entity_name', 'gics_sector', 'date', 'close', 'price_basis', 'market_cap', 'shares_as_of', 'shares_source', 'shares_stale']market_cap here is whole USD for the AAPL share class. An entity_id names one share class directly, with no ticker in the request:
import xfinlink as xfl
gm = xfl.prices(entity_id=10001879, start="2026-09-21", end="2026-09-25")
print(gm[["entity_id", "ticker", "entity_name", "date", "close"]]) entity_id ticker entity_name date close
0 10001879 GM GENERAL MOTORS CO 2026-09-21 83.95
1 10001879 GM GENERAL MOTORS CO 2026-09-22 83.45
2 10001879 GM GENERAL MOTORS CO 2026-09-23 83.77
3 10001879 GM GENERAL MOTORS CO 2026-09-24 80.57
4 10001879 GM GENERAL MOTORS CO 2026-09-25 82.63entity_id=10001629 reaches the earlier General Motors Corporation, whose series ends on 2009-06-01; that history needs a paid plan.
Fundamentals: fundamentals()
Income statement, balance sheet and cash-flow lines per fiscal period. Calls GET /v1/fundamentals/{ticker}. Money is in millions of USD, per-share values in USD, share counts in millions. entity_id names the issuer.
xfl.fundamentals(ticker=None, start=None, end=None, period=None, period_type="all",
version="restated", view=None, as_of=None, fields=None,
period_only=False, include_segments=False,
segment_members="primary", fiscal_year=None, period_end=None,
limit=None, max_rows=10000, *, tickers=None, entity_id=None) -> pd.DataFrameParameters
tickerstr | list[str] | tupleRequired unless tickers or entity_id is givenSame caps and batching as prices().
tickersstr | list[str] | tupleDefault: NoneKeyword-only alias for ticker.
entity_idint | list[int] | strDefault: NoneKeyword-only issuer ids. An id is served as itself, so a share-class id returns no rows.
startstr | dateDefault: None (365 days back)Earliest period_end, inclusive. Always sent: from start, from period, or the 365-day default.
endstr | dateDefault: NoneLatest period_end, inclusive. Omitted: not sent.
periodstrDefault: NoneShorthand for start.
period_typestrDefault: "all""annual", "quarterly" or "all", case-insensitive, always sent. Other values raise XfinlinkError locally.
versionstrDefault: "restated""restated", "original" or "all". Always sent.
viewstrDefault: None"latest" or "as_reported". Omitted: not sent, and the API uses latest. See Point-in-time and as-of.
as_ofstr | dateDefault: NoneReturn the figures knowable on that date. Omitted: not sent.
fieldsstr | list[str]Default: NoneSubset of fundamentals fields. Structural columns are always returned.
fiscal_yearintDefault: NoneExact fiscal year. Omitted: not sent.
period_endstr | dateDefault: NoneExact period_end. Omitted: not sent.
limitintDefault: NoneMaximum rows in total; page size up to 5,000.
max_rowsintDefault: 10000Hard cap on rows, with a UserWarning when it cuts a longer result.
Returns
A DataFrame with one row per company and fiscal period. period_end and filing_date are datetime64; rows are sorted by ticker then period_end. Field meanings are on Fundamentals fields.
import xfinlink as xfl
df = xfl.fundamentals("AAPL", period_type="quarterly", start="2025-10-01",
fields=["revenue", "net_income", "eps_diluted"])
print(df[["ticker", "period_end", "fiscal_period", "filing_date", "revenue", "net_income", "eps_diluted"]])
print(list(df.columns)) ticker period_end fiscal_period filing_date revenue net_income eps_diluted
0 AAPL 2025-12-27 Q1 2026-01-30 143756 42097 2.84
1 AAPL 2026-03-28 Q2 2026-05-01 111184 29578 2.01
2 AAPL 2026-06-27 Q3 2026-07-31 109417 29789 2.02
['entity_id', 'ticker', 'entity_name', 'gics_sector', 'period_end', 'period_type', 'fiscal_year', 'fiscal_period', 'filing_date', 'version', 'source', 'revenue', 'net_income', 'eps_diluted']revenue and net_income are millions of USD; eps_diluted is USD per share.
Metrics: metrics()
Valuation, profitability, leverage and other computed metrics. Calls GET /v1/metrics/{ticker}. Ratios are decimals, not percentages; market_cap and enterprise_value are millions of USD; per-share values are USD. entity_id names the issuer.
xfl.metrics(ticker=None, period_type="annual", fields=None, start=None, end=None,
period=None, growth_basis=None, as_of=None, limit=None, max_rows=10000,
*, tickers=None, entity_id=None) -> pd.DataFrameParameters
tickerstr | list[str] | tupleRequired unless tickers or entity_id is givenSame caps and batching as prices().
tickersstr | list[str] | tupleDefault: NoneKeyword-only alias for ticker.
entity_idint | list[int] | strDefault: NoneKeyword-only issuer ids.
period_typestrDefault: "annual""annual", "quarterly", "ttm" or "daily", case-insensitive, always sent. Other values raise XfinlinkError locally.
fieldsstr | list[str]Default: NoneMetric names or category names such as "valuation". Omitted: not sent, and every metric is returned. See Metrics catalogue.
startstr | dateDefault: None (365 days back for annual and quarterly)For annual and quarterly, the client sends the 365-day default when you pass neither start nor period. For ttm and daily it sends no start unless you pass one, and the API applies its own window.
endstr | dateDefault: NoneOmitted: not sent.
periodstrDefault: NoneShorthand for start.
growth_basisstrDefault: None"yoy", "qoq" or "ttm_yoy", case-insensitive. Omitted: not sent, and the API uses yoy.
as_ofstr | dateDefault: NoneMetrics knowable on that date. Omitted: not sent.
limitintDefault: NoneMaximum rows in total; page size up to 100.
max_rowsintDefault: 10000Hard cap on rows.
Returns
A DataFrame with one row per company and period (or trading day for daily). period_end is datetime64; rows are sorted by ticker then period_end.
import xfinlink as xfl
df = xfl.metrics("AAPL", period_type="ttm", fields=["pe_ratio", "roe", "market_cap"])
print(df.T) 0
entity_id 1
ticker AAPL
entity_name APPLE INC
period_end 2026-06-27 00:00:00
period_type ttm
fiscal_year 2026
fiscal_period Q3
source filing
basis ttm
priced_on 2026-09-25
shares_as_of 2026-07-17
shares_source filing
pe_ratio 39.113532
roe 1.199126
market_cap 4977636.9726roe is a ratio (1.199 is 119.9%). market_cap is millions of USD, about 4.98 trillion USD.
Resolve: resolve()
Every entity that has used a ticker, with validity dates, FIGI and CIK codes, classifications and index membership. Calls GET /v1/resolve/{ticker}. resolve_ticker is the same function under a second name.
xfl.resolve(ticker, include=None, date_=None) -> dictParameters
tickerstr | list[str] | tupleRequiredUp to 10 tickers per call on every plan. Not batched.
includelist[str]Default: NoneSections to return: "events", "index", "classifications" or "all", joined with commas. Omitted: not sent, and every section is returned.
date_str | dateDefault: NoneResolve the ticker as it stood on that date. Sent as the date query parameter. Omitted: not sent, and the API uses today. The keyword is date_; date= raises TypeError.
Returns
The JSON body as a dict: {"data": {TICKER: {"entities": [...]}}, "meta": {...}}. The call makes one request with no retry, and the result is cached. A single ticker whose entities list is empty emits a UserWarning.
import xfinlink as xfl
info = xfl.resolve("GM")
for e in info["data"]["GM"]["entities"]:
print(e["entity_id"], e["name"], e["ticker_valid_from"], e["ticker_valid_to"])
print(sorted(info["meta"]))
print(xfl.resolve_ticker is xfl.resolve)10001879 GENERAL MOTORS CO 2010-11-18 None
10001629 GENERAL MOTORS CORP 1962-07-02 2009-06-01
['entity_id_grain', 'freshness', 'include', 'knowledge_time', 'resolved_as_of', 'tickers_requested', 'tickers_resolved', 'tickers_resolved_via', 'tickers_unresolved']
TrueResolved as of a past date, with one section:
import xfinlink as xfl
info = xfl.resolve("GM", include=["index"], date_="2005-01-01")
gm = info["data"]["GM"]["entities"]
print([(e["entity_id"], e["name"], e["ticker_valid_from"], e["ticker_valid_to"]) for e in gm])
print(gm[0]["index_membership"])
print(info["meta"]["resolved_as_of"], info["meta"]["include"])[(10001629, 'GENERAL MOTORS CORP', '1968-01-02', '2009-06-01')]
[{'index': 'SP500', 'index_name': 'S&P 500', 'added': '1925-12-31', 'removed': '2009-06-02'}]
2005-01-01 ['index']Search: search()
Find entities by name, ticker, sector, type or industry code. Calls GET /v1/search.
xfl.search(q=None, gics_sector=None, entity_type=None, sic=None, naics=None,
country="US", limit=50, offset=0) -> pd.DataFrameParameters
qstrDefault: NoneMatch on company name or ticker. Omitted or empty: not sent.
gics_sectorstrDefault: NoneExact GICS sector, for example "Information Technology".
entity_typestrDefault: NoneFor example "corporation" or "etf".
sicstrDefault: NoneExact SIC code.
naicsstrDefault: NoneExact NAICS code.
countrystrDefault: "US"Sent on every call unless you pass an empty value.
limitintDefault: 50Rows per call, at most 500. Always sent. A larger value raises XfinlinkError (HTTP 400).
offsetintDefault: 0Rows to skip. Always sent.
Returns
A DataFrame built from the rows unchanged: no date parsing, API order (match strength). Not cached. entity_id names a share class where one matched, otherwise the issuer.
import xfinlink as xfl
df = xfl.search(q="apple", limit=5)
print(df[["entity_id", "ticker", "entity_name", "entity_type", "gics_sector", "ticker_valid_to"]])
print(list(df.columns)) entity_id ticker entity_name entity_type gics_sector ticker_valid_to
0 10003635 AAPL APPLE INC corporation Information Technology NaN
1 10004284 APLE APPLE HOSPITALITY REIT INC corporation Real Estate NaN
2 10027873 CRA APPLERA CORP corporation Health Care 2008-06-30
3 10021208 ATRE APPLETREE INC corporation Consumer Staples 1996-10-21
4 10025139 APWD APPLEWOODS INC corporation Consumer Staples 1998-11-06
['entity_id', 'ticker', 'entity_name', 'entity_type', 'gics_sector', 'gics_sub_industry', 'sic', 'naics', 'country', 'ticker_valid_to']Index constituents: index()
Constituents of the S&P 500, Nasdaq-100 or Dow Jones Industrial Average, today or on a past date. Calls GET /v1/index/{index_name}.
xfl.index(index_name="sp500", as_of=None, limit=1000, offset=0) -> pd.DataFrameParameters
index_namestrDefault: "sp500""sp500", "ndx100" or "djia", placed in the URL path. Other names raise XfinlinkError (HTTP 400).
as_ofstr | dateDefault: NoneMembership on that date. Omitted: not sent, and the API returns the current roster.
limitintDefault: 1000Rows per call, at most 1,000. Always sent.
offsetintDefault: 0Rows to skip. Always sent.
Returns
A DataFrame of the rows unchanged, one row per membership spell. The result is cached. When a member has no entity record, its entity_id is NaN and the column becomes float64.
import xfinlink as xfl
df = xfl.index("djia")
print(len(df))
print(df[["ticker", "entity_name", "added_date", "removed_date", "member_grain"]].head(3))30
ticker entity_name added_date removed_date member_grain
0 MMM 3M CO 1976-08-09 None security
1 GOOGL ALPHABET INC 2026-06-29 None security
2 AMZN AMAZON COM INC 2024-02-26 None securityIndex events: index_events()
Index additions and removals as a dated event log. Calls GET /v1/index/{index_name}/events.
xfl.index_events(index_name="sp500", start=None, end=None, event_type=None,
limit=1000, offset=0) -> pd.DataFrameParameters
index_namestrDefault: "sp500""sp500", "ndx100" or "djia".
startstr | dateDefault: NoneEarliest effective_date. Omitted: not sent; the API returns from the start of its record (on Free, from one year back).
endstr | dateDefault: NoneLatest effective_date. Omitted: not sent.
event_typestrDefault: None"added" or "removed". Other values raise XfinlinkError locally. Omitted: both.
limitintDefault: 1000Rows per call, at most 1,000. Always sent.
offsetintDefault: 0Rows to skip. Always sent.
Returns
A DataFrame of the rows unchanged, oldest first. The result is cached.
import xfinlink as xfl
df = xfl.index_events("sp500", start="2026-02-01", end="2026-05-31", event_type="added")
print(df[["entity_id", "ticker", "entity_name", "event_type", "effective_date"]])
print(list(df.columns)) entity_id ticker entity_name event_type effective_date
0 10026156 CIEN Ciena added 2026-02-09
1 10001357 COHR Coherent Corp. added 2026-03-23
2 10032608 SATS EchoStar added 2026-03-23
3 10004418 LITE Lumentum added 2026-03-23
4 10006313 VRT Vertiv added 2026-03-23
5 10009318 CASY Casey's added 2026-04-09
6 10003320 VEEV Veeva Systems added 2026-05-07
['entity_id', 'ticker', 'entity_name', 'index', 'event_type', 'effective_date', 'member_grain']Insider transactions: insiders()
Insider transactions from SEC Forms 3, 4 and 5. Calls GET /v1/insiders/{ticker}. Pro plan and above; a Free key raises XfinlinkError (HTTP 402). shares are share counts, transaction_price is USD per share, transaction_value is USD. entity_id names the issuer.
xfl.insiders(ticker=None, start=None, end=None, period=None, transaction_type=None,
acquisition_or_disposition=None, ownership_type=None, form_type=None,
insider_role=None, insider_name=None, min_value=None,
include_amendments=False, include_derivatives=False,
include_uncoded=False, fields=None, limit=None, max_rows=10000, *,
tickers=None, entity_id=None) -> pd.DataFrameParameters
tickerstr | list[str] | tupleRequired unless tickers or entity_id is givenUp to 100 per request on Pro.
tickersstr | list[str] | tupleDefault: NoneKeyword-only alias for ticker.
entity_idint | list[int] | strDefault: NoneKeyword-only ids. A share-class id returns its issuer's rows.
startstr | dateDefault: None (365 days back)Earliest transaction_date. Omitted with no period: the client sends the 365-day default. period="max" sends 1900-01-01.
endstr | dateDefault: NoneLatest transaction_date. Omitted: not sent.
periodstrDefault: NoneShorthand for start.
transaction_typestr | list[str]Default: NoneFor example "open_market_buy", "open_market_sell", "grant_or_award", "option_exercise", "tax_withholding", "gift". A list is joined with commas.
acquisition_or_dispositionstrDefault: None"A" or "D".
ownership_typestrDefault: None"direct" or "indirect".
form_typestr | list[str]Default: None"3", "4", "5", "3/A", "4/A", "5/A".
insider_rolestrDefault: NoneCase-insensitive substring of the role, for example "CEO".
insider_namestrDefault: NoneCase-insensitive substring of the insider's name.
min_valuefloatDefault: NoneMinimum transaction_value, in USD.
include_amendmentsbool | NoneDefault: FalseFalse sends include_amendments=false: original filings only. None sends nothing: the version in force for each filing. True sends include_amendments=true: the version in force plus the versions it superseded.
include_derivativesboolDefault: FalseTrue sends include_derivatives=true and adds derivative-security lines, flagged by is_derivative. False sends nothing.
include_uncodedboolDefault: FalseTrue sends include_uncoded=true and adds historical rows with no SEC transaction code. False sends nothing.
fieldsstr | list[str]Default: NoneSubset of insider fields.
limitintDefault: NoneMaximum rows; page size up to 5,000.
max_rowsintDefault: 10000Hard cap on rows.
Returns
A DataFrame of transaction rows. transaction_date and filing_date are datetime64; rows are sorted by transaction_date, newest first. Field meanings are on Insider and holdings fields. The call returns the first page only; see Pagination below.
import xfinlink as xfl
df = xfl.insiders("NVDA", transaction_type="open_market_sell", start="2026-06-01", limit=5)
print(df[["transaction_date", "insider_name", "insider_role", "shares", "transaction_price", "transaction_value"]])
print(list(df.columns)) transaction_date insider_name insider_role shares transaction_price transaction_value
0 2026-09-21 Teter Timothy S. General Counsel 4499 223.7479 1.006642e+06
1 2026-09-21 Teter Timothy S. General Counsel 13478 223.0489 3.006253e+06
2 2026-09-21 Teter Timothy S. General Counsel 12483 222.1932 2.773638e+06
3 2026-09-18 STEVENS MARK A Director 10000 220.4199 2.204199e+06
4 2026-09-18 STEVENS MARK A Director 1356000 219.7219 2.979429e+08
['entity_id', 'ticker', 'entity_name', 'transaction_date', 'filing_date', 'form_type', 'insider_name', 'insider_role', 'officer_title_as_filed', 'transaction_code', 'transaction_type', 'acquisition_or_disposition', 'shares', 'shares_held_after', 'transaction_price', 'transaction_value', 'ownership_type', 'is_amendment', 'document_id', 'is_derivative', 'source', 'is_current', 'security_title', 'nature_of_ownership', 'is_weekend_dated', 'is_dated_after_own_filing', 'rule_10b5_1_affirmed', 'issuer_link_method', 'reporting_owners']The three include_amendments settings on one GE filing chain:
import xfinlink as xfl
args = dict(start="2024-08-03", end="2024-08-03", insider_name="procacci")
cols = ["transaction_date", "insider_name", "form_type", "shares", "is_amendment"]
for value in (False, None, True):
df = xfl.insiders("GE", include_amendments=value, **args)
print(f"include_amendments={value}: {len(df)} rows")
print(df[cols].to_string(index=False))include_amendments=False: 2 rows
transaction_date insider_name form_type shares is_amendment
2024-08-03 Procacci Riccardo 4 4635 False
2024-08-03 Procacci Riccardo 4 10799 False
include_amendments=None: 1 rows
transaction_date insider_name form_type shares is_amendment
2024-08-03 Procacci Riccardo 4/A 10779 True
include_amendments=True: 3 rows
transaction_date insider_name form_type shares is_amendment
2024-08-03 Procacci Riccardo 4/A 10779 True
2024-08-03 Procacci Riccardo 4 4635 False
2024-08-03 Procacci Riccardo 4 10799 FalseInstitutional holdings: holdings()
Institutional Form 13F positions in a security, quarter by quarter. Calls GET /v1/holdings/{ticker}. Pro plan and above. shares is the reported share count, not split-adjusted; value_usd is whole USD. entity_id names one share class.
xfl.holdings(ticker=None, start=None, end=None, period=None, quarter=None,
manager=None, manager_id=None, min_shares=None, min_value=None,
security_class=None, source=None, include_amendments=True, fields=None,
limit=None, max_rows=10000, *, tickers=None, entity_id=None) -> pd.DataFrameParameters
tickerstr | list[str] | tupleRequired unless tickers or entity_id is givenUp to 100 per request on Pro.
tickersstr | list[str] | tupleDefault: NoneKeyword-only alias for ticker.
entity_idint | list[int] | strDefault: NoneKeyword-only share-class ids. See Institutional holdings for issuer ids.
startstr | dateDefault: None (365 days back)Earliest report_date. Omitted with no period and no quarter: the client sends the 365-day default.
endstr | dateDefault: NoneLatest report_date. Omitted: not sent.
periodstrDefault: NoneShorthand for start.
quarterstr | dateDefault: NoneExact quarter-end report_date, for example "2026-06-30".
managerstrDefault: NoneCase-insensitive substring of manager_name.
manager_idintDefault: NoneExact manager id from managers(). Must be a Python int of 1 or more; a NumPy integer raises XfinlinkError locally, so wrap it in int().
min_valuefloatDefault: NoneMinimum value_usd, in USD.
sourcestrDefault: None"historical" or "filing": ask one era for its own statement of a quarter. Omitted: not sent.
include_amendmentsbool | NoneDefault: TrueTrue sends include_amendments=true: the position in force plus the versions it superseded. None sends nothing: one row per manager, security and quarter, the position in force. False sends include_amendments=false: original filings only.
fieldsstr | list[str]Default: NoneSubset of holdings fields. value_scale_corrected is returned only when named.
limitintDefault: NoneMaximum rows; page size up to 5,000.
max_rowsintDefault: 10000Hard cap on rows.
Returns
A DataFrame in API order: report_date newest first, then largest shares. report_date and filing_date are datetime64. The call returns the first page only; see Pagination below.
Amendment defaults differ from the REST API
insiders() sends include_amendments=false and holdings() sends include_amendments=true unless you pass another value. Pass include_amendments=None to get the REST default: one in-force version per filing or position.
import xfinlink as xfl
cols = ["manager_name", "shares", "value_usd", "is_amendment"]
default = xfl.holdings("AAPL", quarter="2026-06-30", min_value=150_000_000_000)
in_force = xfl.holdings("AAPL", quarter="2026-06-30", min_value=150_000_000_000,
include_amendments=None)
print(default[cols])
print(in_force[cols]) manager_name shares value_usd is_amendment
0 BlackRock, Inc. 1162996939 336524794350 False
1 VANGUARD CAPITAL MANAGEMENT LLC 959107911 277527465127 False
2 STATE STREET CORP 615129929 177993996255 True
3 STATE STREET CORP 615129929 177993996255 False
manager_name shares value_usd is_amendment
0 BlackRock, Inc. 1162996939 336524794350 False
1 VANGUARD CAPITAL MANAGEMENT LLC 959107911 277527465127 False
2 STATE STREET CORP 615129929 177993996255 TrueThe default call returned State Street's amended position and the original it superseded. value_usd is whole USD.
Manager holdings: manager_holdings()
One manager's reported Form 13F portfolio. Calls GET /v1/managers/{manager_id}/holdings. Pro plan and above.
xfl.manager_holdings(manager_id=None, start=None, end=None, period=None,
quarter=None, min_shares=None, min_value=None, fields=None,
limit=None, max_rows=10000) -> pd.DataFrameParameters
manager_idintRequired; the None default raises XfinlinkErrorManager id from managers(). Converted with int(); must be 1 or more.
startstr | dateDefault: None (365 days back)Earliest report_date. Omitted with no period and no quarter: the client sends the 365-day default.
endstr | dateDefault: NoneLatest report_date.
periodstrDefault: NoneShorthand for start.
quarterstr | dateDefault: NoneExact quarter-end report_date.
min_valuefloatDefault: NoneMinimum value_usd, in USD.
fieldsstr | list[str]Default: NoneSubset of holdings fields.
limitintDefault: NoneMaximum rows; page size up to 5,000.
max_rowsintDefault: 10000Hard cap on rows.
Returns
A DataFrame with the same columns as holdings(), sorted by report_date newest first, then largest value_usd. The response's manager object is not returned; the manager columns are on each row. The result is cached. An id with no rows in the window returns an empty DataFrame and a UserWarning. The call returns the first page only.
import xfinlink as xfl
df = xfl.manager_holdings(1067983, quarter="2026-06-30", limit=5)
print(df[["report_date", "ticker", "entity_name", "shares", "value_usd"]])
print(list(df.columns)) report_date ticker entity_name shares value_usd
0 2026-06-30 AAPL APPLE INC 227917808 65950296923
1 2026-06-30 AXP AMERICAN EXPRESS CO 151610700 51282319275
2 2026-06-30 KO COCA COLA CO 400000000 32508000000
3 2026-06-30 GOOGL ALPHABET INC 78791167 28157599351
4 2026-06-30 BAC BANK OF AMERICA CORP 483394015 27543790975
['entity_id', 'ticker', 'entity_name', 'report_date', 'manager_id', 'manager_name', 'manager_type', 'shares', 'filing_date', 'value_usd', 'sole_voting', 'shared_voting', 'no_voting', 'security_class', 'put_call', 'source', 'is_amendment', 'manager_id_space', 'issuer_entity_id', 'link_method', 'issuer_name_as_filed', 'title_of_class', 'lines', 'filer_cik', 'accession_number', 'unit_scale_suspect', 'is_current']Manager search: managers()
Find a Form 13F manager by name. Calls GET /v1/managers. Pro plan and above.
xfl.managers(search=None, limit=1000) -> pd.DataFrameParameters
searchstrRequired; the None default raises XfinlinkErrorCase-insensitive substring of the manager name, 2 to 100 characters after stripping. Shorter or longer values raise XfinlinkError locally.
limitintDefault: 1000Rows returned, at most 5,000. Always sent.
Returns
A DataFrame of the rows unchanged, sorted by name. Not cached. No match returns an empty DataFrame and a UserWarning.
import xfinlink as xfl
df = xfl.managers("berkshire hathaway")
print(df) manager_id manager_name manager_type country cik first_quarter last_quarter id_regime
0 1067983 Berkshire Hathaway Inc None None 0001067983 2013-06-30 2026-06-30 filingChange feed: changes()
Every change to served data after a cursor, oldest first. Calls GET /v1/changes. Max and Redistribution plans. Client 0.14.0 and later.
xfl.changes(since=None, dataset=None, limit=1000) -> dictParameters
sincestrDefault: NoneA cursor from an earlier call, meta["next_cursor"] or meta["latest_cursor"]. None reads the records published in the last 24 hours.
datasetstr | list[str] | tupleDefault: NoneDataset names, as "prices", ["prices", "fundamentals"] or "prices,fundamentals". None reads every dataset.
limitintDefault: 1000Records per page, at most 1,000. Every page is fetched whatever the value.
Returns
A dict with data, the records in feed order, and meta, the last page's meta with count set to the number of records returned. Every page is fetched, so meta["has_more"] is False. Not cached. A call that returns one or more records clears the in-memory response cache, so later calls in the process fetch fresh data.
A cursor older than 90 days raises XfinlinkError, and the error's latest_cursor attribute holds the cursor to continue from. A Free or Pro key raises XfinlinkError with API error (402).
import xfinlink as xfl
feed = xfl.changes(dataset="prices")
print(len(feed["data"]), feed["meta"]["has_more"])
try:
xfl.changes(since="MTcwMDAwMDAwMDAwMHw1")
except xfl.XfinlinkError as e:
print(e.latest_cursor)0 False
MTc5MDkyMDE0MDQzMHwwStatus: status()
API health and dataset freshness. Calls GET /v1/status without a key. Returns the JSON body as a dict with status, version and freshness. One attempt, no retry, not cached.
import xfinlink as xfl
s = xfl.status()
print(s["status"], s["version"])
for f in s["freshness"][:3]:
print(f)
print(len(s["freshness"]), "datasets")live 0.9.0
{'dataset': 'classifications', 'through': '2026-09-18', 'promoted_at': '2026-09-18T05:22:46Z', 'changed_at': None, 'change_cursor': None}
{'dataset': 'entities', 'through': '2026-09-13', 'promoted_at': '2026-09-18T05:14:35Z', 'changed_at': None, 'change_cursor': None}
{'dataset': 'entity_events', 'through': '2026-08-17', 'promoted_at': '2026-09-18T05:18:51Z', 'changed_at': None, 'change_cursor': None}
19 datasetsversion is the API's version label, not the client version.
Usage: usage()
Your account's request counts and limits for the current UTC day and clock hour. Calls GET /v1/auth/usage with your key in X-API-Key and returns the JSON body as a dict. One attempt, no retry, not cached. The call does not count against your quotas.
xfl.usage() -> dictimport xfinlink as xfl
u = xfl.usage()
print(u["tier"], u["requests_today"], "of", u["daily_limit"], "requests today, reset at", u["resets_at"])| field | type | unit | nullable | description |
|---|---|---|---|---|
requests_today | int | requests | no | Requests counted against the daily limit since 00:00 UTC, across every key on the account. |
daily_limit | int | requests | no | Requests per UTC day on your plan: 100 on Free, 10,000 on Pro, 50,000 on Max and 500,000 on Redistribution. |
resets_at | str | UTC timestamp | no | When the daily count resets: the next 00:00 UTC, such as 2026-10-01T00:00:00.000Z. |
burst_last_hour | int | requests | no | Requests counted in the current UTC clock hour. |
burst_limit | int | requests | no | Requests per UTC clock hour: 40 on Free. Pro, Max and Redistribution have no hourly limit and report 999999. |
tier | str | — | no | Your plan: free, pro, business (the Max plan) or redistribution. |
With no key set, usage() raises XfinlinkError before any request. A key the API does not accept raises XfinlinkError with the API's message for the cause. See Rate limits for how requests are counted.
LLM context: llm_context()
Fetches llms.txt from the base URL, prints it, and returns it as a str. It sends no key, uses a fixed 10-second timeout, and makes one attempt. When the pyperclip package is installed, it also copies the text to the clipboard. See Vibe coding.
import xfinlink as xfl
text = xfl.llm_context()
print(type(text).__name__)# xfinlink
Python client for US equity data: prices, financial statements, computed metrics, entity resolution, search, index constituents and membership changes, insider transactions, and institutional (Form 13F) holdings. ...
...
https://xfinlink.com/llms-holdings.txt
strClear the cache: clear_cache()
Empties the in-memory response cache. Takes no arguments and returns None.
Pagination
Each function either follows the API's cursor to the end of the result, or makes one request and returns that page. See Pagination for the API side.
prices(): followsnext_cursorand returns all rows, up tolimit,max_rows(default 50,000) or 50 pages, with up to 5,000 rows per page.fundamentals(): followsnext_cursorand returns all rows, up tolimit,max_rows(default 10,000) or 50 pages, with up to 5,000 rows per page.metrics(): followsnext_cursorand returns all rows, up tolimit,max_rows(default 10,000) or 50 pages, with up to 100 rows per page.insiders(): one request, the first page, up to 5,000 rows.holdings(): one request, the first page, up to 5,000 rows.manager_holdings(): one request, the first page, up to 5,000 rows.managers(): one request, up tolimitrows, at most 5,000.search(): one request, up tolimitrows, at most 500; page withoffset.meta.totalin the REST response counts every match.index(): one request, up tolimitrows, at most 1,000; page withoffset.index_events(): one request, up tolimitrows, at most 1,000; page withoffset.resolve(): one request, up to 10 tickers.
First page only
insiders(), holdings() and manager_holdings() return at most 5,000 rows per call and emit no warning when more exist. Split the window with start and end or quarter, or use limit and cursor on the REST endpoint.
index() and index_events() emit a UserWarning when more rows exist. search() and managers() do not.
import xfinlink as xfl
first = xfl.index("sp500", limit=100)
rest = xfl.index("sp500", limit=1000, offset=100)
print(len(first), len(rest))...: UserWarning: xfinlink: index('sp500') returned 100 of 503 rows (limit=100, offset=0). Fix: page with offset=100 to get the rest.
100 403When max_rows cuts a longer result and limit is not set lower, the client warns:
import xfinlink as xfl
df = xfl.prices("AAPL", start="2026-01-01", max_rows=10)
print(len(df))...: UserWarning: xfinlink: Results truncated at max_rows (10 rows). Later tickers in the request may be missing entirely. Fix: narrow your query with start/end dates or fewer tickers, or raise max_rows.
10When the 50-page limit stops a result, the client warns xfinlink: Results truncated at 50 pages (N rows).
Errors
Every failure raises xfinlink.XfinlinkError, a direct subclass of Exception. It carries the message in str(e) and e.args[0] and has no status-code or response attribute. Messages start with xfinlink: and end with a Fix: hint.
import xfinlink as xfl
try:
xfl.prices("ZZZZZZ")
except xfl.XfinlinkError as e:
print([c.__name__ for c in type(e).__mro__])
print(e.args)
print(hasattr(e, "status_code"), hasattr(e, "response"))['XfinlinkError', 'Exception', 'BaseException', 'object']
("xfinlink: Ticker 'ZZZZZZ' not found. Fix: check spelling, or use xfl.search(q='ZZZZZZ') to find the correct symbol.",)
False False| status | error code | when it happens | how to fix |
|---|---|---|---|
| none | local validation | A bad argument: period, interval, adjust, period_type, growth_basis, event_type, date shape, ticker with entity_id, manager_id, search length. No request is sent. | Correct the argument the message names. |
| 400 | bad_request | The API rejected a parameter, for example search(limit=1000) or a ticker list on Free. Message: API error (400): {detail}. | Change the parameter. |
| 401 | unauthorized | The key is incomplete, revoked or not recognised. The message is the API's, naming the cause; a 403 raises the API's message the same way. With no key set, the call raises before any request is sent. | Set a valid key. |
| 402 | upgrade_required | A Free key called insiders(), holdings(), manager_holdings() or managers(). Message: API error (402): {detail}. | Use a Pro key or higher. |
| 404 | not_found | No entity matches the ticker or entity_id. | Check the symbol with search(). |
| 409 | ambiguous_ticker | The ticker names more than one security on the date. The candidate list is not included in the message. | Call resolve() and pass entity_id. |
| 429 | rate limit | A quota is used up. Message: Rate limit reached ({error}). Fix: wait {retry_after_seconds}s, .... Row and as_of limits carry no retry_after_seconds, and their message reads wait ?s. | Wait the number of seconds shown. Daily limits reset at 00:00 UTC and hourly limits at the top of the UTC hour. See Rate limits. |
| 5xx | server error | The API failed after the retries described below. Message: Server error ({status}). This is temporary. | Retry later. |
| none | timeout | No response within the timeout, after retries. | Narrow the request or raise set_timeout(). |
| none | connection | The host cannot be reached, after retries. | Check the network and set_api_base(). |
Real messages for the most common cases:
import xfinlink as xfl
calls = {
"unknown ticker": lambda: xfl.prices("ZZZZZZ"),
"unknown entity_id": lambda: xfl.prices(entity_id=999999999),
"ambiguous ticker": lambda: xfl.prices("BRK"),
"invalid parameter": lambda: xfl.search(q="apple", limit=1000),
"local validation": lambda: xfl.prices("AAPL", period="2w"),
}
for name, call in calls.items():
try:
call()
except xfl.XfinlinkError as e:
print(f"{name}: {e}")unknown ticker: xfinlink: Ticker 'ZZZZZZ' not found. Fix: check spelling, or use xfl.search(q='ZZZZZZ') to find the correct symbol.
unknown entity_id: xfinlink: entity_id '999999999' not found. Fix: get a valid id from xfl.resolve('TICKER') or xfl.search(q='...').
ambiguous ticker: xfinlink: API error (409): BRK names more than one security on the requested date. Request by entity_id to choose one.. Fix: check your parameters and try again.
invalid parameter: xfinlink: API error (400): limit max is 500. Fix: check your parameters and try again.
local validation: xfinlink: period='2w' not valid. Fix: use one of ['1w', '1mo', '3mo', '6mo', '1y', '2y', '3y', '4y', '5y', '6y', '7y', '8y', '9y', '10y', '11y', '12y', '13y', '14y', '15y', '16y', '17y', '18y', '19y', '20y', '21y', '22y', '23y', '24y', '25y', '26y', '27y', '28y', '29y', '30y', 'ytd', 'max']With a Free key on a paid function:
import xfinlink as xfl
try:
xfl.insiders("AAPL")
except xfl.XfinlinkError as e:
print(e)xfinlink: API error (402): Insider transactions are available on Pro. https://xfinlink.com/pricing. Fix: check your parameters and try again.Retries
prices, fundamentals, metrics, insiders, holdings, manager_holdings, search, index, index_events and managers make up to 3 attempts when a request times out, cannot connect, or returns HTTP 502, 503 or 504. They wait 1 second after the first attempt and 2 seconds after the second. resolve, status, usage and llm_context make one attempt. A 429 is never retried.
import time
import xfinlink as xfl
xfl.set_api_base("http://localhost:9/")
print(xfl.client.API_BASE)
t0 = time.perf_counter()
try:
xfl.prices("AAPL")
except xfl.XfinlinkError as e:
print(e)
print(f"gave up after {time.perf_counter() - t0:.1f}s")http://localhost:9
xfinlink: Cannot connect to api.xfinlink.com. Fix: check your internet connection.
gave up after 3.1sThe connection message text is fixed.
Warnings
The client uses Python UserWarning for results that are complete but need your attention. Filter them with the standard warnings module.
No data returned for '{ticker}':prices,fundamentals,metrics,insidersorholdingsreturned no rows. The function returns an emptyDataFrame.No holdings returned for manager_id {id}:manager_holdingsreturned no rows.No managers matched '{search}':managersreturned no rows.Ticker '{t}' exists but has no entity data:resolveof a single ticker returned an emptyentitieslist.Results truncated at max_rows ({n} rows):max_rowscut a longer result.Results truncated at 50 pages ({n} rows): the 50-page limit stopped a cursor-following call.{call} returned {k} of {total} rows:indexorindex_eventsreturned one page of a longer result.{n} batch(es) failed: ... Partial results returned.: a list of more than 100 tickers was split into batches and some batches failed.
search, index and index_events return an empty DataFrame without a warning.
Partial failure
A ticker list longer than 100 is split into batches of 100. When some batches fail, the client returns the rows of the batches that succeeded and emits a UserWarning; when every batch fails, it raises XfinlinkError with All batch requests failed.
import xfinlink as xfl
members = xfl.index("sp500")
tickers = members["ticker"].dropna().head(100).tolist() + ["ZZZZZZ"]
df = xfl.prices(tickers, start="2026-09-18", end="2026-09-18")
print(len(tickers), "tickers requested,", df["ticker"].nunique(), "returned")...: UserWarning: xfinlink: 1 batch(es) failed: ZZZZZZ...: xfinlink: Ticker 'ZZZZZZ' not found. Fix: check spelling, or use xfl.search(q='ZZZZZZ') to find the correct symbol.. Partial results returned.
101 tickers requested, 100 returnedThe sample needs Pro or higher, because Free allows one ticker per request. Each ticker counts against your daily request quota, so this call used 101 units plus 1 for index(). See Rate limits.
Client update notice
The client reads the X-Xfinlink-Client-Update response header. When it is set, the client emits one UserWarning per process with the text of X-Xfinlink-Client-Update-Message. Client 0.13.1 receives no update notice; earlier versions, 0.13.0 included, receive one.
Plan differences
- Tickers per call: 1 on Free, where a list of two or more raises
XfinlinkError(HTTP 400). 100 on Pro, 500 on Max and 5,000 on Redistribution. The client sends batches of 100 whatever the plan. - History: on Free,
prices,fundamentals,metricsandindex_eventscover the last 12 months. The API moves an earlierstartto the date one year before today and answers normally, so the client raises no error or warning; over REST, theX-Data-Floorheader carries that date. Pro and higher plans get full history. insiders,holdings,manager_holdingsandmanagers: raiseXfinlinkError(HTTP 402) on Free, and are available on Pro and higher plans.
The same call on Free:
import xfinlink as xfl
df = xfl.prices("AAPL", start="2020-01-01")
print(len(df), df["date"].min().date(), df["date"].max().date())
try:
xfl.prices(["AAPL", "MSFT"], start="2026-09-01")
except xfl.XfinlinkError as e:
print(e)250 2025-09-29 2026-09-25
xfinlink: API error (400): Free tier allows 1 ticker per request. Upgrade to Pro for up to 100. https://xfinlink.com/pricing. Fix: check your parameters and try again.On Pro, the first call returns every AAPL bar from 2020-01-02. See Plans and limits.
Command-line tool
Installing the package adds an xfinlink command. It reads the key from XFINLINK_API_KEY or XFL_API_KEY; it has no key option. Running it with no command prints the help and exits with code 2. Every command exits with code 1 after an error and prints the client's error message.
xfinlink version: prints the installed client version. No arguments, no request.xfinlink status: callsstatus()and prints the JSON. No arguments.xfinlink resolve TICKER: callsresolve(TICKER)and prints thedataobject as JSON.TICKERis required; there is noincludeor date option.xfinlink search QUERY: callssearch(q=QUERY, limit=INT)and prints a table.QUERYis required;--limit INTdefaults to 10.xfinlink changescallschanges()and prints the JSON.--since CURSORand--dataset NAME,...are optional.xfinlink --help: help and shell completion, with--install-completion,--show-completionand--help. Each command also accepts--help.
xfinlink --help Usage: xfinlink [OPTIONS] COMMAND [ARGS]...
xfinlink: financial data API client.
╭─ Options ────────────────────────────────────────────────────────────────────────────────────────╮
│ --install-completion Install completion for the current shell. │
│ --show-completion Show completion for the current shell, to copy it or customize the │
│ installation. │
│ --help Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ───────────────────────────────────────────────────────────────────────────────────────╮
│ version Print the installed xfinlink version. │
│ status Show xfinlink API status. │
...
│ resolve Resolve a ticker to its underlying entity (via the xfinlink API). │
│ search Search for entities by name (via the xfinlink API). │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯xfinlink version0.13.1xfinlink status{
"status": "live",
"version": "0.9.0",
"freshness": [
{
"dataset": "classifications",
"through": "2026-09-18",
"promoted_at": "2026-09-18T05:22:46Z",
"changed_at": null,
"change_cursor": null
},
...
]
}xfinlink resolve AAPL{
"AAPL": {
"entities": [
{
"entity_id": 10003635,
"name": "APPLE INC",
"entity_type": "corporation",
"country": "US",
"figi": "BBG000B9XRY4",
"cik": "0000320193",
"entity_grain": "security",
"issuer_entity_id": 1,
"share_class": null,
"instrument_type": "common",
"exchange": "NASDAQ",
"ticker_valid_from": "1980-12-12",
"ticker_valid_to": null,
"classifications": {
"sic_code": "3663",
"naics_code": "334220",
"gics_sector": "Information Technology",
...
"classification_as_of": "2026-09-18"
},
"index_membership": [
{
"index": "DJIA",
"index_name": "Dow Jones Industrial Average",
"added": "2015-03-19",
"removed": null
},
...
]
}
]
}
}xfinlink search "general motors" --limit 2┏━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━┳━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ entity_id ┃ ticker ┃ entity_name ┃ entity_type ┃ gics_sector ┃ gics_sub_industry ┃ sic ┃ naics ┃ country ┃ ticker_valid_to ┃
┡━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━╇━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ 10001879 │ GM │ GENERAL MOTORS CO │ corporation │ Consumer Discretionary │ Automobile Manufacturers │ 3711 │ 336110 │ US │ nan │
│ 10001629 │ GM │ GENERAL MOTORS CORP │ corporation │ Consumer Discretionary │ Automobile Manufacturers │ 3711 │ 336110 │ US │ 2009-06-01 │
└───────────┴────────┴─────────────────────┴─────────────┴────────────────────────┴──────────────────────────┴──────┴────────┴─────────┴─────────────────┘xfinlink resolve ZZZZZZxfinlink: Ticker 'ZZZZZZ' not found. Fix: check spelling, or use xfl.search(q='ZZZZZZ') to find the
correct symbol.The command exits with code 1.
Module contents
xfinlink.__all__ lists __version__, XfinlinkError and the client's functions. dir(xfinlink) also shows client, the module that defines the functions, and annotations, a Python language import. Neither is part of the documented interface; import the functions from xfinlink directly.
Request examples
The same five days of MSFT prices from the client, from REST, and through the MCP server.
import xfinlink as xfl
df = xfl.prices("MSFT", start="2026-09-14", end="2026-09-18")
print(df[["ticker", "date", "close", "adj_close", "volume", "return_daily"]])curl -s -H "X-API-Key: YOUR_API_KEY" \
"https://api.xfinlink.com/v1/prices/MSFT?start=2026-09-14&end=2026-09-18"Show Microsoft's daily closing prices for 14 to 18 September 2026. ticker date close adj_close volume return_daily
0 MSFT 2026-09-14 505.41 505.41 23091900 0.019732
1 MSFT 2026-09-15 497.12 497.12 17794600 -0.016403
2 MSFT 2026-09-16 490.30 490.30 16670300 -0.013719
3 MSFT 2026-09-17 497.75 497.75 17841600 0.015195
4 MSFT 2026-09-18 493.78 493.78 39622300 -0.007976{
"data": [
{
"entity_id": 10000086,
"ticker": "MSFT",
"entity_name": "MICROSOFT CORP",
"gics_sector": "Information Technology",
"date": "2026-09-14",
"open": 497.06,
"high": 509.95,
"low": 495.34,
"close": 505.41,
"adj_close": 505.41,
"volume": 23091900,
"return_daily": 0.01973246171539267,
"exchange_code": "NASDAQ",
"price_basis": "trade",
"shares_outstanding": 7425545491,
"shares_as_of": "2026-07-23",
...
},
...
],
"meta": {
"tickers_requested": ["MSFT"],
"tickers_resolved": {"MSFT": 10000086},
"tickers_unresolved": [],
"resolved_via": {"MSFT": "security_alias"},
"entity_id_grain": "security",
"adj_close_basis": "split",
...
"interval": "1d",
"data_through": "2026-09-18",
"count": 5,
"limit": 1000,
"has_more": false,
"next_cursor": null,
"knowledge_time": "2026-09-26T12:48:18Z",
"as_of": "2026-09-18",
...
}
}{"tool": "get_prices", "arguments": {"ticker": "MSFT", "start": "2026-09-14", "end": "2026-09-18"}}
{
"data": [
{
"entity_id": 10000086,
"ticker": "MSFT",
"entity_name": "MICROSOFT CORP",
"gics_sector": "Information Technology",
"date": "2026-09-14",
"open": 497.06,
"high": 509.95,
"low": 495.34,
"close": 505.41,
"adj_close": 505.41,
"volume": 23091900,
"return_daily": 0.01973246171539267,
"exchange_code": "NASDAQ",
"price_basis": "trade",
"shares_outstanding": 7425545491,
"shares_as_of": "2026-07-23",
"shares_source": "filing",
"shares_stale": false,
"split_ratio": null,
"split_ratio_suspect": false,
"dividend": null,
"dividend_type": null,
...
},
...
],
"meta": {
"tickers_requested": ["MSFT"],
"tickers_resolved": {"MSFT": 10000086},
"tickers_unresolved": [],
"resolved_via": {"MSFT": "security_alias"},
"entity_id_grain": "security",
"adj_close_basis": "split",
"segments": [
{"entity_id": 10000086, "entity_name": "MICROSOFT CORP", "first_date": "2026-09-14", "last_date": "2026-09-18"}
],
"interval": "1d",
"data_through": "2026-09-18",
"count": 5,
"limit": 1000,
"has_more": false,
"next_cursor": null,
"knowledge_time": "2026-09-26T12:48:18Z",
"as_of": "2026-09-18",
"freshness": [
{"dataset": "prices", "through": "2026-09-25", "promoted_at": "2026-09-21T14:19:25Z"},
{"dataset": "price_adjustments", "through": "2026-09-25", "promoted_at": "2026-09-18T19:07:02Z"},
{"dataset": "shares_outstanding", "through": "2026-09-23", "promoted_at": "2026-09-24T08:30:33Z"}
]
}
}The client sent the same request as the curl sample, plus limit=5000 and adjust=split.
Was this page helpful?
Thank you for helping improve xfinlink documentation.
- Need help? Get in touch.
- Building with an LLM? Read llms.txt.
- Using Claude or ChatGPT? Connect the MCP server.