3spread
All guides

Export a fund's 13F portfolio to CSV

Pull a manager's latest 13F filing and write every position to a CSV you can open in a spreadsheet, without losing precision on the dollar values. Python, on a free key.

The 3spread TeamJuly 14, 2026Institutional holdings dataset

Sometimes you do not want a pipeline. You want Berkshire's latest portfolio in a spreadsheet, in the next thirty seconds.

This is the shortest useful thing you can do with the API: one call to find the filing, one to stream its holdings, and a CSV on disk.

Everything below runs on the free Community tier: every dataset, 10,000 requests per day (600 per minute), no credit card.

Setup

pip install py3spread
export THREESPREAD_API_KEY="sk_live_..."

The whole thing

import csv
import itertools
from decimal import Decimal
 
from py3spread import Client
 
MANAGER_CIK = "1067983"  # Berkshire Hathaway
OUT = "holdings.csv"
 
with Client() as client:
    filings = client.institutional_holdings.list(filing_manager_cik=MANAGER_CIK, limit=1)["data"]
    if not filings:
        print(f"no 13F filings for cik {MANAGER_CIK}")
        raise SystemExit
 
    filing = filings[0]
    print(f"{filing['filer_name']}, period {filing['period_of_report']}")
 
    rows = itertools.islice(
        client.institutional_holdings.iter_holdings(filing_id=filing["filing_id"]),
        5000,
    )
 
    fields = ["name_of_issuer", "title_of_class", "cusip", "ssh_prnamt", "ssh_prnamt_type", "value_usd"]
    count = 0
    total = Decimal(0)
 
    with open(OUT, "w", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=fields, extrasaction="ignore")
        writer.writeheader()
        for row in rows:
            writer.writerow(row)
            total += Decimal(row["value_usd"])
            count += 1
 
print(f"wrote {count} holdings (${total:,.0f} reported value) to {OUT}")

That is it. Open holdings.csv in anything.

The parts that are load-bearing

limit=1 gets you the latest filing. Filings come back newest first, so you do not need to sort or filter on a date to find the current one. Then filing_id is what ties the filing to its positions: the list endpoint gives you filings, iter_holdings(filing_id=...) gives you the rows inside one.

Decimal, not float. The API returns dollar values as strings at full precision, deliberately. If you parse them as floats to sum them, you are introducing rounding error into a number that was exact when it arrived. On a portfolio the size of Berkshire's, that shows up in the total.

extrasaction="ignore". The API rows carry more fields than the six in the CSV. Without this, DictWriter raises on the first row it sees. With it, you get exactly the columns you asked for, and adding one is a matter of putting its name in fields.

One note on the terms

This exports a single filing for your own analysis, which is exactly what the Community tier is for. It is not a license to mirror the dataset: the terms rule out bulk downloads and systematically copying or storing substantial portions of it, and the Community tier is for personal, non-commercial research. Analysis you build on top of the data is yours. The data itself is not.

If your use case genuinely needs a local copy of a lot of it, that is a conversation worth having, and sales is where it starts.

Where to take it

Change MANAGER_CIK and you have any manager's book. You can find the CIK through the manager index, which is what client.institutional_holdings exposes alongside the filings themselves.

Export two consecutive quarters for the same manager and diff them in a spreadsheet, and you have their trades: what they opened, exited, added to and trimmed. That is the whole "what did the big funds buy" exercise, and it is now a VLOOKUP.

The dataset

Full endpoint reference and response schemas are on the 13F institutional holdings dataset page.

Run this yourself

Everything here works on the free Community tier: every dataset, 10,000 requests per day (600 per minute), no credit card. Provision a key and paste the code in.