# SEC 13F Scraper - Fund Holdings (`datalayer/institutional-holdings-13f`) Actor

Institutional holdings with the part nobody sells: what changed. Positions opened, exited, added to and trimmed between quarters, classified on share count so a market move is never mistaken for a trade. Handles the 2023 units change and the split-row trap.

- **URL**: https://apify.com/datalayer/institutional-holdings-13f.md
- **Developed by:** [Datalayer](https://apify.com/datalayer) (community)
- **Categories:** Other, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / 1,000 holdings

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## SEC 13F Scraper — Fund Holdings & Quarterly Changes

Every manager running $100m+ files a quarterly 13F listing its US equity
positions. Dumping that table is easy, and it is what every 13F scraper does.
It is also wrong three ways.

### The three traps

**1. The values changed units in 2023 — silently, by a factor of 1000.**

SEC amended Form 13F for filings made from 3 January 2023: values are whole
dollars. Before that they were thousands. Berkshire Hathaway's own filings:

| Filed | `tableValueTotal` | Actually |
|---|---|---|
| 2022-11-14 | 296,096,640 | **$296.1 billion** |
| 2026-05-15 | 263,095,703,570 | **$263.1 billion** |

Read both literally and the 2022 portfolio comes out as $296 *million*. This
Actor normalises everything to dollars and labels which convention each filing
used.

**2. One holding is filed as many rows.**

The same CUSIP repeats once per internal manager group. Berkshire's Q1 2026
filing lists `ALLY FINL INC` three separate times. Row-dumping reports three
positions where there is one — and every weight, rank and count derived from it
is wrong.

Verified on the same filing: **90 rows collapse to 29 real positions**, 18 of
which were split. This Actor aggregates by CUSIP and tells you how many rows
were merged, so the arithmetic is auditable.

**3. Amendments replace a quarter; they do not add one.**

A `13F-HR/A` restates a period already filed. Treat it as another quarter and
you double-count; ignore it and you are reading data the filer has withdrawn.
This Actor keeps the latest filing per period and names the accession it
supersedes.

### What nobody sells: the diff

A list of positions is a snapshot. What *moved* is the information. Every
quarter is compared against the one before it and each position is labelled:

| Action | Meaning |
|---|---|
| `NEW` | Position opened this quarter |
| `EXITED` | Position closed entirely |
| `ADDED` | Share count up by more than the threshold |
| `TRIMMED` | Share count down by more than the threshold |
| `HELD` | No meaningful change |

**Decided on share count, never dollar value.** If a stock halves and the
manager does nothing, the dollar value halves — that is the market moving, not
a trade. Value change is still reported, just never used to classify.

Real output, Berkshire Q1 2026 vs Q4 2025:

| Action | Holding | Shares before → after | Δ |
|---|---|---|---|
| NEW | Delta Air Lines | 0 → 39,809,456 | +$2,646.5M |
| ADDED | Alphabet Class C | 17,846,142 → 54,249,798 | +204% |
| TRIMMED | Constellation Brands | 13,000,000 → 632,890 | −95.1% |
| TRIMMED | Nucor Corp | 6,407,749 → 3,907,075 | −39% |

Positions are matched on **CUSIP**, not issuer name — filers type names by hand
and they drift. Share classes are tracked separately, which is why Alphabet can
legitimately appear as `NEW` in one class and `ADDED` in another.

### Input

```json
{
  "ciks": ["1067983", "1350694"],
  "quarters": 4,
  "changesOnly": true,
  "minPositionValue": 10000000
}
```

**These are the manager's CIK, not the companies they hold.** Berkshire
Hathaway is `1067983`, Bridgewater `1350694`, Renaissance Technologies
`1037389`.

| Field | Default | Notes |
|---|---|---|
| `ciks` | `["1067983"]` | Manager CIKs. |
| `quarters` | `4` | Newest first. One extra is read as the comparison baseline. |
| `startPeriod` / `endPeriod` | — | `YYYY-MM-DD`, quarter ends. |
| `changesOnly` | `false` | Skip the holdings table, return only what moved. |
| `minPositionValue` | — | Drop the long tail of tiny positions. |
| `trimThresholdPercent` | `5` | Share-count move before ADDED / TRIMMED fires. |
| `includeAmendments` | `true` | Prefer a `13F-HR/A` over the original. |
| `contactEmail` | — | Goes in the User-Agent, nowhere else. |

### Output

**`portfolio`** — one row per quarter:

```json
{
  "type": "portfolio",
  "managerName": "Berkshire Hathaway Inc",
  "periodOfReport": "2026-03-31",
  "positions": 29,
  "rowsFiled": 90,
  "positionsWithSplitRows": 18,
  "totalValue": 263095703570,
  "reportedTotalValue": 263095703570,
  "totalsAgree": true,
  "largestHolding": "APPLE INC",
  "largestHoldingPercent": 22.0,
  "concentrationHHI": 1185
}
```

`totalsAgree` cross-checks the sum of the rows against the total the manager
reported on its own cover page. On the run above it reconciles exactly, which
is the strongest available proof that the units and aggregation are right.

**`change`** — one row per position that moved. **`holding`** — one row per
position per quarter, with weight, rank and implied price. **`quarter_changes`**
— the per-quarter headline.

### Reliability

- Official SEC endpoints only: `data.sec.gov` and `www.sec.gov/Archives`. No
  login, no session, no rendered-page scraping.
- The information table has an arbitrary numeric filename (`53405.xml` on one
  filing, `18337.xml` on another). It is discovered from the filing's own
  `index.json`, never guessed.
- Runs under SEC's published 10 requests/second ceiling with a single serial
  pacer, and sends a declared `User-Agent` with a contact address — SEC returns
  403 to anything without one.
- One unreadable quarter never fails the run; it lands in `RUN_SUMMARY`.

### Limits

- 13F covers US-listed equities, ADRs, certain convertibles and options. It
  does **not** cover cash, bonds, foreign listings or short positions. A 13F is
  not the whole portfolio and never was.
- Filings are due 45 days after quarter end, so the data is six weeks stale on
  arrival. That is the form, not the Actor.
- Filings before roughly 2013 are plain text with no XML table. Those are
  skipped with a recorded reason.
- `PRN` rows are bond principal amounts, not share counts. They are kept
  separate and no per-share price is derived for them.
- This is public regulatory data returned as filed. It is not investment advice
  and no forecast is implied by any field.

# Actor input Schema

## `ciks` (type: `array`):

SEC CIK numbers of the investment managers. Note these are the MANAGER's CIK, not the companies they hold. Berkshire Hathaway is 1067983, Bridgewater is 1350694, Renaissance Technologies is 1037389.

## `quarters` (type: `integer`):

How many recent quarters to pull for each manager, newest first. One extra quarter is always read behind the scenes so the oldest requested quarter still has something to be compared against.

## `startPeriod` (type: `string`):

Earliest report period to include, YYYY-MM-DD. 13F periods are quarter ends, for example 2025-12-31.

## `endPeriod` (type: `string`):

Latest report period to include, YYYY-MM-DD.

## `changesOnly` (type: `boolean`):

Skip the full holdings list and return only what moved between quarters. Much smaller output when you are watching for activity rather than building a position table.

## `minPositionValue` (type: `integer`):

Drop positions and changes worth less than this. Large managers carry long tails of tiny positions that add noise.

## `trimThresholdPercent` (type: `integer`):

How much the share count must move before a position counts as increased or reduced rather than held. Keeps rounding and small rebalancing out of the signal.

## `includeAmendments` (type: `boolean`):

When a manager amends a quarter (13F-HR/A), use the amendment instead of the original. Turn off to read only originals.

## `includeHoldings` (type: `boolean`):

One row per position per quarter, with value, shares, portfolio weight and rank.

## `includeChanges` (type: `boolean`):

One row per position that moved, labelled NEW, EXITED, ADDED or TRIMMED, plus a per-quarter summary row.

## `includePortfolioSummary` (type: `boolean`):

One row per quarter: total value, position count, concentration, largest holding, and whether the filing's own totals reconcile.

## `contactEmail` (type: `string`):

SEC asks automated callers to identify themselves. Your email goes in the User-Agent header and nowhere else.

## `proxyConfiguration` (type: `object`):

Optional. SEC allows 10 requests per second per IP and this Actor stays under that.

## Actor input object example

```json
{
  "ciks": [
    "1067983",
    "1350694"
  ],
  "quarters": 4,
  "changesOnly": false,
  "trimThresholdPercent": 5,
  "includeAmendments": true,
  "includeHoldings": true,
  "includeChanges": true,
  "includePortfolioSummary": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `changes` (type: `string`):

Positions opened, exited, added to and trimmed between quarters.

## `holdings` (type: `string`):

One row per position per quarter, with weight and rank.

## `portfolios` (type: `string`):

Per-quarter totals, concentration and reconciliation against the filing's own numbers.

## `all` (type: `string`):

Holdings, changes and portfolio summaries together.

## `csv` (type: `string`):

Everything as a CSV download.

## `runSummary` (type: `string`):

Counts, plus any manager or quarter that could not be read.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "ciks": [
        "1067983",
        "1350694"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("datalayer/institutional-holdings-13f").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "ciks": [
        "1067983",
        "1350694",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("datalayer/institutional-holdings-13f").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "ciks": [
    "1067983",
    "1350694"
  ]
}' |
apify call datalayer/institutional-holdings-13f --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datalayer/institutional-holdings-13f"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/ow69wo7LqtfdP3SGr/builds/cKt28YEqSKTcHSelQ/openapi.json
