> ## Documentation Index
> Fetch the complete documentation index at: https://docs.llmquantdata.com/llms.txt
> Use this file to discover all available pages before exploring further.

# ETF Holdings

> Latest/as-of regulatory holdings for one US-listed ETF — full position list, normalized, sorted by weight descending.

<Note icon="sparkles">
  **Available as MCP tool**: `etf_holdings` — call directly from Claude / Cursor / any MCP client. See [MCP Server](/en/integration/mcp-server) for the 60-second setup.
</Note>

<Badge color="green" icon="circle-check">Live</Badge>
 
<Badge color="blue" size="sm">1 credit (0 if unsupported)</Badge>

## What it does for your agent

`etf_holdings` returns the **latest or as-of regulatory disclosure holdings** for a single US-listed ETF — full position list (subject to `limit`), each row normalized to a common `EtfHolding` shape with `ticker` / `cusip` / `isin` / `sedol` identifiers, `weight`, `market_value`, `shares`, and `sector` / `country` / `asset_type`. Rows are sorted by `weight` descending.

Use it when the agent needs the **actual position table** — to compute concentration (Top-10 weight, HHI), overlap between two ETFs (call twice and diff by CUSIP / ISIN), or to build a thematic basket from an ETF's underlying. For ETF basic info, top-N summary, and exposure breakdowns, prefer the lighter [`etf_lookup`](/en/api/etf/lookup) which is free.

This endpoint has one documented exception to the platform's "the action runs, you pay" billing rule: an `unsupported` ticker is treated as a free **coverage check**, not a holdings retrieval. Covered tickers return rows and cost 1 credit; tickers outside coverage return `200 OK` with `coverage_status="unsupported"`, empty `holdings`, and **0 credits**. This is a specific sanctioned carve-out for ETF Holdings — it is not a general "no data = free" rule elsewhere on the platform. Pass `as_of=YYYY-MM-DD` for the latest snapshot with `as_of_date <= as_of`; cross-ETF overlap is still computed client-side.

## Response

<ResponseField name="data" type="EtfHoldingsResult" required>
  <Expandable title="EtfHoldingsResult fields">
    <ResponseField name="ticker" type="string" required>
      ETF ticker (uppercased).
    </ResponseField>

    <ResponseField name="fund_name" type="string" nullable>
      Fund display name. `null` when `coverage_status="unsupported"`.
    </ResponseField>

    <ResponseField name="issuer" type="string" nullable>
      Issuer / sponsor.
    </ResponseField>

    <ResponseField name="holdings" type="EtfHolding[]" required>
      Position rows sorted by `weight` descending. Empty array when `coverage_status="unsupported"`.

      <Expandable title="EtfHolding fields">
        <ResponseField name="holding_name" type="string" required>Holding name as filed.</ResponseField>

        <ResponseField name="ticker" type="string" nullable>
          Underlying ticker. **Often `null` for bonds, cash, derivatives, and crypto trusts** — use `cusip` / `isin` to identify these rows.
        </ResponseField>

        <ResponseField name="cusip" type="string" nullable>CUSIP. Preferred join key for overlap computation.</ResponseField>
        <ResponseField name="isin" type="string" nullable>ISIN. Secondary join key.</ResponseField>
        <ResponseField name="sedol" type="string" nullable>SEDOL, when present in the filing.</ResponseField>

        <ResponseField name="asset_type" type="string" nullable>
          `equity` / `fixed_income` / `cash` / `derivative` / `crypto` / `other`.
        </ResponseField>

        <ResponseField name="sector" type="string" nullable>Sector classification; may be `null`.</ResponseField>
        <ResponseField name="country" type="string" nullable>Country of risk; may be `null`.</ResponseField>
        <ResponseField name="shares" type="number" nullable>Number of shares / units held.</ResponseField>
        <ResponseField name="market_value" type="number" nullable>Market value in USD.</ResponseField>

        <ResponseField name="weight" type="number" nullable>
          Portfolio weight as a decimal (e.g. `0.071` for 7.1%).
        </ResponseField>

        <ResponseField name="notional_value" type="number" nullable>
          Notional for derivatives and special instruments; may be `null`.
        </ResponseField>

        <ResponseField name="source" type="string" required>Per-row identifier of the originating SEC disclosure dataset.</ResponseField>
        <ResponseField name="source_url" type="string" nullable>Link to the related SEC disclosure dataset, for citation.</ResponseField>
        <ResponseField name="as_of_date" type="string" nullable>Regulatory disclosure report date this row reflects.</ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="source" type="string" required>Top-level identifier of the originating SEC disclosure dataset.</ResponseField>
    <ResponseField name="source_url" type="string" nullable>Top-level link to the related SEC disclosure dataset, for citation.</ResponseField>
    <ResponseField name="as_of_date" type="string" nullable>Regulatory disclosure snapshot date (`YYYY-MM-DD`). If `as_of` is passed, this is the latest available date `<= as_of`.</ResponseField>
    <ResponseField name="fetched_at" type="string" nullable>ISO timestamp when LLMQuant Data last refreshed this holdings data.</ResponseField>
    <ResponseField name="stale" type="boolean" required>`true` when the snapshot is past the freshness window or a refresh degraded.</ResponseField>

    <ResponseField name="coverage_status" type="string" required>
      One of `full` / `partial` / `stale` / `unsupported`.
    </ResponseField>

    <ResponseField name="coverage_notice" type="string" required>
      Plain-English explanation of what's covered or why it isn't.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="meta.creditsUsed" type="number">
  `1` for `full` / `partial` / `stale` returns. `0` when `coverage_status="unsupported"`.
