Get startedDashboard

Recipes

Paid plans

Run complete Python scripts for recycled tickers, point-in-time statements, past index rosters, backtests, bulk downloads, succession links, ownership joins and incremental refresh.

View llms.txt

Each recipe is one complete script. Install the client with pip install -U xfinlink, replace YOUR_API_KEY with your key, and run the file. The output under each script is the output it printed on 2026-09-27, or on 2026-10-02 for Backtest on past index members and Pull many tickers within your limits.

The recipes use Pro. The last one, Refresh only what changed, also runs on Free. The others need Pro or higher because they send more than one ticker or entity_id per request, ask for dates more than one year back, or call insider and holdings endpoints, which return 402 on Free. See Plans and limits.

Two kinds of entity_id

On prices, resolve, search, index and holdings an entity_id names one share class. On fundamentals, metrics and insiders it names the issuer. See Tickers and entities.

Follow a recycled ticker

  1. 1. Import the client and set your key

    Lines 1–3
  2. 2. Resolve FB on each date

    date_ asks resolve for the security that carried the ticker on that date. In 2020 that is Meta Platforms' Class A common stock. In September 2025 it is a ProShares exchange-traded fund.

    Lines 5–6
  3. 3. Print the holder

    instrument_type tells the two apart: common for the stock, etf for the fund.

    Line 7
  4. 4. Pull prices for each holder

    Passing the holder's entity_id pins the series to that share class, whatever the ticker names today. close is in USD per share, as traded. The row ticker is the symbol in force on each date.

    Lines 8–9
recycled_ticker.py
import xfinlink as xflxfl.set_api_key("YOUR_API_KEY")for start, end in [("2020-01-02", "2020-01-08"), ("2025-09-02", "2025-09-08")]:    holder = xfl.resolve("FB", date_=start)["data"]["FB"]["entities"][0]    print(start, holder["entity_id"], holder["name"], holder["instrument_type"])    px = xfl.prices(entity_id=holder["entity_id"], start=start, end=end, fields=["close"])    print(px[["entity_id", "ticker", "date", "close"]].to_string(index=False))
stdout
2020-01-02 10002684 META PLATFORMS INC common
 entity_id ticker       date     close
  10002684     FB 2020-01-02 209.78000
  10002684     FB 2020-01-03 208.67000
  10002684     FB 2020-01-06 212.60001
  10002684     FB 2020-01-07 213.06000
  10002684     FB 2020-01-08 215.22000
2025-09-02 10039593 PROSHARES TRUST etf
 entity_id ticker       date   close
  10039593     FB 2025-09-02 40.9411
  10039593     FB 2025-09-03 40.9500
  10039593     FB 2025-09-04 41.0600
  10039593     FB 2025-09-05 41.1500
  10039593     FB 2025-09-08 41.2000
  1. 1. Import the client and set your key

    Lines 1–3
  2. 2. Read the succession link

    include=["events"] adds succession to each entity. RTX lists Raytheon Co as a predecessor through a merger dated 2020-04-02. The link's entity_id is an issuer id.

    Lines 5–7
  3. 3. Prices for the predecessor

    Prices accepts the issuer id and serves that issuer's listed share class, so the rows carry the share-class id 10012171 and the ticker RTN. close is in USD per share, as traded. The series ends on the merger date.

    Lines 9–10
  4. 4. Statements for the predecessor

    Fundamentals are keyed by issuer, so the link's entity_id works as it is. revenue and net_income are in millions of USD.

    Lines 12–13
