Get startedDashboard

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.

View llms.txt

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.

Terminal
pip install -U xfinlink
version.py
import xfinlink as xfl

print(xfl.__version__)
stdout
0.13.1

API key

The client looks for a key in this order, on every request:

  1. The value passed to xfl.set_api_key().
  2. The XFINLINK_API_KEY environment variable.
  3. The XFL_API_KEY environment 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.

set_key.py
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"]])
stdout
  ticker       date   close
0   MSFT 2026-09-18  493.78
Terminal
export XFINLINK_API_KEY=YOUR_API_KEY

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

precedence.py
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)
stdout
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/dashboard

status() and llm_context() never send the key. See Authentication for getting and rotating keys.

Configure the client

set_api_key(key)strRequired

Sets 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.0

HTTP timeout in seconds for every request except llm_context(), which always uses 10 seconds.

timeout.py
import xfinlink as xfl

xfl.set_timeout(0.001)
try:
    xfl.status()
except xfl.XfinlinkError as e:
    print(e)
stdout
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.

cache.py
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()")
stdout
first call: 1.775s
same call again: 0.003s
after clear_cache(): 0.527s

The 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: None

Keyword-only alias for ticker, used when ticker is not given.

entity_idint | list[int] | strDefault: None

Keyword-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 function

A 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: None

Shorthand 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: None

Joined 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).

signature.py
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.DataFrame

Parameters

tickerstr | list[str] | tupleRequired unless tickers or entity_id is given

Up 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: None

Keyword-only alias for ticker.

entity_idint | list[int] | strDefault: None

Keyword-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: None

Last date, inclusive. Omitted: not sent, and the API returns through the latest bar.

periodstrDefault: None

Shorthand 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: None

Subset 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: None

Maximum rows in total. Also the page size, up to 5,000.

max_rowsintDefault: 50000

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

prices.py
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))
stdout
  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":

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

prices_entity.py
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"]])
stdout
   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.63

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

signature.py
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.DataFrame

Parameters

tickerstr | list[str] | tupleRequired unless tickers or entity_id is given

Same caps and batching as prices().

tickersstr | list[str] | tupleDefault: None

Keyword-only alias for ticker.

entity_idint | list[int] | strDefault: None

Keyword-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: None

Latest period_end, inclusive. Omitted: not sent.

periodstrDefault: None

Shorthand 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: None

Return the figures knowable on that date. Omitted: not sent.

fieldsstr | list[str]Default: None

Subset of fundamentals fields. Structural columns are always returned.

fiscal_yearintDefault: None

Exact fiscal year. Omitted: not sent.

period_endstr | dateDefault: None

Exact period_end. Omitted: not sent.

limitintDefault: None

Maximum rows in total; page size up to 5,000.

max_rowsintDefault: 10000

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

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

signature.py
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.DataFrame

Parameters

tickerstr | list[str] | tupleRequired unless tickers or entity_id is given

Same caps and batching as prices().

tickersstr | list[str] | tupleDefault: None

Keyword-only alias for ticker.

entity_idint | list[int] | strDefault: None

Keyword-only issuer ids.

period_typestrDefault: "annual"

"annual", "quarterly", "ttm" or "daily", case-insensitive, always sent. Other values raise XfinlinkError locally.

fieldsstr | list[str]Default: None

Metric 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: None

Omitted: not sent.

periodstrDefault: None

Shorthand for start.

growth_basisstrDefault: None

"yoy", "qoq" or "ttm_yoy", case-insensitive. Omitted: not sent, and the API uses yoy.

as_ofstr | dateDefault: None

Metrics knowable on that date. Omitted: not sent.

limitintDefault: None

Maximum rows in total; page size up to 100.

max_rowsintDefault: 10000

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

metrics.py
import xfinlink as xfl

df = xfl.metrics("AAPL", period_type="ttm", fields=["pe_ratio", "roe", "market_cap"])
print(df.T)
stdout
                                 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.9726

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

signature.py
xfl.resolve(ticker, include=None, date_=None) -> dict

Parameters