</ResponseField>

<ResponseField name="meta.remainingCredits" type="number">Account credits remaining.</ResponseField>

```json title="200 OK · etf_holdings (supported)" expandable theme={null}
{
  "data": {
    "ticker": "SPY",
    "fund_name": "SPDR S&P 500 ETF Trust",
    "issuer": "State Street",
    "holdings": [
      {
        "holding_name": "APPLE INC",
        "ticker": "AAPL",
        "cusip": "037833100",
        "isin": "US0378331005",
        "sedol": null,
        "asset_type": "equity",
        "sector": "Information Technology",
        "country": "US",
        "shares": 168000000,
        "market_value": 30450000000,
        "weight": 0.071,
        "notional_value": null,
        "source": "sec_nport",
        "source_url": "https://www.sec.gov/dera/data/form-n-port-data-sets",
        "as_of_date": "2019-09-30"
      },
      {
        "holding_name": "MICROSOFT CORP",
        "ticker": "MSFT",
        "cusip": "594918104",
        "isin": "US5949181045",
        "sedol": null,
        "asset_type": "equity",
        "sector": "Information Technology",
        "country": "US",
        "shares": 78000000,
        "market_value": 27890000000,
        "weight": 0.065,
        "notional_value": null,
        "source": "sec_nport",
        "source_url": "https://www.sec.gov/dera/data/form-n-port-data-sets",
        "as_of_date": "2019-09-30"
      }
    ],
    "source": "sec_nport",
    "source_url": "https://www.sec.gov/dera/data/form-n-port-data-sets",
    "as_of_date": "2019-09-30",
    "fetched_at": "2026-05-12T03:14:00Z",
    "stale": false,
    "coverage_status": "full",
    "coverage_notice": "Latest available SEC regulatory disclosure snapshot. Not the issuer's daily latest holdings."
  },
  "meta": { "creditsUsed": 1, "remainingCredits": 99 }
}
```

```json title="200 OK · etf_holdings (unsupported)" expandable theme={null}
{
  "data": {
    "ticker": "IBIT",
    "fund_name": null,
    "issuer": null,
    "holdings": [],
    "source": "sec_nport",
    "source_url": null,
    "as_of_date": null,
    "fetched_at": null,
    "stale": false,
    "coverage_status": "unsupported",
    "coverage_notice": "IBIT is not in the current covered ETF list. As a spot Bitcoin trust, its SEC disclosure path differs from the conventional ETFs we cover today."
  },
  "meta": { "creditsUsed": 0, "remainingCredits": 100 }
}
```

## Credit rule

| Coverage outcome                                                   | HTTP  |                         `creditsUsed`                         |
| ------------------------------------------------------------------ | ----- | :-----------------------------------------------------------: |
| `full` / `partial` / `stale` (holdings returned)                   | `200` |                              `1`                              |
| `unsupported` (no holdings, explicit notice)                       | `200` |                              `0`                              |
| Caller has insufficient credits **and** ticker is in coverage      | `402` |                  n/a — holdings not returned                  |
| Caller has insufficient credits **and** ticker is outside coverage | `200` | `0` — coverage envelope is delivered without spending credits |

Core rule: checking whether a ticker is covered is free; only a resolved holdings retrieval charges 1 credit. This free-when-unsupported behaviour is a documented, sanctioned exception scoped to ETF Holdings — every other endpoint charges as soon as the action runs, even when the result is empty.

## Notes

<Tip>
  **For overlap between two ETFs**, call `etf_holdings` twice and diff client-side. **Join by `cusip` first**, then `isin`, then `ticker` — holding rows often have a `null` ticker for bonds / cash / derivatives, but stable identifiers like CUSIP survive.
</Tip>

<Tip>
  **Concentration metrics** (Top-10 weight, HHI) are cheap once you have the rows. Sort is already weight-descending; just slice.
</Tip>