predecessor.py
import xfinlink as xflxfl.set_api_key("YOUR_API_KEY")rtx = xfl.resolve("RTX", include=["events"])["data"]["RTX"]["entities"][0]link = rtx["succession"]["predecessors"][0]print(rtx["entity_id"], rtx["name"], "<-", link)px = xfl.prices(entity_id=link["entity_id"], start="2020-03-27", end="2020-04-10", fields=["close"])print(px[["entity_id", "ticker", "date", "close"]].to_string(index=False))fin = xfl.fundamentals(entity_id=link["entity_id"], period_type="annual", start="2017-01-01", fields=["revenue", "net_income"])print(fin[["entity_id", "entity_name", "period_end", "revenue", "net_income"]].to_string(index=False))
stdout
10006181 R T X CORP <- {'entity_id': 6172, 'name': 'RAYTHEON CO', 'event_type': 'merger', 'event_date': '2020-04-02'}
 entity_id ticker       date     close
  10012171    RTN 2020-03-27 142.02000
  10012171    RTN 2020-03-30 141.21001
  10012171    RTN 2020-03-31 131.14999
  10012171    RTN 2020-04-01 122.43000
  10012171    RTN 2020-04-02 116.96000
 entity_id entity_name period_end  revenue  net_income
      6172 RAYTHEON CO 2017-12-31    25348        2024
      6172 RAYTHEON CO 2018-12-31    27058        2909
      6172 RAYTHEON CO 2019-12-31    29176        3343

Compare two share classes

  1. 1. Import the client and set your key

    Lines 1–3
  2. 2. Prices for both classes

    GOOGL and GOOG are separate securities, each with its own entity_id and its own close, in USD per share. One request carries both tickers, which needs Pro.

    Lines 5–7
  3. 3. Statements for their issuer

    Both tickers resolve to the same issuer, entity_id 31223, and return the same rows. The ticker column repeats the ticker you asked for. revenue and net_income are in millions of USD.

    Lines 9–11
share_classes.py
import xfinlink as xflxfl.set_api_key("YOUR_API_KEY")px = xfl.prices(["GOOGL", "GOOG"], start="2026-09-21", end="2026-09-25", fields=["close"])print(px.pivot(index="date", columns="ticker", values="close"))print(px.groupby("ticker")["entity_id"].first().to_dict())for ticker in ["GOOGL", "GOOG"]:    fin = xfl.fundamentals(ticker, period_type="annual", start="2024-01-01", fields=["revenue", "net_income"])    print(fin[["entity_id", "ticker", "entity_name", "period_end", "revenue", "net_income"]].to_string(index=False))
stdout
ticker        GOOG   GOOGL
date                      
2026-09-21  350.87  354.97
2026-09-22  347.41  351.16
2026-09-23  334.98  337.83
2026-09-24  339.01  342.36
2026-09-25  341.08  343.92
{'GOOG': 10003590, 'GOOGL': 10030636}
 entity_id ticker  entity_name period_end  revenue  net_income
     31223  GOOGL ALPHABET INC 2024-12-31   350018      100118
     31223  GOOGL ALPHABET INC 2025-12-31   402836      132170
 entity_id ticker  entity_name period_end  revenue  net_income
     31223   GOOG ALPHABET INC 2024-12-31   350018      100118
     31223   GOOG ALPHABET INC 2025-12-31   402836      132170

Rebuild a past S&P 500 roster

  1. 1. Import pandas and the client, set your key

    Lines 1–4
  2. 2. Roster on 2008-09-12

    as_of returns the members on that date, including companies that later left the index or stopped trading. Each entity_id names a share class.

    Lines 6–7
  3. 3. Prices for every member

    Pro accepts up to 100 entity_id values per request, so the ids go in chunks of 100. With interval="1y" each member gets one row for the window, and its return_daily is the total return compounded from the close on 2008-09-12 to the member's last close on or before 2008-10-10. Returns are decimal fractions: -0.3137 is a loss of 31.37%.

    Lines 8–11
  4. 4. Join prices to the roster

    Both tables carry the same share-class entity_id.

    Line 12
  5. 5. Compare all members with the stayers

    removed_date is empty for members that have never left the index. Keeping only those members moves the equal-weighted return from -0.3137 to -0.2897, a ratio, not a percentage. The worst performer, Lehman Brothers, left the index on 2008-09-16, so a list built from today's members would not contain it.

    Lines 14–18