tickerstr | list[str] | tupleRequired

Up to 10 tickers per call on every plan. Not batched.

includelist[str]Default: None

Sections to return: "events", "index", "classifications" or "all", joined with commas. Omitted: not sent, and every section is returned.

date_str | dateDefault: None

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

resolve.py
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)
stdout
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']
True

Resolved as of a past date, with one section:

resolve_dated.py
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"])
stdout
[(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']

Find entities by name, ticker, sector, type or industry code. Calls GET /v1/search.

signature.py
xfl.search(q=None, gics_sector=None, entity_type=None, sic=None, naics=None,
           country="US", limit=50, offset=0) -> pd.DataFrame

Parameters

qstrDefault: None

Match on company name or ticker. Omitted or empty: not sent.

gics_sectorstrDefault: None

Exact GICS sector, for example "Information Technology".

entity_typestrDefault: None

For example "corporation" or "etf".

sicstrDefault: None

Exact SIC code.

naicsstrDefault: None

Exact NAICS code.

countrystrDefault: "US"

Sent on every call unless you pass an empty value.

limitintDefault: 50

Rows per call, at most 500. Always sent. A larger value raises XfinlinkError (HTTP 400).

offsetintDefault: 0

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

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

signature.py
xfl.index(index_name="sp500", as_of=None, limit=1000, offset=0) -> pd.DataFrame

Parameters

index_namestrDefault: "sp500"

"sp500", "ndx100" or "djia", placed in the URL path. Other names raise XfinlinkError (HTTP 400).

as_ofstr | dateDefault: None

Membership on that date. Omitted: not sent, and the API returns the current roster.

limitintDefault: 1000

Rows per call, at most 1,000. Always sent.

offsetintDefault: 0

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

index.py
import xfinlink as xfl

df = xfl.index("djia")
print(len(df))
print(df[["ticker", "entity_name", "added_date", "removed_date", "member_grain"]].head(3))
stdout
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     security

Index events: index_events()

Index additions and removals as a dated event log. Calls GET /v1/index/{index_name}/events.

signature.py
xfl.index_events(index_name="sp500", start=None, end=None, event_type=None,
                 limit=1000, offset=0) -> pd.DataFrame

Parameters

index_namestrDefault: "sp500"

"sp500", "ndx100" or "djia".

startstr | dateDefault: None

Earliest effective_date. Omitted: not sent; the API returns from the start of its record (on Free, from one year back).

endstr | dateDefault: None

Latest effective_date. Omitted: not sent.

event_typestrDefault: None

"added" or "removed". Other values raise XfinlinkError locally. Omitted: both.

limitintDefault: 1000

Rows per call, at most 1,000. Always sent.

offsetintDefault: 0

Rows to skip. Always sent.

Returns

A DataFrame of the rows unchanged, oldest first. The result is cached.

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

signature.py
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.DataFrame

Parameters

tickerstr | list[str] | tupleRequired unless tickers or entity_id is given

Up to 100 per request on Pro.

tickersstr | list[str] | tupleDefault: None

Keyword-only alias for ticker.

entity_idint | list[int] | strDefault: None

Keyword-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: None

Latest transaction_date. Omitted: not sent.

periodstrDefault: None

Shorthand for start.

transaction_typestr | list[str]Default: None

For 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: None

Case-insensitive substring of the role, for example "CEO".

insider_namestrDefault: None

Case-insensitive substring of the insider's name.

min_valuefloatDefault: None

Minimum transaction_value, in USD.

include_amendmentsbool | NoneDefault: False

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

True sends include_derivatives=true and adds derivative-security lines, flagged by is_derivative. False sends nothing.

include_uncodedboolDefault: False

True sends include_uncoded=true and adds historical rows with no SEC transaction code. False sends nothing.

fieldsstr | list[str]Default: None

Subset of insider fields.

limitintDefault: None

Maximum rows; page size up to 5,000.

max_rowsintDefault: 10000

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

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

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

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

signature.py
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.DataFrame

Parameters

tickerstr | list[str] | tupleRequired unless tickers or entity_id is given

Up to 100 per request on Pro.

tickersstr | list[str] | tupleDefault: None

Keyword-only alias for ticker.

entity_idint | list[int] | strDefault: None

Keyword-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: None

Latest report_date. Omitted: not sent.

periodstrDefault: None

Shorthand for start.

quarterstr | dateDefault: None

Exact quarter-end report_date, for example "2026-06-30".

managerstrDefault: None

Case-insensitive substring of manager_name.

manager_idintDefault: None

Exact 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_sharesfloatDefault: None

Minimum share count.

min_valuefloatDefault: None

Minimum 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: True

True 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: None

Subset of holdings fields. value_scale_corrected is returned only when named.

limitintDefault: None

Maximum rows; page size up to 5,000.

max_rowsintDefault: 10000

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

holdings.py
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])
stdout
                      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          True

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

