Get startedDashboard

Changelog

Dated changes to the API, the MCP server and the Python client, newest first.

View llms.txt

This page lists changes that affect callers of the REST API, the MCP server and the Python client, newest first.

Each change is tagged Added, Changed, Fixed, Removed, Deprecated or Security. Breaking marks a change that requires callers to update their code.

Install the current client with pip install -U xfinlink. The current version is 0.13.1.

2026-10-11: Reporting currency, scale labels and request counting

  • Added Fundamentals rows carry currency, the three-letter code of the currency the row's money values are stated in, and field_currency, which names any requested value in another currency than its row's. Values are not converted. See Fundamentals fields.
  • Changed On /v1/fundamentals, meta.units reads millions_usd when every row on the page says USD and none carries field_currency, and millions_reporting_currency otherwise. A computed field is null where its inputs are in two currencies.
  • Added labels on filing rows carry money_scale_suspect, the money fields served as filed that the archive record carries about 1,000 or 1,000,000 times larger or smaller, and money_scale_withheld, the money fields left empty because another filing of the period and the archive record carry them at such a multiple. In the default view, a period with a field left empty is served by its archive row where that row reports revenue or net_income that the filing row lacks, and otherwise the fields filled_from_historical covers are filled from the archive row.
  • Changed eps_scale_suspect is set on filing and archive rows, where an EPS figure exceeds 100,000 per share in absolute value or is about 1,000 or 1,000,000 times another measure of the same figure: on a filing row, larger or smaller than the archive record's figure, or larger than the company's other filings of the period or the row's own net_income per weighted share; on an archive row, larger than the row's own income per basic share. share_scale_suspect compares a share count with the archive record's count for the period as well as with the count implied by net_income and EPS.
  • Added On /v1/metrics, a metric that would set statement figures in two currencies against each other, or a figure in a currency other than USD against a USD close or market cap, or that is a per-share figure or money amount computed from such a figure, is null, and missing_inputs names each such input as <input>:currency. See Metrics.
  • Changed A refused request does not count against the request quota and carries no quota headers: every 400, including too many tickers, an invalid parameter and a page past the plan's page limit, the 402 and the row-allowance 429. A 200, 404 or 409 counts one request per ticker sent. See Rate limits.

2026-10-10: Request checks, error bodies and share classes

  • Added Every error body from the data endpoints and /v1/status carries code, the error code to branch on, with error, status and detail. error keeps its value: a short sentence on the request-limit and Temporarily blocked 429 responses, on the page-limit 400 and on the 403, and the code on every other error. The 401, 403, page-limit 400 and those 429 bodies also carry message, and every 409 carries hint. See Errors.
  • Changed Dates are real calendar dates from 0001-01-01 to 9999-12-31. Fundamentals takes start and end from 0002-01-01 to 9999-11-30, and metrics takes start and end from 0005-01-01 to 9999-11-30 and as_of from 0005-01-01. A date outside these ranges, an end before start, a limit that is not a whole number from 1 to 999,999,999, a negative offset, an include_amendments, include_derivatives or include_uncoded value other than true or false, a holdings quarter that is not a calendar quarter end, a fundamentals period_end that is not a real date, and a metrics view or version other than latest and restated return 400 with a detail that names the parameter. These checks skip an empty value, such as limit=, and on the paid endpoints a Free key gets their 400 before the 402.
  • Added warnings names a parameter the endpoint does not read, with the parameter meant where the name is a common variant, and a Free request that reaches before the one-year floor.
  • Changed On /v1/metrics, a ticker that names more than one security on the date returns 409, as on /v1/prices and /v1/resolve. /v1/fundamentals, /v1/insiders and /v1/holdings serve the company when a ticker names several share classes of one company, with a warning, and return 409 when it names more than one company.
  • Changed On /v1/fundamentals, /v1/metrics and /v1/insiders, rows requested by an issuer's entity_id carry the ticker and entity_name of its primary listed class, chosen as described on Tickers and entities. Metrics requested by issuer id are priced on that class. On /v1/holdings, rows requested by issuer id or served at the company level carry each class's own ticker and the company's name.
  • Changed /v1/status answers methods other than GET and OPTIONS with 405, and every 405 carries an Allow: GET header.
  • Changed % and _ in search q, insider_name, insider_role, holdings manager and manager search search match those characters.
  • Changed /v1/holdings serves a request that carries security_class, which is not a filter, with a warning.
  • Added A comma-separated ticker list in the path may be percent-encoded, as in AAPL%2CMSFT.
  • Added MCP: get_prices takes interval, get_fundamentals takes view and as_of, get_metrics takes as_of, get_insiders takes include_derivatives and include_uncoded, and get_holdings takes source. resolve_ticker takes date and returns the same body as /v1/resolve with your key. A tool error carries the REST error body, code included. See MCP server.