past_roster.py
import pandas as pdimport xfinlink as xflxfl.set_api_key("YOUR_API_KEY")roster = xfl.index("sp500", as_of="2008-09-12")ids = roster["entity_id"].tolist()moves = pd.concat([    xfl.prices(entity_id=ids[i:i + 100], start="2008-09-15", end="2008-10-10", interval="1y", fields=["return_daily"])    for i in range(0, len(ids), 100)])df = roster.merge(moves[["entity_id", "return_daily"]], on="entity_id", how="left")stayed = df["removed_date"].isna()print(len(df), "members on 2008-09-12;", stayed.sum(), "have never left the index since")print("equal-weighted return, all members:  ", round(df["return_daily"].mean(), 4))print("equal-weighted return, stayers only: ", round(df.loc[stayed, "return_daily"].mean(), 4))print(df.nsmallest(5, "return_daily")[["entity_id", "ticker", "entity_name", "removed_date", "return_daily"]].to_string(index=False))
stdout
498 members on 2008-09-12; 248 have never left the index since
equal-weighted return, all members:   -0.3137
equal-weighted return, stayers only:  -0.2897
 entity_id ticker                      entity_name removed_date  return_daily
  10023380    LEH     LEHMAN BROTHERS HOLDINGS INC   2008-09-16     -0.964384
  10024136     WM              MR COOPER GROUP INC   2008-09-29     -0.941246
  10022022    GGP     BROOKFIELD PROPERTY REIT INC   2008-11-12     -0.825045
  10018030    AIG AMERICAN INTERNATIONAL GROUP INC          NaN     -0.808073
  10030487    GNW           GENWORTH FINANCIAL INC   2015-11-17     -0.763438

Backtest on past index members

This recipe takes the S&P 500 members on each rebalance date and holds them for the following month, identifying each member by entity_id.

  1. 1. Import pandas and the client, set your key

    Lines 1–4
  2. 2. Choose the rebalance dates

    Each tuple is a rebalance date followed by the first and last day of the month held after it.

    Lines 6–7
  3. 3. Members on the rebalance date

    as_of returns the members on that date, including companies that later left the index or stopped trading. Each entity_id names one share class, so a recycled ticker cannot pull in another company: WM is Waste Management's ticker today, and on 2008-08-29 it was Washington Mutual's, whose security carries its later name, MR COOPER GROUP INC.

    Lines 10–11
  4. 4. One monthly return per member

    Pro accepts up to 100 entity_id values per request, so the ids go in chunks of 100. With interval="1mo" each member gets one row for the month, and its return_daily is the total return compounded from the last close before the month to the member's last close in the month. Returns are decimal ratios: -0.1091 is a loss of 10.91%.

    Lines 12–16
  5. 5. Score the portfolio

    Members join their returns on entity_id. Every member held in September 2008 has a return, including Lehman Brothers, which stopped trading on 2008-09-17.

    Lines 17–19
  6. 6. Members that left

    Lehman Brothers and Washington Mutual left the index in September 2008, so the October portfolio holds neither. Their September returns run to their last close. meta.delisting on the REST prices response gives the return from that close to the exit, -0.6 for Lehman Brothers; see Data coverage and boundaries.

    Lines 20–21
pit_backtest.py
import pandas as pdimport xfinlink as xflxfl.set_api_key("YOUR_API_KEY")months = [("2008-08-29", "2008-09-01", "2008-09-30"),          ("2008-09-30", "2008-10-01", "2008-10-31")]for as_of, start, end in months:    members = xfl.index("sp500", as_of=as_of)    ids = members["entity_id"].tolist()    px = pd.concat([        xfl.prices(entity_id=ids[i:i + 100], start=start, end=end,                   interval="1mo", fields=["return_daily"])        for i in range(0, len(ids), 100)    ])    held = members.merge(px[["entity_id", "return_daily"]], on="entity_id", how="left")    print(f"held from {as_of}: {len(held)} members, {held['return_daily'].notna().sum()} with a return, "          f"equal-weighted {held['return_daily'].mean():.4f}")    print(held[held["ticker"].isin(["LEH", "WM", "WB"])]          [["entity_id", "ticker", "entity_name", "removed_date", "return_daily"]].to_string(index=False))
stdout
held from 2008-08-29: 500 members, 500 with a return, equal-weighted -0.1091
 entity_id ticker                  entity_name removed_date  return_daily
  10023380    LEH LEHMAN BROTHERS HOLDINGS INC   2008-09-16     -0.991920
  10024136     WM          MR COOPER GROUP INC   2008-09-29     -0.960395
  10014467     WB        WACHOVIA CORP 2ND NEW   2008-12-31     -0.779735
