Call.Report
GitHub
GitHub

ffiec-data-connect (Python)

Bulk download

Patterns for pulling many institutions or many periods without tripping the 2500/hour rate limit.

The REST API serves one institution’s facsimile per call. For multi-institution work you’ll make many calls — the shape of your loop determines whether you finish in minutes or hit a 429.

The rate budget

The REST API allows 2,500 requests per hour per user on a rolling basis. Every collect_data(...) call and every collect_ubpr_facsimile_data(...) call counts as one request. collect_filers_on_reporting_period(...) and collect_reporting_periods(...) count too.

Back-of-envelope: pulling one period for 5,000 banks at sequential pace hits the limit hard. You need either pacing, async concurrency, or both.

Pattern 1: synchronous with pacing

Simple, slow, never 429s:

import time
from ffiec_data_connect import OAuth2Credentials, collect_data, collect_filers_on_reporting_period

creds = OAuth2Credentials(...)
period = "12/31/2025"

# Get the full panel
panel = collect_filers_on_reporting_period(creds, reporting_period=period, series="call")
rssds = [p["id_rssd"] for p in panel]

# ~1.5 seconds per call = ~2400/hour, safely under 2500
interval = 1.5
results = []
for rssd in rssds:
    results.append(collect_data(creds, rssd_id=rssd, reporting_period=period, series="call",
                                output_type="list"))
    time.sleep(interval)

Best for: one-off ad-hoc pulls, low-volume scripts, demos.

Pattern 2: RateLimiter + threads

ffiec_data_connect.RateLimiter wraps a rolling-window counter. Combine it with a thread pool for concurrency without 429s.

from concurrent.futures import ThreadPoolExecutor, as_completed
from ffiec_data_connect import RateLimiter, collect_data

limiter = RateLimiter(max_requests=2400, window_seconds=3600)  # under the 2500 ceiling

def fetch(rssd: str) -> list[dict]:
    limiter.wait_if_needed()
    return collect_data(creds, rssd_id=rssd, reporting_period=period, series="call",
                        output_type="list")

with ThreadPoolExecutor(max_workers=8) as pool:
    futures = {pool.submit(fetch, r): r for r in rssds}
    for fut in as_completed(futures):
        rssd = futures[fut]
        try:
            rows = fut.result()
            # ... persist rows
        except Exception as exc:
            print(f"{rssd}: {exc}")

Best for: batch pipelines, backfills, anywhere you have 1–several thousand RSSDs.

Pattern 3: AsyncCompatibleClient

For async codebases (FastAPI, Prefect, etc.), use the built-in client:

import asyncio
from ffiec_data_connect import AsyncCompatibleClient, RateLimiter

async def run():
    limiter = RateLimiter(max_requests=2400, window_seconds=3600)
    async with AsyncCompatibleClient(creds, rate_limiter=limiter) as client:
        tasks = [
            client.collect_data_async(
                rssd_id=rssd, reporting_period=period, series="call",
                output_type="list",
            )
            for rssd in rssds
        ]
        return await asyncio.gather(*tasks, return_exceptions=True)

results = asyncio.run(run())

The client handles pacing internally when given a RateLimiter. See Async and rate limits for the full treatment.

Storing results

If you’re pulling many periods × many banks, don’t hold it all in memory. Stream to disk as you go.

Parquet per (period, rssd) works well for large panels:

import pandas as pd
from pathlib import Path

out = Path("data/call")
out.mkdir(parents=True, exist_ok=True)

for rssd in rssds:
    rows = collect_data(creds, rssd_id=rssd, reporting_period=period, series="call",
                        output_type="list")
    pd.DataFrame(rows).to_parquet(out / f"{period.replace('/', '-')}_{rssd}.parquet")

When to use bulk ZIPs instead

The FFIEC CDR publishes per-quarter bulk ZIPs containing every institution’s facsimile. If your scope is the entire industry for one quarter, the bulk ZIP is almost always faster than iterating the REST API. See Bulk files.

Use the REST API when your scope is a specific set of institutions or the latest amendments for an institution you already track.

See also

navigate · open · Esc close