2026-10-10: Institutional holdings amendments

  • Changed In the default view of /v1/holdings, /v1/managers/{manager_id}/holdings and the MCP get_holdings tool, a 13F amendment marked as adding new holdings entries adds them to the report it amends: a position stated in both is their sum, and lines counts the lines of both filings. Such an amendment replaces the positions it states instead when it repeats an entry already in force with the same quantity, has more than a quarter as many lines as the holdings in force, states more than a quarter of the positions in force, or states more than a tenth of the positions it shares with them within 5% of the quantity already held. An amendment marked as a restatement replaces the report, and any other amendment replaces the positions it states. With include_amendments=true, each filing's figures are their own row; with include_amendments=false, the report's figures. See Holdings.

2026-10-10: Fundamentals column definitions

  • Changed On filing rows, revenue is the filing's top line; a total smaller than one of its own parts in the same filing is not used. For a bank, it is total net revenue: the bank's own total where it reports one, otherwise net interest income plus noninterest income, and null where that total is negative.
  • Changed interest_expense is total interest expense as the filer reports it, for a bank including interest on deposits. Where the filer reports only a net interest figure, it is the net interest cost as a positive number; net interest income is not reported as interest expense.
  • Changed pretax_income is the sum of domestic and foreign pretax income where the filer reports only those two.
  • Changed cash_and_equivalents is the balance sheet's cash and cash equivalents or, where the balance sheet has no such line, the cash-flow statement's total of cash, cash equivalents and restricted cash, or the balance sheet's cash or cash and due from banks line. Any of these three above the filing's total assets is not used, and for a blank-check company whose balance sheet shows its trust account separately, cash held in the trust is not included.
  • Changed On filing rows, property_plant_equipment_net is net utility plant for a utility and net investment property for a real-estate company; either line above the filing's total assets is not used.
  • Changed pension_liabilities is one line the filing reports, usually the noncurrent amount: the noncurrent line for pension and other post-retirement benefits together where the filing reports that line; otherwise the pension line alone; otherwise another benefit line, such as other post-retirement benefits, post-employment benefits, or a combined line that also includes post-employment benefits or the current amount. Asset-retirement obligations, accrued payroll and deferred compensation are not included.
  • Changed dividends_paid_common is the cash dividends paid to common shareholders or, where the filing reports no payment of common dividends, the common dividends it declares. dividend_yield and the other metrics built on it follow. See Fundamentals fields.

2026-10-07: Cursor paging

  • Changed A next_cursor value works only with the request that returned it: the same path and the same parameters, with limit free to change. A cursor the API did not issue, an altered cursor, or a cursor from another request or endpoint returns HTTP 400 with a detail that begins Invalid cursor. An empty cursor= returns the first page.
  • Added meta.has_more and meta.next_cursor on every cursor endpoint: /v1/prices, /v1/fundamentals, /v1/metrics, /v1/insiders, /v1/holdings, /v1/managers/{manager_id}/holdings and /v1/managers. Endpoints that also return a top-level next_cursor keep it, with the same value. /v1/search returns meta.has_more.
  • Changed The plan's cap on pages per query applies to /v1/metrics and /v1/insiders as well as the other cursor endpoints.
  • Changed Row order on every paged endpoint ends in a unique key, so a row never repeats across pages.