held from 2008-09-30: 499 members, 499 with a return, equal-weighted -0.2091
 entity_id ticker           entity_name removed_date  return_daily
  10014467     WB WACHOVIA CORP 2ND NEW   2008-12-31      0.831425

Read statements as of a date

  1. 1. Import the client and set your key

    Lines 1–3
  2. 2. Same query at two knowledge dates

    as_of returns each period as the filings made on or before that date reported it. Without as_of you get the latest view. start and end bound period_end to the fiscal year ending 2020-12-31. See Point-in-time and as-of.

    Lines 5–7
  3. 3. Print both versions

    On 2021-04-01 the only filing for fiscal 2020 was the original annual report of 2021-03-01, with net_income of -273.035. The amendment filed on 2021-05-10 reports -644.887. Values are in millions of USD, so revenue of 0.238 is USD 238,000.

    Lines 9–13
as_of.py
import xfinlink as xflxfl.set_api_key("YOUR_API_KEY")query = dict(period_type="annual", start="2020-12-31", end="2020-12-31", fields=["revenue", "net_income"])known = xfl.fundamentals("SPCE", as_of="2021-04-01", **query)latest = xfl.fundamentals("SPCE", **query)cols = ["entity_id", "period_end", "filing_date", "version", "revenue", "net_income"]print("Known on 2021-04-01:")print(known[cols].to_string(index=False))print("Latest:")print(latest[cols].to_string(index=False))
stdout
Known on 2021-04-01:
 entity_id period_end filing_date  version  revenue  net_income
     18383 2020-12-31  2021-03-01 original    0.238    -273.035
Latest:
 entity_id period_end filing_date   version  revenue  net_income
     18383 2020-12-31  2021-05-10 amendment    0.238    -644.887

List every filed version

  1. 1. Import the client and set your key

    Lines 1–3
  2. 2. Ask for every vintage of one period

    view="as_reported" returns one row per filing that reported the period. start and end bound period_end to Microsoft's fiscal year ending 2017-06-30.

    Lines 5–12
  3. 3. Print the vintages

    The filing of 2017-08-02 reported revenue of 89,950. The filings of 2018-08-03 and 2019-08-01 report the same year at 96,571, marked restated. Values are in millions of USD.

    Line 13
vintages.py
import xfinlink as xflxfl.set_api_key("YOUR_API_KEY")df = xfl.fundamentals(    "MSFT",    view="as_reported",    period_type="annual",    start="2017-06-30",    end="2017-06-30",    fields=["revenue", "net_income"],)print(df[["period_end", "fiscal_year", "filing_date", "version", "revenue", "net_income"]].to_string(index=False))
stdout
period_end  fiscal_year filing_date  version  revenue  net_income
2017-06-30         2017  2017-08-02 original    89950       21204
2017-06-30         2017  2018-08-03 restated    96571       25489
2017-06-30         2017  2019-08-01 restated    96571       25489