<Tip>
  Use [`etf_lookup`](/en/api/etf/lookup) first (free) to confirm coverage and read `holdings_count` before deciding the `limit` for this call. Broad-market ETFs like `VTI` have thousands of positions; default `limit=50` is enough for most agent workflows.
</Tip>

### Current limitations

<Warning>
  **Not current / daily holdings.** Rows reflect the latest SEC official regulatory disclosure snapshot (typically monthly or quarterly disclosure with a public-release lag), not the issuer's daily book. `as_of_date` makes this explicit.
</Warning>

<Warning>
  **Per-row `ticker` is nullable.** Bonds, cash, derivatives, and crypto trusts frequently have no ticker in a holdings row. Always fall back to CUSIP / ISIN for identification and joins.
</Warning>

<Warning>
  **No `etf_compare_holdings` server-side.** Cross-ETF overlap, weight-difference, and basket comparisons are agent-side computations from two `etf_holdings` calls.
</Warning>

<Warning>
  **Only single-date historical lookup is supported.** `as_of` selects the latest snapshot at or before one date; ranges, quarters, and multi-period returns are not part of this endpoint.
</Warning>

<Warning>
  **Coverage is limited.** Currently covered: `SPY`, `QQQ`, `VTI`, `SOXX`, `ARKK` and other curated popular ETFs. `IBIT` / `DRAM` are outside coverage today.
</Warning>

<Warning>
  **Regulatory disclosures only.** Issuer fact-sheet PDFs and other non-regulatory datasets are not used in this response.
</Warning>

## Direct invocation

<Accordion title="HTTP / SDK examples" icon="terminal">
  <CodeGroup>
    ```typescript MCP (Claude / Cursor) theme={null}
    // Default — top 50 by weight
    {
      "method": "tools/call",
      "params": {
        "name": "etf_holdings",
        "arguments": { "ticker": "SPY" }
      }
    }

    // Custom limit + as-of snapshot
    {
      "method": "tools/call",
      "params": {
        "name": "etf_holdings",
        "arguments": { "ticker": "VTI", "limit": 200, "as_of": "2025-10-01" }
      }
    }
    ```

    ```python Python (HTTP) theme={null}
    import os, requests

    headers = {"Authorization": f"Bearer {os.environ['LLMQUANT_API_KEY']}"}

    resp = requests.get(
        "https://api.llmquantdata.com/api/etf/holdings",
        headers=headers,
        params={"ticker": "VTI", "limit": 50, "as_of": "2025-10-01"},
    ).json()
    d = resp["data"]
    if d["coverage_status"] == "unsupported":
        print(f"Not covered: {d['coverage_notice']}")
    else:
        top10_weight = sum(h["weight"] or 0 for h in d["holdings"][:10])
        print(f"{d['ticker']} snapshot {d['as_of_date']}  Top-10 weight: {top10_weight:.1%}")
    ```

    ```bash cURL theme={null}
    curl "https://api.llmquantdata.com/api/etf/holdings?ticker=VTI&limit=50&as_of=2025-10-01" \
      -H "Authorization: Bearer $LLMQUANT_API_KEY"
    ```
  </CodeGroup>
</Accordion>

## Full parameter reference

<Accordion title="etf_holdings — request parameters" icon="sliders">
  <ParamField query="ticker" type="string" required>
    US-listed ETF ticker (e.g. `SPY`, `QQQ`, `VTI`, `SOXX`, `ARKK`). Case-insensitive; the server uppercases and trims. Tickers outside coverage return `200 OK` with `coverage_status="unsupported"` and `creditsUsed=0`.
  </ParamField>

  <ParamField query="limit" type="integer" default={50}>
    Maximum number of holdings rows to return, sorted by `weight` descending. Default `50`. Max `500`. For broad-market ETFs (e.g. `VTI`), the full underlying list will exceed the maximum; cursor-based pagination is on the roadmap.
  </ParamField>

  <ParamField query="as_of" type="string">
    Optional period date in `YYYY-MM-DD` format. Returns the latest holdings snapshot with `as_of_date <= as_of`; omit it for the latest available snapshot.
  </ParamField>
</Accordion>

## Related

<Columns cols={3}>
  <Card title="ETF Lookup" icon="magnifying-glass" href="/en/api/etf/lookup">
    Fund identity, SEC mapping, top holdings summary, and exposure breakdowns — free.
  </Card>

  <Card title="Equity Historical Prices" icon="chart-line" href="/en/api/prices/equity-historical">
    ETF OHLCV history lives on the equity daily-bar endpoint.
  </Card>

  <Card title="MCP Server setup" icon="plug" href="/en/integration/mcp-server">
    Connect Claude / Cursor / any harness in 60 seconds.
  </Card>
</Columns>