2026-10-07: Per-share metrics on the price date's share basis

  • Added meta.per_share_basis on /v1/metrics, with the value split_adjusted_to_price_date.
  • Changed Earnings per share enters per-share metrics on the share basis of the price date where the filing's own weighted share count confirms the basis it reported. Where it does not, the row's EPS enters as filed and the metrics computed from it follow.
  • Changed Growth metrics compare with the period ending a year earlier (yoy), a quarter earlier (qoq) or the trailing twelve months ending a year earlier (ttm_yoy), selected by date. A derived fourth quarter is compared like any other quarter. Where no such period exists the metric is null and missing_inputs names prior:<field>.
  • Added missing_inputs reasons eps_diluted:scale_suspect, eps_basic:scale_suspect, shares_outstanding:depositary_count and market_cap:<entity_id>:depositary_count.
  • Removed missing_inputs reasons derived_q4:no_prior_comparison and derived_q4:window_contains_derived.

2026-10-07: Full docs in one file

  • Added https://xfinlink.com/llms-full.txt holds every page of these docs as Markdown, in sidebar order. It is served at the same path on https://api.xfinlink.com. See llms.txt.

2026-10-07: API keys in the Authorization header

  • Added The REST API and the MCP server accept the API key in the Authorization header as Bearer YOUR_API_KEY, as well as in the X-API-Key header and the api_key query parameter. When a request carries a key in more than one place, X-API-Key is read first, then api_key, then Authorization. See Authentication.
  • Changed A request that carries a valid API key is served even when its IP address is temporarily blocked. Requests from that address without a valid key are still refused with Temporarily blocked. See Errors.

2026-10-04: Derived fourth quarters and metric reasons

  • Added On Fundamentals, a quarterly row whose labels carry derived_q4: true is a fourth quarter derived as the fiscal year reported on the 10-K less the first nine months reported on the same fiscal year's third-quarter 10-Q. It is present only where no fourth-quarter row from a filing or the archive is on record for that fiscal year. Its income-statement and cash-flow lines are those differences, listed in derived_from_ytd; its balance-sheet lines are the 10-K's year-end figures; eps_basic, eps_diluted, shares_outstanding and weighted_avg_shares_basic are null. The row reads fiscal_period Q4, source filing and version original, with the 10-K's filing_date.
  • Added On Metrics, every row carries ttm_includes_derived_q4: true where the four quarters behind its trailing-twelve-month figures include a derived fourth quarter.
  • Added Quarterly, ttm and daily metrics rows carry ttm_eps_basis where a trailing EPS was not the four quarters summed: per EPS-based metric, one entry per window read, quarterly_sum or annual_as_filed:<period_end> (the EPS filed on that fiscal year's annual report).
  • Changed Where a quarter's EPS in a trailing window is null, the trailing EPS is the EPS filed on the annual report when the four quarters span exactly that fiscal year, or when the window's only missing EPS is a derived fourth quarter's. pe_ratio and the other EPS-based metrics read it.
  • Added New reasons in missing_inputs: close:not_common_equity (pe_ratio and peg_ratio are null where the priced security is not common stock, a depositary receipt or a fund share), derived_q4:no_prior_comparison, derived_q4:window_contains_derived, eps_diluted:zero, eps_diluted:negative, shares_outstanding:zero, prior:<field>:zero and eps_diluted_growth:zero. See Metrics.
  • Changed grahams_number is null, not 0, at zero EPS.

2026-10-01: Financial statements before 1970

  • Added /v1/fundamentals and /v1/metrics return annual periods from 1950-06-30 and quarterly periods from 1962-01-31. The added rows carry source: "archive" and fill periods that had no row. See Data coverage and boundaries.
  • Changed On /v1/metrics, growth figures such as revenue_growth on the first periods after the added rows take those rows as the prior period.
  • Added An archive row carries knowledge_time_basis: observed or bulk_load where the row has a publication date on record, none where it has none. bulk_load marks a figure that was public before 1986-12-14, the day the point-in-time record opens. A filing row does not carry the key; its clock is filing_date.
  • Changed A row whose knowledge_time_basis is none carries filing_date: null and is returned by the default view alone. On /v1/fundamentals, as_of, view=as_reported and version=original return nothing for those periods; on /v1/metrics, as_of returns nothing for them. Point-in-time versions run from 1970-06-30 for annual periods, with a few annual periods from 1968-06-30, and from 1973-09-30 for quarterly periods. See Point-in-time and as-of.
  • Added Every /v1/fundamentals and /v1/metrics response carries a fundamentals_historical entry in meta.freshness, alongside fundamentals and fundamentals_point_in_time. /v1/status lists it, for nineteen datasets in all.

2026-10-01: Python client 0.13.1

  • Changed On HTTP 401 and 403, XfinlinkError carries the API's message for the cause.
  • Changed A call that needs a key raises XfinlinkError without sending a request when no key is set, or when the key contains characters outside ASCII, such as the masked key text on the dashboard. search() and resolve() run without a key.

2026-09-30: API key errors and MCP index snapshots

  • Changed Every 401 names its cause in message: no key, a key under another header or parameter name, an incomplete key, a revoked key (with the date it was revoked) or an unrecognised key. The body keeps "error": "unauthorized". See Authentication.
  • Changed /v1/resolve and /v1/search return 401 for a key sent in X-API-Key or api_key that is incomplete, revoked or unrecognised. A request with no key is still answered as a keyless request.
  • Fixed MCP key errors repeat the request's JSON-RPC id, or leave it out when the request has none, and carry the same messages as the REST API.
  • Fixed The MCP get_index tool applies the plan's as_of allowance, as the REST API does: 50 distinct dates per UTC day on Free and no limit on paid plans.
response.json
{"error":"unauthorized","message":"No API key was sent. Pass your key in the X-API-Key header or the ?api_key= query parameter. Get one free at https://xfinlink.com/signup"}

2026-09-19: API and client 0.13.0

Response metadata

Added Every data response carries meta.knowledge_time and meta.freshness, and carries meta.as_of when the request fixes a date. freshness holds one {dataset, through, promoted_at} entry for each dataset behind the response. Read coverage from freshness rather than assuming a fixed end date. /v1/status returns the entries for every dataset. See Response metadata and Status and freshness.

response.json
"knowledge_time": "2026-09-26T12:48:18Z",
"as_of": "2026-09-25",
"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"}
]