Join insider trades to holdings

  1. 1. Import the client and set your key

    Lines 1–3
  2. 2. Form 4 purchases by Berkshire

    insider_name matches any reporting owner on the filing, so filings that list Berkshire Hathaway as a co-owner are included. include_amendments=None asks for the version in force of each filing. Insider rows carry the issuer in entity_id.

    Lines 5–6
  3. 3. Berkshire's Form 13F position

    manager is a case-insensitive match on part of the manager name. Holdings rows carry the share class in entity_id and the issuer in issuer_entity_id. shares is the reported share count and value_usd is in whole USD. Both endpoints need Pro.

    Lines 7–8
  4. 4. Sum the purchases by quarter

    Each purchase is assigned to its calendar quarter end. form4_bought is in shares. form4_held_after is the largest shares_held_after in the quarter, the holding after the last purchase.

    Lines 10–12
  5. 5. Join on the issuer and quarter

    The 13F shares at each quarter end equals the holding after the last Form 4 purchase in that quarter. value_usd_bn is value_usd in billions of USD. Quarters without purchases show <NA>.

    Lines 14–19
insiders_vs_holdings.py
import xfinlink as xflxfl.set_api_key("YOUR_API_KEY")buys = xfl.insiders("OXY", start="2023-01-01", end="2025-12-31", insider_name="berkshire",                    transaction_type="open_market_buy", include_amendments=None)held = xfl.holdings("OXY", start="2023-01-01", end="2025-12-31", manager="berkshire hathaway inc",                    include_amendments=None)buys["report_date"] = buys["transaction_date"].dt.to_period("Q").dt.end_time.dt.normalize()form4 = buys.groupby(["entity_id", "report_date"]).agg(    form4_bought=("shares", "sum"), form4_held_after=("shares_held_after", "max")).reset_index()joined = held.merge(form4, left_on=["issuer_entity_id", "report_date"],                    right_on=["entity_id", "report_date"], how="left", suffixes=("", "_issuer"))joined["value_usd_bn"] = (joined["value_usd"] / 1e9).round(2)cols = ["issuer_entity_id", "report_date", "shares", "value_usd_bn", "form4_bought", "form4_held_after"]print(joined[cols].astype({"form4_bought": "Int64", "form4_held_after": "Int64"})      .sort_values("report_date").to_string(index=False))
stdout
 issuer_entity_id report_date    shares  value_usd_bn  form4_bought  form4_held_after
             5451  2023-03-31 211707119         13.22      17355469         211707119
             5451  2023-06-30 224129192         13.18      12422073         224129192
             5451  2023-09-30 224129192         14.54          <NA>              <NA>
             5451  2023-12-31 243715804         14.55      19586612         243715804
             5451  2024-03-31 248018128         16.12       4302324         248018128
             5451  2024-06-30 255281524         16.09       7263396         255281524
             5451  2024-09-30 255281524         13.16          <NA>              <NA>
             5451  2024-12-31 264178414         13.05       8896890         264178414
             5451  2025-03-31 264941431         13.08        763017         264941431
             5451  2025-06-30 264941431         11.13          <NA>              <NA>
             5451  2025-09-30 264941431         12.52          <NA>              <NA>
             5451  2025-12-31 264941431         10.89          <NA>              <NA>

Track a manager's portfolio

  1. 1. Import pandas and the client, set your key

    Lines 1–4
  2. 2. Find the manager's ids

    managers searches Form 13F filers from 2013 onward and returns the filing-era id. Quarters on or before meta.archive_edge, 2025-12-31 on 2026-09-27, are served under a historical-era id instead. Line 7 reads it from a holdings row, which carries it in manager_id.

    Lines 6–8
  3. 3. Download both eras

    manager_holdings returns one row per position and quarter. Each quarter comes from one era only, so nothing is counted twice.

    Lines 10–13
  4. 4. Summarise each year end

    Line 14 keeps every December quarter and the latest quarter. value_usd is in whole USD, shown here in billions of USD. In the historical era it is shares times the quarter-end price. In the filing era it is the value the filer reported. Compare values within one era.

    Lines 14–19
  5. 5. Name the largest position

    ticker is the symbol of each position's security on its report date.

    Lines 20–21