signature.py
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.DataFrame

Parameters

manager_idintRequired; the None default raises XfinlinkError

Manager 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: None

Latest report_date.

periodstrDefault: None

Shorthand for start.

quarterstr | dateDefault: None

Exact quarter-end report_date.

min_sharesfloatDefault: None

Minimum share count.

min_valuefloatDefault: None

Minimum value_usd, in USD.

fieldsstr | list[str]Default: None

Subset of holdings fields.

limitintDefault: None

Maximum rows; page size up to 5,000.

max_rowsintDefault: 10000

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

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

signature.py
xfl.managers(search=None, limit=1000) -> pd.DataFrame

Parameters

limitintDefault: 1000

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

managers.py
import xfinlink as xfl

df = xfl.managers("berkshire hathaway")
print(df)
stdout
   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    filing

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

signature.py
xfl.changes(since=None, dataset=None, limit=1000) -> dict

Parameters

sincestrDefault: None

A 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: None

Dataset names, as "prices", ["prices", "fundamentals"] or "prices,fundamentals". None reads every dataset.

limitintDefault: 1000

Records 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).

changes.py
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)
stdout
0 False
MTc5MDkyMDE0MDQzMHww

Status: 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.

status.py
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")
stdout
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 datasets

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

signature.py
xfl.usage() -> dict
usage.py
import xfinlink as xfl

u = xfl.usage()
print(u["tier"], u["requests_today"], "of", u["daily_limit"], "requests today, reset at", u["resets_at"])
fieldtypeunitnullabledescription
requests_todayintrequestsnoRequests counted against the daily limit since 00:00 UTC, across every key on the account.
daily_limitintrequestsnoRequests per UTC day on your plan: 100 on Free, 10,000 on Pro, 50,000 on Max and 500,000 on Redistribution.
resets_atstrUTC timestampnoWhen the daily count resets: the next 00:00 UTC, such as 2026-10-01T00:00:00.000Z.
burst_last_hourintrequestsnoRequests counted in the current UTC clock hour.
burst_limitintrequestsnoRequests per UTC clock hour: 40 on Free. Pro, Max and Redistribution have no hourly limit and report 999999.
tierstr—noYour 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.

llm_context.py
import xfinlink as xfl

text = xfl.llm_context()
print(type(text).__name__)
stdout
# 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

str

Clear 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(): follows next_cursor and returns all rows, up to limit, max_rows (default 50,000) or 50 pages, with up to 5,000 rows per page.
  • fundamentals(): follows next_cursor and returns all rows, up to limit, max_rows (default 10,000) or 50 pages, with up to 5,000 rows per page.
  • metrics(): follows next_cursor and returns all rows, up to limit, 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 to limit rows, at most 5,000.
  • search(): one request, up to limit rows, at most 500; page with offset. meta.total in the REST response counts every match.
  • index(): one request, up to limit rows, at most 1,000; page with offset.
  • index_events(): one request, up to limit rows, at most 1,000; page with offset.
  • 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.

paging.py
import xfinlink as xfl