New parameters

  • Added as_of on /v1/fundamentals and /v1/metrics returns the figures knowable on that date.
  • Added view on /v1/fundamentals takes latest (one row per period) or as_reported (every filed version of each period).
  • Added date on /v1/resolve resolves a ticker as it stood on that date.
  • Added source on /v1/holdings takes historical or filing and asks that era for its own figures for a quarter.
  • Changed Breaking include_amendments on /v1/holdings and /v1/insiders takes three states. Omitted returns the version in force, true adds the superseded versions, and false returns originals only. meta.amendment_view reads current, all or originals.
  • Added include_derivatives=true on /v1/insiders adds derivative-security lines. meta.derivative_lines reads included.
  • Added include_uncoded=true on /v1/insiders adds archive-era rows that carry no SEC transaction code (see "Insider transactions in two eras").

view=as_reported returns one row per filed version. Microsoft's fiscal 2022 revenue, in millions of USD, from GET /v1/fundamentals/MSFT?period=annual&start=2022-06-30&end=2022-06-30&view=as_reported&fields=revenue:

response.json
{
  "data": [
    {"entity_id": 8611, "ticker": "MSFT", "entity_name": "MICROSOFT CORP", "gics_sector": "Information Technology", "period_end": "2022-06-30", "period_type": "annual", "fiscal_year": 2022, "fiscal_period": "FY", "filing_date": "2022-07-28", "version": "original", "source": "filing", "revenue": 198270},
    {"entity_id": 8611, "ticker": "MSFT", "entity_name": "MICROSOFT CORP", "gics_sector": "Information Technology", "period_end": "2022-06-30", "period_type": "annual", "fiscal_year": 2022, "fiscal_period": "FY", "filing_date": "2023-07-27", "version": "restated", "source": "filing", "revenue": 198270},
    ...
  ],
  "meta": {
    ...
    "count": 4,
    "units": "millions_usd",
    "entity_id_basis": "issuer",
    "view": "as_reported",
    ...
  }
}