manager_history.py
import pandas as pdimport xfinlink as xflxfl.set_api_key("YOUR_API_KEY")filing_id = xfl.managers("berkshire hathaway")["manager_id"].iloc[0]historical_id = xfl.holdings("KO", quarter="2025-12-31", manager="berkshire hathaway inc")["manager_id"].iloc[0]print("filing-era id:", filing_id, "historical-era id:", historical_id)book = pd.concat([    xfl.manager_holdings(historical_id, start="2016-01-01"),    xfl.manager_holdings(filing_id, start="2016-01-01"),], ignore_index=True)snap = book[book["report_date"].dt.month.eq(12) | book["report_date"].eq(book["report_date"].max())]summary = snap.groupby("report_date").agg(    source=("source", "first"),    positions=("shares", "size"),    value_usd_bn=("value_usd", lambda v: round(v.sum() / 1e9, 1)),)summary["largest"] = snap.loc[snap.groupby("report_date")["value_usd"].idxmax()].set_index("report_date")["ticker"]print(summary.to_string())
stdout
filing-era id: 1067983 historical-era id: 900008350
                 source  positions  value_usd_bn largest
report_date                                             
2016-12-31   historical         45         147.1     KHC
2017-12-31   historical         48         152.7    AAPL
2018-12-31   historical         48         183.1    AAPL
2019-12-31   historical         52         242.1    AAPL
2020-12-31   historical         47         269.9    AAPL
2021-12-31   historical         44         331.0    AAPL
2022-12-31   historical         49         299.0    AAPL
2023-12-31   historical         41         347.4    AAPL
2024-12-31   historical         38         267.2    AAPL
2025-12-31   historical         42         274.2    AAPL
2026-06-30       filing         29         299.3    AAPL

Pull many tickers within your limits

Every page of a response counts each ticker or entity_id in the request, and a page of /v1/prices holds up to 5,000 rows. This recipe reads a year of daily closes for every Nasdaq-100 member in requests sized to fit one page each.

  1. 1. Import pandas and the client, set your key

    Lines 1–4
  2. 2. List the securities

    xfl.index("ndx100") returns today's Nasdaq-100 members, one share-class entity_id per row, and dropna() keeps the rows that carry one. Any list of ids works the same way, such as one built with Search.

    Line 6
  3. 3. Size each request to one page

    A year holds about 252 trading days, so 19 securities fit in one page of 5,000 rows. A request that runs to a second page counts every id again.

    Lines 7–8
  4. 4. Fetch in batches

    Each call sends up to 19 ids and returns one page. The plan caps, 100 ids per request on Pro, 500 on Max and 5,000 on Redistribution, are upper limits; for daily rows the page size sets the cheapest batch. close is in USD per share, as traded.

    Lines 10–12
  5. 5. Check the result

    The run counts 100 requests against your daily quota: 1 for the roster and 1 for each of the 99 ids. The 24,347 price rows count toward the row allowances, 1,000,000 per clock hour and 5,000,000 per day on Pro. See Rate limits.

    Lines 14–16
many_tickers.py
import pandas as pdimport xfinlink as xflxfl.set_api_key("YOUR_API_KEY")ids = xfl.index("ndx100")["entity_id"].dropna().astype(int).tolist()start, end = "2025-10-01", "2026-09-30"batch = 5000 // 252frames = [xfl.prices(entity_id=ids[i:i + batch], start=start, end=end, fields=["close"])          for i in range(0, len(ids), batch)]px = pd.concat(frames, ignore_index=True)print(len(ids), "securities,", len(frames), "requests of up to", batch, "ids,", len(px), "rows")closes = px.pivot(index="date", columns="ticker", values="close")print(closes.iloc[-3:, :5].to_string())
stdout
99 securities, 6 requests of up to 19 ids, 24347 rows
ticker        AAPL    ABNB    ADBE     ADI     ADP
date                                              
2026-09-28  338.40  156.08  231.01  395.43  261.80
2026-09-29  329.40  156.96  233.17  398.22  261.80
2026-09-30  333.02  160.65  239.94  396.71  258.06