first = xfl.index("sp500", limit=100)
rest = xfl.index("sp500", limit=1000, offset=100)
print(len(first), len(rest))
output
...: UserWarning: xfinlink: index('sp500') returned 100 of 503 rows (limit=100, offset=0). Fix: page with offset=100 to get the rest.
100 403

When max_rows cuts a longer result and limit is not set lower, the client warns:

max_rows.py
import xfinlink as xfl

df = xfl.prices("AAPL", start="2026-01-01", max_rows=10)
print(len(df))
output
...: 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.
10

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

error_type.py
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"))
stdout
['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
statuserror codewhen it happenshow to fix
nonelocal validationA 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.
400bad_requestThe API rejected a parameter, for example search(limit=1000) or a ticker list on Free. Message: API error (400): {detail}.Change the parameter.
401unauthorizedThe 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.
402upgrade_requiredA Free key called insiders(), holdings(), manager_holdings() or managers(). Message: API error (402): {detail}.Use a Pro key or higher.
404not_foundNo entity matches the ticker or entity_id.Check the symbol with search().
409ambiguous_tickerThe ticker names more than one security on the date. The candidate list is not included in the message.Call resolve() and pass entity_id.
429rate limitA 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.
5xxserver errorThe API failed after the retries described below. Message: Server error ({status}). This is temporary.Retry later.
nonetimeoutNo response within the timeout, after retries.Narrow the request or raise set_timeout().
noneconnectionThe host cannot be reached, after retries.Check the network and set_api_base().

Real messages for the most common cases:

errors.py
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}")
stdout
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:

errors_402.py
import xfinlink as xfl

try:
    xfl.insiders("AAPL")
except xfl.XfinlinkError as e:
    print(e)
stdout
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.

retry.py
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")
stdout
http://localhost:9
xfinlink: Cannot connect to api.xfinlink.com. Fix: check your internet connection.
gave up after 3.1s

The 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, insiders or holdings returned no rows. The function returns an empty DataFrame.
  • No holdings returned for manager_id {id}: manager_holdings returned no rows.
  • No managers matched '{search}': managers returned no rows.
  • Ticker '{t}' exists but has no entity data: resolve of a single ticker returned an empty entities list.
  • Results truncated at max_rows ({n} rows): max_rows cut 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: index or index_events returned 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.

batches.py
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")
output
...: 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 returned

The 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, metrics and index_events cover the last 12 months. The API moves an earlier start to the date one year before today and answers normally, so the client raises no error or warning; over REST, the X-Data-Floor header carries that date. Pro and higher plans get full history.
  • insiders, holdings, manager_holdings and managers: raise XfinlinkError (HTTP 402) on Free, and are available on Pro and higher plans.

The same call on Free:

free_plan.py
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)
stdout
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: calls status() and prints the JSON. No arguments.
  • xfinlink resolve TICKER: calls resolve(TICKER) and prints the data object as JSON. TICKER is required; there is no include or date option.
  • xfinlink search QUERY: calls search(q=QUERY, limit=INT) and prints a table. QUERY is required; --limit INT defaults to 10.
  • xfinlink changes calls changes() and prints the JSON. --since CURSOR and --dataset NAME,... are optional.
  • xfinlink --help: help and shell completion, with --install-completion, --show-completion and --help. Each command also accepts --help.
Terminal
xfinlink --help
stdout
 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).                                     │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
Terminal
xfinlink version
stdout
0.13.1
Terminal
xfinlink status
stdout
{
  "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
    },
    ...
  ]
}
Terminal
xfinlink resolve AAPL
stdout
{
  "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
          },
          ...
        ]
      }
    ]
  }
}
Terminal
xfinlink search "general motors" --limit 2
stdout
┏━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━┳━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ 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      │
└───────────┴────────┴─────────────────────┴─────────────┴────────────────────────┴──────────────────────────┴──────┴────────┴─────────┴─────────────────┘
Terminal
xfinlink resolve ZZZZZZ
stdout
xfinlink: 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.

request.pycurlprompt
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.
stdoutresponse.jsontool result
  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?

request.pycurlprompt
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.
stdoutresponse.jsontool result
  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"}
    ]
  }
}