Share classes are separate securities

Changed Breaking entity_id names one share class on prices, resolve, search, index and holdings, and the company (the issuer) on fundamentals, metrics and insiders. Every response states which in meta. See Tickers and entities.

Changed Breaking A ticker that names two or more listed share classes on the resolution date, with nothing else in the request to serve, returns 409 ambiguous_ticker with a candidates list. Pass one of those IDs as entity_id. Dot and hyphen class forms both resolve: BRK.B and BRK-B return the same security.

response.json
{
  "error": "ambiguous_ticker",
  "status": 409,
  "detail": "BRK names more than one security on 2026-09-25. Request by entity_id to choose one.",
  "candidates": {
    "BRK": [
      {"entity_id": 10006133, "name": "BERKSHIRE HATHAWAY INC DEL", "exchange": "NYSE", "share_class": "A", "last_bar_date": "2026-09-25", "valid_from": null, "valid_to": null},
      {"entity_id": 10025221, "name": "BERKSHIRE HATHAWAY INC DEL", "exchange": "NYSE", "share_class": "B", "last_bar_date": "2026-09-25", "valid_from": null, "valid_to": null}
    ]
  }
}

New fields

  • Added Prices: price_basis, ca_source, dividend_type, split_ratio_suspect, shares_as_of, shares_source, shares_stale, and meta.delisting on a series that ends at a recorded delisting.
  • Added Resolve and search: entity_grain, issuer_entity_id, share_class, instrument_type, exchange, classification_as_of (inside classifications) and ticker_valid_to.
  • Added Index constituents and events: member_grain, ticker_basis, and meta.coverage_end.
  • Added Holdings: manager_id_space, manager_number, filer_cik, issuer_entity_id, link_method, title_of_class (the filer's own class text), issuer_name_as_filed, accession_number, vintage_date, unit_scale_suspect, is_current and lines.
  • Added Insiders: officer_title_as_filed, is_derivative, is_current, security_title, nature_of_ownership, reporting_owners, is_weekend_dated, is_dated_after_own_filing and rule_10b5_1_affirmed.
  • Added Fundamentals: derived_from_ytd, and filled_from_historical as a per-row list of field names.

Indexes

Changed Breaking /v1/index/{index_name} and /v1/index/{index_name}/events serve sp500, ndx100 and djia. Any other index returns 400 with the list of available ones. For djia and ndx100, a date after meta.coverage_end returns 400 naming that date.

response.json
{
  "error": "bad_request",
  "status": 400,
  "detail": "Unknown index: dax. Available: djia, ndx100, sp500"
}
response.json
{
  "error": "bad_request",
  "status": 400,
  "detail": "djia membership is recorded through 2026-09-26. Request a date on or before 2026-09-26."
}

Cost of revenue

Changed cost_of_goods_sold is the cost the filing tags for goods and services sold. cost_of_revenue is the total cost of revenue. Where a filing tags a product-only cost of goods sold together with its own gross profit, cost_of_revenue is revenue less that gross profit, and the row lists derived_from_filed: ["cost_of_revenue"]. cost_of_goods_sold stays the tagged figure.

Added Three per-row lists now name where a value came from: filled_from_historical, derived_from_ytd and derived_from_filed. Microsoft's fiscal 2016 row from GET /v1/fundamentals/MSFT?period=annual&start=2016-06-30&end=2016-06-30, in millions of USD:

response.json
{
  "entity_id": 8611,
  "ticker": "MSFT",
  ...
  "period_end": "2016-06-30",
  ...
  "fiscal_year": 2016,
  ...
  "revenue": 85320,
  "cost_of_revenue": 32780,
  "cost_of_goods_sold": 17880,
  "gross_profit": 52540,
  ...
  "derived_from_filed": ["cost_of_revenue"]
}

Insider transactions in two eras

Added /v1/insiders serves an archive era and a filing era in one series. Each row's source reads archive up to meta.archive_era.through and filing from meta.archive_era.seam. Archive rows carry source_quality_code.

Added transaction_code uses the SEC Form 4 transaction codes. Archive rows without such a code are left out by default and counted in meta.uncoded_rows_excluded. include_uncoded=true returns them with transaction_code set to null. /v1/status lists insider_transactions_historical alongside the other datasets.

Requested names that are not returned

Changed Breaking meta.fields_not_available on /v1/fundamentals and meta.metrics_not_available on /v1/metrics name requested fields and metrics that the response does not carry. fields= accepts those names, and rows omit them.

Changed Manager search by name (/v1/managers) covers managers that have filed Form 13F from 2013 onward. A manager that appears only in earlier quarters is reached by ID at /v1/managers/{manager_id}/holdings.

Errors

Fixed 409 ambiguous_ticker, 504 query_timeout and 504 request_timeout return JSON bodies with error, status and detail, like other errors. 500 internal_error returns error and status. Errors shows each body.

Python client 0.13.0

New keyword arguments:

  • Added fundamentals(view=, as_of=)
  • Added metrics(as_of=)
  • Added resolve(date_=)
  • Added holdings(source=)
  • Added insiders(include_derivatives=, include_uncoded=)

No function or argument was removed, and no Python default changed.

Changed llm_context() returns the current llms.txt.

Changed Breaking What a default call sends for include_amendments did change. In 0.13.0, insiders() sends include_amendments=false (originals only) and holdings() sends include_amendments=true (every filed version). Client 0.12.1 left the parameter out of both, which returns the version in force. Pass include_amendments=None to get the version in force from 0.13.0.

request.py
import logging
import xfinlink as xfl

logging.basicConfig(level=logging.INFO, format="%(message)s")
xfl.set_api_key("YOUR_API_KEY")

xfl.insiders("AAPL", start="2026-09-01", max_rows=1)
xfl.holdings("AAPL", quarter="2026-06-30", max_rows=1)
xfl.insiders("AAPL", start="2026-09-01", include_amendments=None, max_rows=1)
xfl.holdings("AAPL", quarter="2026-06-30", include_amendments=None, max_rows=1)
stderr
HTTP Request: GET https://api.xfinlink.com/v1/insiders/AAPL?limit=1&start=2026-09-01&include_amendments=false "HTTP/1.1 200 OK"
HTTP Request: GET https://api.xfinlink.com/v1/holdings/AAPL?limit=1&quarter=2026-06-30&include_amendments=true "HTTP/1.1 200 OK"
HTTP Request: GET https://api.xfinlink.com/v1/insiders/AAPL?limit=1&start=2026-09-01 "HTTP/1.1 200 OK"
HTTP Request: GET https://api.xfinlink.com/v1/holdings/AAPL?limit=1&quarter=2026-06-30 "HTTP/1.1 200 OK"

2026-07-30: Date-aware ticker resolution

Changed /v1/prices, /v1/fundamentals, /v1/metrics, /v1/insiders and /v1/holdings resolve a ticker on the dates of the request window. A window that ends before the ticker's current holder acquired it returns the company that held the ticker then, and historical_resolution names the entity served and the ticker's current holder. Pass entity_id to choose an entity outright. The change applies to every client version. See Tickers and entities.

2026-07-30: Python client 0.12.1

Fixed A call that passes end without start or period gets a default window of one year ending on that end date. prices(), fundamentals(), metrics(), insiders(), holdings() and manager_holdings() all follow this rule. A call with start, with period, with an end after today, or with no dates is unaffected.

2026-07-30: Python client 0.12.0

Added prices(), fundamentals(), metrics(), insiders() and holdings() take an entity_id argument in place of a ticker. It accepts an integer, a list or tuple of integers, or a comma-separated string such as "3165,4". Passing both entity_id and a ticker raises XfinlinkError before any request is sent. Each id counts as one ticker against the plan's per-request limit. Find ids with resolve() or search(). See Tickers and entities.

Was this page helpful?