Page through a cursor

  1. 1. Set up the request

    httpx is installed with xfinlink. quarter selects one report date and limit sets the page size, up to 5,000 rows.

    Lines 1–5
  2. 2. Follow next_cursor to the end

    Pass each next_cursor back unchanged as cursor. On the last page next_cursor is null. Holdings and insiders return next_cursor at the top level of the body. Prices, fundamentals and metrics return it in meta.next_cursor, and xfl.prices, xfl.fundamentals and xfl.metrics follow it for you. See Pagination.

    Lines 7–15
  3. 3. Count the positions

    Each page counts as one request against your daily quota.

    Line 17
paging.py
import httpxurl = "https://api.xfinlink.com/v1/holdings/AAPL"headers = {"X-API-Key": "YOUR_API_KEY"}params = {"quarter": "2026-06-30", "limit": 1000}rows, pages = [], 0while True:    body = httpx.get(url, params=params, headers=headers, timeout=120).raise_for_status().json()    rows += body["data"]    pages += 1    print(f"page {pages}: {body['count']} rows, next_cursor={body['next_cursor'] is not None}")    if not body["next_cursor"]:        break    params["cursor"] = body["next_cursor"]print(len(rows), "positions in AAPL on", rows[0]["report_date"], "in", pages, "pages")
stdout
page 1: 1000 rows, next_cursor=True
page 2: 1000 rows, next_cursor=True
page 3: 1000 rows, next_cursor=True
page 4: 1000 rows, next_cursor=True
page 5: 1000 rows, next_cursor=True
page 6: 1000 rows, next_cursor=True
page 7: 473 rows, next_cursor=False
6473 positions in AAPL on 2026-06-30 in 7 pages

Refresh only what changed

  1. 1. Import the modules

    xfl.status() calls /v1/status, which needs no key.

    Lines 1–5
  2. 2. Load saved state and current freshness

    /v1/status lists every dataset with through, the last date it covers, and promoted_at, when that build went live, in UTC.

    Lines 7–10
  3. 3. Skip tickers whose inputs have not moved

    Every data response names the datasets it was built from in meta.freshness, with the same dataset, through and promoted_at fields as /v1/status. Only through and promoted_at are compared, and a ticker is downloaded again only when one of its datasets shows a new value.

    Lines 12–16
  4. 4. Download and remember the freshness

    The client's DataFrame functions do not return meta, so this recipe reads prices with httpx. The output shows two runs: the first downloads both tickers, the second finds nothing changed and sends no data request.

    Lines 17–21
  5. 5. Save the state

    This recipe also runs on Free. On Max and Redistribution, the change feed lists exactly what changed, by entity_id and date.

    Line 23
refresh.py
import jsonimport osimport httpximport xfinlink as xflSTATE = "freshness_state.json"headers = {"X-API-Key": "YOUR_API_KEY"}state = json.load(open(STATE)) if os.path.exists(STATE) else {}live = {f["dataset"]: f for f in xfl.status()["freshness"]}for ticker in ["AAPL", "MSFT"]:    saved = state.get(ticker)    if saved and all(live.get(f["dataset"], {}).get(k) == f[k] for f in saved["freshness"] for k in ("through", "promoted_at")):        print(ticker, "unchanged since last run: skipped")        continue    body = httpx.get(f"https://api.xfinlink.com/v1/prices/{ticker}", params={"start": "2026-09-01"},                     headers=headers, timeout=120).raise_for_status().json()    state[ticker] = {"freshness": body["meta"]["freshness"], "data": body["data"]}    print(ticker, "downloaded", len(body["data"]), "rows; depends on",          [(f["dataset"], f["through"]) for f in body["meta"]["freshness"]])json.dump(state, open(STATE, "w"), indent=2)
stdout
AAPL downloaded 18 rows; depends on [('prices', '2026-09-25'), ('price_adjustments', '2026-09-25'), ('shares_outstanding', '2026-09-23')]
MSFT downloaded 18 rows; depends on [('prices', '2026-09-25'), ('price_adjustments', '2026-09-25'), ('shares_outstanding', '2026-09-23')]
AAPL unchanged since last run: skipped
MSFT unchanged since last run: skipped

Was this page helpful?