# FDIC Bank M\&A, Charter & Structure-Change Monitor (`malonestar/fdic-structure-change-delta-monitor`) Actor

Typed delta feed of FDIC bank structure changes: mergers paired acquirer-to-target and sized on both sides (assets, deposits, holding company), de-novo charters, failures, regulator and class conversions. Live filter-drift assertions. Keyless FDIC source, no login.

- **URL**: https://apify.com/malonestar/fdic-structure-change-delta-monitor.md
- **Developed by:** [Kyle Maloney](https://apify.com/malonestar) (community)
- **Categories:** Business, Developer tools, Agents
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $8.25 / 1,000 results

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/platform/actors/running/actors-in-store#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

## FDIC Bank M\&A, Charter & Structure-Change Monitor

A typed delta feed of every structure change in the US banking system, straight from the
FDIC's own institution-history record: mergers paired acquirer-to-target **and sized on
both sides**, de-novo charters, failures and assisted resolutions, branch openings and
sales, relocations, regulator and class conversions, legal name changes.

Keyless, no login, no scraping. One `mode`, one date window, optionally a state list.

**v1.1 adds deal-size financials** — total assets, deposits, offices, holding company and
RSSD for the acquirer, target, survivor and subject on every row — plus three **live
drift assertions** that fail the run rather than hand you a plausible wrong answer.

***

### Why this exists

`api.fdic.gov/banks/history` is public and free, and also close to unusable raw: 150
columns per record, 41 undocumented numeric change codes, acquirer/target linkage smeared
across `ACQ_*`/`OUT_*`/`SUR_*` triples plus an underscore-packed `ID`, no financials at
all, and an aggregation endpoint (`agg_by`) that returns HTTP 500.

Worse, it fails *quietly*. Two requests, verified live on 2026-07-28:

| Request | `meta.total` | What FDIC did |
|---|---|---|
| `PSTALP:TX AND CHANGECODE:223` | 1,457 | applied both clauses |
| `PSTALP:TX and CHANGECODE:223` | **52,138** | echoed the filter back byte-for-byte, HTTP 200, **and never applied the CHANGECODE clause** |

52,138 is exactly 35,239 (all TX) + 18,356 (all code 223) − 1,457 (the overlap). A filter
FDIC declines to honour is not an error; it is a different, larger answer, and you would
be billed for it. This actor asserts at runtime both that the echoed filter matches what
was sent **and** that every returned row satisfies the constraints you asked for, and
fails the run outright if either does not hold. Nothing is emitted and nothing is billed.

***

### Who it is for

| Persona | What they run |
|---|---|
| **Bank M\&A / corp-dev analyst** | `mode: "mergers"`, 365-day window, `enrichFinancials: true` — a deal book with both balance sheets, relative deal size, intrastate/interstate, and both holding companies. |
| **Community-bank strategist** | `mode: "mergers"` + `states` — who is consolidating your footprint, and how big the targets are. |
| **Bank vendor / fintech BD team** | `mode: "denovo"` — every new charter the moment FDIC records it. New banks buy core, compliance and card platforms. |
| **Risk / credit analyst** | `mode: "failures"` — failures, payoffs, conservatorships, open-bank assistance and assisted mergers, back to the 1930s if you widen the window. |
| **Branch-network / site-selection analyst** | `mode: "branches"` — openings, closings, purchases and sales with coordinates. |
| **Compliance / KYB pipeline** | `mode: "events"` — legal name changes, cert changes, charter-agency and primary-regulator moves. A bank that changed its name is not a new counterparty. |
| **Market researcher / journalist** | `mode: "rollup"` — consolidation counts by state, event type, quarter and acquirer. |
| **AI agent** | Any mode over MCP. Every field is described in the output schema, and every field says whether it was actually checked. |

***

### Example input

```json
{
  "mode": "mergers",
  "sinceDays": 365,
  "dateField": "processed",
  "states": ["TX", "OK"],
  "maxResults": 500,
  "enrichFinancials": true
}
```

Every input carries a working prefill and a default. Running with the defaults returns a
full year of US bank mergers, sized.

| Input | Type | Notes |
|---|---|---|
| `mode` | string | `events` · `mergers` · `denovo` · `failures` · `branches` · `rollup`. Defaults to `mergers`. |
| `sinceDays` | integer | Delta anchor. Ignored when `sinceDate` is set. Default 365. |
| `sinceDate` / `untilDate` | `YYYY-MM-DD` | Explicit window. `sinceDate` overrides `sinceDays`. |
| `dateField` | `processed` | `effective` | `processed` = FDIC's `PROCDATE`, the right anchor for delta monitoring. `effective` = `EFFDATE`, the legal effective date. |
| `states` | array | Two-letter USPS codes. Empty = all states and territories. |
| `changeCodes` | array | Explicit FDIC `CHANGECODE` list. Overrides the set implied by `mode`. |
| `maxResults` | integer | Hard row cap. Billing is per result, so this is also your cost ceiling. |
| `resolveMissingNames` | boolean | Backfill a counterparty name from `/banks/institutions` when the history row omits it. |
| `enrichFinancials` | boolean | Deal-size lookup (below). Default on. Costs about one extra request per 250 certificates. |

***

### What comes back

One row per structure-change event, or one row per aggregate bucket in `rollup` mode.
184 declared fields; a `mergers` run populates 162 of them.

#### Identity and classification

| Field | Notes |
|---|---|
| `eventId` | FDIC `ID`. On institution rows it decodes as `TRANSNUM_CHANGECODE_UNINUM_ACQUNINUM_OUTUNINUM`. Branch rows carry an opaque hash instead; the parser refuses to invent linkage from one. |
| `transactionNumber` | Shared by every record belonging to the same deal. |
| `changeCode`, `changeCodeDescription` | Raw FDIC code plus FDIC's own text. |
| `eventType`, `eventCategory` | Normalized snake\_case type and coarse category, decoded from all 41 change codes present in the live index. |
| `unmappedChangeCode` | `true` if FDIC publishes a code this build does not decode. Also raises a run-level warning. It is never silently bucketed as "other". |
| `effectiveDate`, `processedDate`, `eventQuarter`, `eventYear`, `processYear` | |
| `isMerger` · `isFailure` · `isAssisted` · `isDeNovoCharter` · `isNewCharter` · `isRelocation` · `isRegulatorChange` · `isClassChange` · `isNameChange` · `isVoluntaryLiquidation` · `isBranchEvent` · `isParticipantMirror` | Booleans, **null-safe**: `false` means FDIC published a 0; `null` means FDIC did not publish the flag at all. |

#### The institution the event happened to

`institutionName` · `cert` · `uninum` · `city` · `state` · `county` · `institutionClass` ·
`institutionClassDescription` · `charterAgency` · `regulator` · `organizationType` ·
`organizationTypeNumber` · `serviceType` · `trustPowers` · `insuranceFund` ·
`establishedDate` · `institutionEndDate` · `bankInsured` · `fdicRegion` · `latitude` ·
`longitude` · `reportType` · `fdicBankFindUrl`

#### Counterparties

Flat columns for CSV and Sheets, plus the full nested `acquirer` / `target` / `survivor`
objects for JSON consumers.

`acquirerName` · `acquirerCert` · `acquirerUninum` · `acquirerCity` · `acquirerState` ·
`acquirerCounty` · `acquirerClass` · `acquirerClassDescription` · `acquirerCharterAgency` ·
`acquirerRegulator` · `acquirerAddress` · `acquirerZip` · `acquirerLatitude` ·
`acquirerLongitude` · `acquirerFdicRegion` · `acquirerBranchesAcquired`
— the same set for `target*` and `survivor*`, plus `survivorChangeCode` and
`survivorChangeCodeDescription` (the mirror record on the surviving charter's timeline).

| Field | Notes |
|---|---|
| `linkageMethod` | *How* we looked: `acq_out_fields`, `id_uninum`, `institutions_lookup`, `none`. |
| `linkageStatus` | *Whether we found anything*: `resolved` · `partial` · `unresolved` · `not_applicable`. A merger with no named side reports `unresolved` — deliberately **not** the same value as a de-novo charter, which genuinely has no counterparty. |
| `survivorUninumSource` | FDIC does not publish `SUR_UNINUM` on this endpoint. When `SUR_CERT` matches `ACQ_CERT` the survivor's UNINUM is taken from `ACQ_UNINUM` and this field says so; otherwise `survivorUninum` stays null rather than being guessed. |

#### Deal size (`enrichFinancials`, new in v1.1)

`/banks/history` carries no financials. `/banks/institutions` retains the **final call
report of a bank that has already merged away** — Stellar Bank, CERT 58629, charter ended
2026-07-01, still reports $10.89 B assets and $9.04 B deposits as of 2026-03-31 — so a
completed deal can be sized on both sides.

For each of `institution`, `acquirer`, `target`, `survivor`:
`*AssetsUsdThousands` · `*DepositsUsdThousands` · `*Offices` · `*HoldingCompany` ·
`*HoldingCompanyRssd` · `*Specialization` · `*IsCommunityBank` · `*IsActive` ·
`*FinancialsAsOf` · `*CharterEndDate`

Plus `targetToAcquirerAssetsPct`, `combinedAssetsUsdThousands` and `dealGeography`
(`intrastate` / `interstate`).

**Assets and deposits are US dollar THOUSANDS.** `*HoldingCompanyRssd` is FDIC's
`RSSDHCR`, the regulatory high-holder pointer — note that `ULTCERT` is *not* a parent
pointer and is deliberately not used. A null financial figure means **not retrieved**,
never zero; `enrichmentStatus` on the same row tells you which.

#### Before / after

`former` and `current` objects, plus `changedAttributes` — an explicit list of only the
attributes that actually moved, e.g. `[{"attribute":"regulator","from":"FED","to":"FDIC"}]`
— and `activeFlags`, the FDIC boolean event flags set to 1 on the record.

#### Provenance — every row says what was and was not checked

| Field | Meaning |
|---|---|
| `historySourceStatus` | `ok`, or `partial` when a page failed after retries. |
| `nameLookupStatus` | `not_needed` · `ok` · `unavailable` · `disabled`. When `unavailable`, a blank `acquirerName` means "not resolved", not "no acquirer". |
| `enrichmentStatus` | `ok` · `partial` · `unavailable` · `disabled` · `not_applicable`. |
| `resultCompleteness` / `partialResult` | `complete` · `capped_by_maxresults` · `partial_upstream_error`. A partial run says so **on the row**, not only in the log. |
| `filterEchoVerified` | Always `true` on emitted rows — the run fails if it is not. |
| `queryFilters` | The exact FDIC filter expression sent, for audit against the echo. |
| `fdicIndexName` | The FDIC search index that served the run, e.g. `history_20260724090007`. |
| `fdicIndexMaxProcessedDate` | Newest `PROCDATE` anywhere in the index, from an unfiltered probe. |
| `pagesRequested` / `pagesFailed` / `rowsAvailableUpstream` | Fetch accounting. `rowsAvailableUpstream` is FDIC's `meta.total` for your query, before `maxResults` capping. |
| `queryMode`, `windowStart`, `windowEnd`, `retrievedAt`, `sourceUrl` | |

#### `rollup` mode

Buckets by `state`, `eventType`, `quarter` and `acquirer` (`dimension` + `key`), each with
`eventCount`, `distinctInstitutions`, `distinctAcquirers`, `distinctStates`,
`firstEventDate`, `lastEventDate`, and summed `targetAssetsUsdThousands` /
`acquirerAssetsUsdThousands` when enrichment ran.

`mergerCount`, `failureCount`, `deNovoCount`, `relocationCount`, `regulatorChangeCount`,
`classChangeCount` and `otherCount` are a **single exclusive chain** — each event lands in
exactly one, in that order — and always sum to `eventCount`. `assistedCount`,
`branchCount`, `nameChangeCount`, `unmappedCodeCount` and `linkageUnresolvedCount` are
**independent, non-exclusive** counters that deliberately overlap the chain. Every bucket
also carries the provenance fields above.

FDIC's `agg_by` returns HTTP 500 on this endpoint, so rollups are computed client-side
from the rows actually fetched. `eventsAnalyzed` tells you how many that was, and
`rowsAvailableUpstream` how many existed — if the second is larger, widen `maxResults`.

Note that `firstEventDate` / `lastEventDate` use the *effective* date while the default
window filters on the *processed* date, so a bucket can legitimately reach back years: a
relocation processed in 2026 may have taken effect in 2023.

***

### Reliability: what fails the run instead of billing you

Three assertions run **live, against the actual response**, on every execution. An offline
fixture cannot see a degraded upstream, because the fixture was shaped from it.

1. **Upstream health probe.** One unfiltered request. The national row count must sit
   inside a sane band (583,491 on 2026-07-28) and the newest `PROCDATE` must be within 60
   days. A truncated or frozen index fails the run.
2. **Echoed-filter assertion, on every page.** `meta.parameters.filters` must equal what
   was sent, byte for byte — including the enrichment requests to `/banks/institutions`.
3. **Row-level constraint check.** Every returned row must carry a `CHANGECODE` in the
   requested set, a `PSTALP` in the requested states, and a date inside the window (1-day
   tolerance). This is the gate that catches the lowercase-`and` case above, where the
   echo matched and the query still had not been applied. A row with no `PSTALP` at all is
   counted as unverifiable and logged, not treated as a violation.

Beyond that: an invalid `mode` is rejected at run creation (HTTP 400, nothing consumed);
an unreachable FDIC endpoint fails with the request URL and the last error; and if every
page fails, the run fails rather than emitting an empty success. Zero rows is reported as
a genuine empty result **only** when the health probe passed, the filter was echoed
intact, and FDIC honestly returned nothing.

An entirely empty input is not an error — the schema defaults apply (`mergers`, 365 days),
so `{}` returns a year of US bank mergers.

***

### Use as an MCP tool

Available to AI agents through `mcp.apify.com`. Every input and output field carries a
description, so an agent can pick a mode and read the result without extra prompting. The
provenance fields matter here in particular: an agent can distinguish a verified negative
(`false`) from an unchecked one (`null`), and will not report "no acquirer" when the
lookup merely failed.

Useful agent phrasings: *"which banks were acquired in Texas this year, and how big were
they"*, *"list new bank charters recorded since January"*, *"has this bank changed its
legal name or primary regulator"*, *"summarise US bank consolidation by state for the last
four quarters"*.

***

### Pricing

**$15 per 1,000 results**, billed as the standard Apify Result event
(`apify-default-dataset-item`) — one event per dataset item, which is one structure-change
event in the event modes and one bucket in `rollup`. Graduated Apify plan discounts apply
(Bronze −20 % through Diamond −70 %).

`maxResults` caps rows fetched *and* emitted, so it is also your cost ceiling. A failed run
emits nothing and bills nothing. The health probe and the enrichment lookups are requests,
not results — they are never billed.

***

### FAQ

**Where does the data come from?**
`https://api.fdic.gov/banks/history` for events and `https://api.fdic.gov/banks/institutions`
for the deal-size enrichment. Both are public and keyless. Do not use
`banks.data.fdic.gov/api/*` — it 302-redirects.

**How far back does it go?**
To the 1930s. Set `sinceDays` large, or use `sinceDate`. `failures` with a wide window
returns the full historical record of US bank failures.

**Why does a merger row show the acquired bank as `institutionName`?**
Because that is what the FDIC record is about: the change happened *to* the institution
that went away. The acquirer is in `acquirerName`, the surviving charter in `survivorName`.

**`FRM_INSTNAME` / `FRM_CERT` are missing on merger rows — is that a bug?**
It is FDIC. Those columns are absent on 100 % of sampled merger-family rows, not merely
null. The actor reconstructs the before-state from the `OUT_*` side and never zero-fills
the gap.

**Why are `organizationType`, `serviceType`, `institutionOffices` and `targetOffices` empty
on my merger run?**
Because FDIC does not publish them for those rows. `ORGTYPE` and `SERVTYPE_DESC` are absent
from merger-family history records — run `mode: "events"`, or `changeCodes: [360]`, and
they populate. `OFFICES` is omitted for closed charters, and in a merger the target *is* the
closed charter. Null means "FDIC does not have it here", not zero.

**Why does the same deal appear twice if I pass `changeCodes` by hand?**
FDIC writes an 8xx "Participated in…" mirror on the acquirer's timeline sharing the deal's
`TRANSNUM`. The institution-level modes exclude 810/811/812 for exactly this reason. If you
request them explicitly they are returned and flagged `isParticipantMirror: true`.

**Can I get quarterly totals without pulling every row?**
Not from FDIC — `agg_by` returns HTTP 500 on this endpoint. Use `mode: "rollup"`, which
aggregates client-side over the rows it fetched.

**Is the run charged if FDIC is down, or answers with the wrong universe?**
No. Every failure path calls `Actor.fail()` before anything reaches the dataset, and the
message ends with "Nothing was emitted and nothing was billed."

***

### Related actors

| Actor | Use it for |
|---|---|
| [FDIC Branch Network Churn Rollup](https://apify.com/malonestar/fdic-branch-network-churn-rollup) | Office-level entry and exit by bank and state, from a full chronological replay of state history — the branch-footprint counterpart to this actor's institution-level feed. |
| [FDIC Deposit Market Share Rollup (SOD)](https://apify.com/malonestar/fdic-sod-deposit-market-share-rollup) | County and MSA deposit share and HHI. Run it after a merger found here to see what the combined entity's concentration looks like. |
| [FDIC & NCUA Bank Health Rollup](https://apify.com/malonestar/fdic-ncua-health-rollup) | Capital, asset quality and CRE-concentration screening on the banks in this feed — peer cohorts, the 2006 CRE guidance two-prong test, and credit unions. |

A typical chain: **this actor** to find the deal → **health rollup** to underwrite both
sides → **SOD rollup** to check the deposit overlap → **branch churn** to see which offices
actually close.

# Actor input Schema

## `mode` (type: `string`):

Which slice of the FDIC structure-change stream to return. events = all institution-level changes; mergers = acquirer-to-target deals; denovo = brand-new bank charters; failures = failures and assisted resolutions; branches = branch openings/closings/sales; rollup = client-side aggregates per state, event type, quarter and acquirer.

## `sinceDays` (type: `integer`):

Delta anchor: return events from the last N days. Ignored when sinceDate is set. Use 30 for a monthly sweep, 365 for a full-year book of deals.

## `sinceDate` (type: `string`):

Explicit delta anchor in YYYY-MM-DD form, e.g. 2025-07-25. Overrides sinceDays. Only events on or after this date are returned.

## `untilDate` (type: `string`):

End of the window in YYYY-MM-DD form, e.g. 2026-07-25. Defaults to today.

## `dateField` (type: `string`):

Which FDIC date the window applies to. 'processed' uses PROCDATE (when FDIC recorded the change, best for delta monitoring); 'effective' uses EFFDATE (the legal effective date of the deal).

## `states` (type: `array`):

Optional list of two-letter USPS state codes to restrict the sweep to, e.g. \["TX","OK"]. Leave empty for all states and territories.

## `changeCodes` (type: `array`):

Optional explicit list of FDIC CHANGECODE numbers, e.g. \[223,213] for unassisted and assisted mergers. Overrides the code set implied by mode. See the README for the full 41-code vocabulary.

## `maxResults` (type: `integer`):

Hard cap on how many event rows are fetched and emitted. Billing is per result, so this is also your cost ceiling.

## `resolveMissingNames` (type: `boolean`):

When an acquirer or target name is absent from the history record, do a follow-up /banks/institutions lookup by CERT to fill it in. Adds one extra request per run.

## `enrichFinancials` (type: `boolean`):

Look up total assets, deposits, offices, holding company (NAMEHCR/RSSDHCR) and specialization for the subject, acquirer, target and surviving charter from FDIC /banks/institutions. FDIC keeps the final call report of a bank that has already merged away, so completed deals get sized on both sides. Costs roughly one extra request per 250 certificates. Turn off for the fastest possible delta sweep.

## Actor input object example

```json
{
  "mode": "mergers",
  "sinceDays": 365,
  "dateField": "processed",
  "states": [],
  "changeCodes": [],
  "maxResults": 500,
  "resolveMissingNames": true,
  "enrichFinancials": true
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# 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 = {
    "mode": "mergers",
    "sinceDays": 365,
    "maxResults": 500,
    "enrichFinancials": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("malonestar/fdic-structure-change-delta-monitor").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 = {
    "mode": "mergers",
    "sinceDays": 365,
    "maxResults": 500,
    "enrichFinancials": True,
}

# Run the Actor and wait for it to finish
run = client.actor("malonestar/fdic-structure-change-delta-monitor").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "mode": "mergers",
  "sinceDays": 365,
  "maxResults": 500,
  "enrichFinancials": true
}' |
apify call malonestar/fdic-structure-change-delta-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=malonestar/fdic-structure-change-delta-monitor",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/PxkJMb7NEnFbfTglG/builds/Y1kFCAIl593gUmbgy/openapi.json
