ETF Holdings API
How to Look Up ETF Holdings with the Dealcharts API
Every ETF and mutual fund registered with the SEC files quarterly portfolio disclosures called NPORT-P reports. These contain the complete list of holdings — every position, every CUSIP, every dollar value. The problem is that NPORT-P filings are buried in EDGAR as XML documents that are tedious to parse by hand.
The Dealcharts API provides structured, machine-readable access to this data. Two endpoints handle the entire workflow: a search API to find funds by name, and a facts endpoint that returns the full portfolio in clean JSON. No API key required. No authentication. This guide covers both endpoints with working examples.
API Quick Reference
| Endpoint | URL Pattern | Returns |
|---|---|---|
| Search | dealcharts.org/.netlify/functions/search?query={term} | Matching entities with facts_url |
| Fund Facts | dealcharts.org/llm/facts/fund/{fund-key}.json | Full portfolio: identifiers, summary, holdings |
| Deal Facts | dealcharts.org/llm/facts/{deal-key}.json | CMBS/ABS deal data with provenance |
| Auth | None required | — |
| CORS | Enabled (all origins) | — |
| License | CC-BY 4.0 | — |
Step 1: Search for a Fund
The search endpoint accepts a free-text query and returns matching entities across funds, CMBS deals, and ABS deals. Each result includes a
— the direct link to the machine-readable data.facts_url
curl "https://dealcharts.org/.netlify/functions/search?query=ishares+semiconductor"
Response:
{"key": "ishares-semiconductor-etf","nameShort": "iShares Semiconductor ETF","sector": "Fund","facts_url": "https://dealcharts.org/llm/facts/fund/ishares-semiconductor-etf.json","page_url": "https://dealcharts.org/capitalmarkets/funds/ishares-semiconductor-etf/"}
The
is what you need. Thefacts_url
links to the human-readable fund page on Dealcharts.page_url
Step 2: Fetch the Fund Facts
curl "https://dealcharts.org/llm/facts/fund/ishares-semiconductor-etf.json"
The response is a JSON object with this structure:
{"name": "iShares Semiconductor ETF","identifiers": {"lei": "5493004SPI3IF1GDIR85","cik": "1100663","fund_id": "S000004354"},"dates": {"filing_date": "2026-02-25","reporting_period_start": "2025-12-31","reporting_period_end": "2026-03-31"},"summary": {"total_positions": 38,"total_value_usd": 18107013004,"asset_categories": ["STIV", "EC", "DE"]},"holdings": [{"issuer_name": "NVIDIA Corp.","cusip": "67066G104","value_usd": 1448021808,"pct_val": 8.26,"asset_cat": "EC","inv_country": "US"},{"issuer_name": "Advanced Micro Devices, Inc.","cusip": "007903107","value_usd": 1352965866,"pct_val": 7.72,"asset_cat": "EC","inv_country": "US"}]}
Key fields in each holding:
- issuer_name — Company name
- cusip — CUSIP identifier for the security
- value_usd — Dollar value of the position
- pct_val — Percentage of total fund value
- asset_cat — Asset category code (EC = Equity Common, DBT = Debt, STIV = Short-Term Investment, etc.)
- inv_country — Country of investment
Step 3: Extract and Analyze
Python: Top 10 Holdings
import requests# Step 1: Search for the fundsearch = requests.get("https://dealcharts.org/.netlify/functions/search",params={"query": "ishares semiconductor"}).json()facts_url = search["facts_url"]# Step 2: Fetch the full portfoliofund = requests.get(facts_url).json()# Step 3: Sort holdings by weight and display top 10holdings = sorted(fund["holdings"], key=lambda h: h["pct_val"], reverse=True)print(f"{fund['name']} — {fund['summary']['total_positions']} positions")print(f"Total value: ${fund['summary']['total_value_usd']:,.0f}")print(f"Filing date: {fund['dates']['filing_date']}\n")for i, h in enumerate(holdings[:10], 1):print(f"{i:2d}. {h['issuer_name']:<40s} {h['cusip']} "f"${h['value_usd']:>14,.0f} {h['pct_val']:5.2f}%")
Output:
iShares Semiconductor ETF — 38 positionsTotal value: $18,107,013,004Filing date: 2026-02-251. NVIDIA Corp. 67066G104 $ 1,448,021,808 8.26%2. Advanced Micro Devices, Inc. 007903107 $ 1,352,965,866 7.72%3. Micron Technology, Inc. 595112103 $ 1,221,375,277 6.97%4. Broadcom, Inc. 11135F101 $ 1,180,341,517 6.74%5. Applied Materials, Inc. 038222105 $ 1,031,008,158 5.88%6. Qualcomm, Inc. 747525103 $ 952,478,221 5.43%7. Texas Instruments, Inc. 882508104 $ 879,246,731 5.02%8. Lam Research Corp. 512807108 $ 855,975,219 4.88%9. KLA Corp. 482480100 $ 836,261,103 4.77%10. Marvell Technology, Inc. 573874104 $ 751,925,693 4.29%
Python: Cross-Fund Comparison
One of the more useful applications is comparing holdings across funds. Since the facts endpoint uses the same schema for every fund, you can compare any two directly:
funds_to_compare = ["ishares-semiconductor-etf","fidelity-msci-health-care-index-etf"]for fundkey in funds_to_compare:url = f"https://dealcharts.org/llm/facts/fund/{fundkey}.json"fund = requests.get(url).json()holdings = sorted(fund["holdings"], key=lambda h: h["pct_val"], reverse=True)top5_pct = sum(h["pct_val"] for h in holdings[:5])print(f"\n{fund['name']}")print(f" Positions: {fund['summary']['total_positions']}")print(f" Top 5 concentration: {top5_pct:.1f}%")for h in holdings[:5]:print(f" {h['issuer_name']}: {h['pct_val']:.2f}%")
curl: Quick One-Liner
If you already know the fund key, skip the search step entirely:
curl -s "https://dealcharts.org/llm/facts/fund/ishares-semiconductor-etf.json" \| jq '.holdings | sort_by(-.pct_val) | .[0:5] | .[] | {issuer_name, cusip, pct_val}'
Finding the Right Fund Key
Fund keys are URL-safe slugs of the fund name — lowercase, hyphens for spaces, special characters stripped. If you're not sure of the exact slug, the search endpoint handles fuzzy matching:
# These all workcurl "https://dealcharts.org/.netlify/functions/search?query=ishares+semiconductor"curl "https://dealcharts.org/.netlify/functions/search?query=vanguard+gnma"curl "https://dealcharts.org/.netlify/functions/search?query=fidelity+health+care"
For a complete list of available funds, the machine-readable sitemap lists every entity:
https://dealcharts.org/sitemap-llm.xml
Data Source and Freshness
All fund holdings data comes from SEC NPORT-P filings — the quarterly portfolio disclosure required of registered investment companies. The
object in each facts response tells you exactly what you're looking at:dates
- filing_date — When the fund filed with the SEC
- reporting_period_start / reporting_period_end — The quarter covered
- Data lag — Typically 60 days after quarter-end (SEC filing deadline)
Holdings data is updated as new NPORT-P filings appear in EDGAR.
Using This with LLMs
The search + facts workflow maps directly to LLM tool use. If you're building an agent that answers questions about fund portfolios, the pattern is:
- User asks: "What are the top holdings of the iShares Semiconductor ETF?"
- Agent calls search endpoint with the fund name
- Agent fetches the
from the resultfacts_url - Agent parses the holdings array and answers with exact data
The facts JSON is designed to be self-explanatory to LLMs — field names like
,issuer_name
, andpct_val
require no documentation to interpret. Thetotal_value_usd
object provides CIK and LEI for cross-referencing with EDGAR and other data sources.identifiers
For more on how Dealcharts structures data for AI consumption, see our guide on LLM-Optimized Facts Endpoints for Finance.
Related Resources
- SEC EDGAR API Guide — CompanyFacts endpoint, CIK lookup, rate limits, and Python examples
- LLM-Optimized Facts Endpoints for Finance — Design principles for provenance-first financial data APIs
- BDC Holdings Dataset Download — Bulk BDC portfolio data from NPORT-P filings
- API Documentation — Full Dealcharts API reference with endpoint patterns and data directory