Call.Report
GitHub
GitHub

ffiec-data-connect (Python)

Migration from v2

ffiec-data-connect v3.0.0 removes SOAP, changes the calling convention, and requires OAuth2 + JWT. What to update and why.

ffiec-data-connect v3.0.0 (April 2026) is a breaking release driven by FFIEC’s retirement of the SOAP webservice. For most users the migration is a credentials-type swap plus dropping the session argument. A handful of v2 parameter combinations that were silently broken (output_type="bytes", polars without the polars extra, date_output_format="python_format" on three methods) now raise ValidationError instead of failing silently — §8 below covers these. If you were on v2.x, read this entire page before upgrading.

The two big changes

  1. SOAP is gone. The FFIEC shut down the SOAP webservice on February 28, 2026. v3.0.0 removes client-side SOAP support entirely. WebserviceCredentials and FFIECConnection now raise SOAPDeprecationError on instantiation.
  2. New calling convention. Old code called collect_data(session, creds, ...). New code calls collect_data(creds, ...) — no session argument. The old form still works but emits a DeprecationWarning.

Step-by-step

1. Switch credentials

# Before (v2.x, SOAP)
from ffiec_data_connect import WebserviceCredentials
creds = WebserviceCredentials(username="...", password="...")

# After (v3.0.0, REST)
from ffiec_data_connect import OAuth2Credentials
creds = OAuth2Credentials(username="...", bearer_token="eyJhbGci...")

Generate the JWT token from the CDR portal. See Authentication.

2. Drop the session argument

# Before
from ffiec_data_connect import FFIECConnection, collect_data
session = FFIECConnection()
data = collect_data(session, creds, reporting_period="...", rssd_id="...", series="call")

# After
from ffiec_data_connect import collect_data
data = collect_data(creds, reporting_period="...", rssd_id="...", series="call")

The old form with explicit None session still works — collect_data(None, creds, ...) — but it emits a DeprecationWarning. As of 3.0.0rc4, the keyword form from old 2.x docs also works (same warning): collect_data(session=None, creds=creds, ...). The library throws SOAPDeprecationError if you pass a non-None session.

3. Review default null handling

v3.0.0 defaults to pd.NA for null values (was np.nan under SOAP in v2). Integer columns now stay as Int64 (display: 100) instead of being cast to float64 (display: 100.0). If downstream code assumed np.nan, set force_null_types="numpy" explicitly.

data = collect_data(
    creds,
    rssd_id="480228",
    reporting_period="12/31/2025",
    series="call",
    force_null_types="numpy",  # restore v2 SOAP behavior
)

For new code, leave this at the default.

4. Update dependency pins

v3.0.0 dropped zeep and requests from its dependencies. If your project transitively depended on them through this library, pin them yourself.

Relaxed upper bounds:

  • httpx now <2.0.0 (was <1.0.0)
  • polars now <2.0.0 (was <1.0.0)

5. Check your error handling

v3.0.0 emits typed exceptions by default (previously legacy mode re-raised as ValueError). If your except ValueError blocks were catching FFIEC errors, either:

  • Switch to specific types (CredentialError, RateLimitError, etc.), or
  • Re-enable legacy mode temporarily:
    import ffiec_data_connect
    ffiec_data_connect.enable_legacy_mode()

6. token_expires no longer needed (3.0.0rc4)

OAuth2Credentials now auto-detects expiry from the JWT’s exp claim. Passing token_expires=... still works but emits a DeprecationWarning, and the supplied value is ignored — the JWT’s own claim is authoritative. Drop the argument from your constructor call:

# Before
creds = OAuth2Credentials(
    username="...", bearer_token="eyJ...",
    token_expires=datetime.now() + timedelta(days=90),   # guess — discarded
)

# After
creds = OAuth2Credentials(username="...", bearer_token="eyJ...")

7. New output formats: xbrl and pdf (3.0.0rc4)

collect_data and collect_ubpr_facsimile_data now accept output_type="xbrl" to return raw UTF-8 XBRL bytes. collect_data additionally accepts output_type="pdf" for an auditor-readable PDF. The old catch-all output_type="bytes" is deprecated — use "xbrl" explicitly. See Output formats for the full matrix.

8. Silent no-ops from v2 now raise (3.0.0rc4 – rc6)

Several parameter combinations that silently did nothing in v2 now fail loudly. These are almost certainly bugs in your v2 code — they “worked” only in the sense that they didn’t crash.

v2 behavior3.0.0 behaviorFix
collect_data(..., output_type="bytes")None returnedRaises ValidationError (rc4)Use output_type="xbrl" or "pdf"
collect_reporting_periods(..., output_type="bytes") (and the four other list-returning methods) → list returned, "bytes" ignoredRaises ValidationError (rc4)Those methods have no raw-bytes representation; drop the arg
collect_*(..., output_type="polars") when polars extra not installed → Python list returned silently (5 methods)Raises ValidationError (rc6)pip install 'ffiec-data-connect[polars]' or switch to "list" / "pandas"
collect_reporting_periods(..., date_output_format="python_format") (and two other methods) → strings returned, parameter ignoredReturns datetime objects (rc6)If you had a datetime.strptime(...) workaround, delete it

9. python_format is now tz-aware — America/New_York (3.0.0rc6)

Everywhere the library returns a datetime under date_output_format="python_format" — the three methods listed above and collect_data’s quarter column — the object carries tzinfo=ZoneInfo("America/New_York"). FFIEC publishes all timestamps in Washington, DC local wall-clock time but sends no tz marker on the wire, so the library attaches one for you. DST is honored: July dates carry EDT (UTC-4), January dates EST (UTC-5).

This is new behavior — no v2 code ever got here (the parameter was a silent no-op in v2). If you had a workaround that parsed the returned strings into naive datetimes, deleting the workaround is not enough: any downstream comparison with a naive datetime will now raise TypeError: can't compare offset-naive and offset-aware datetimes. Either attach a tz to your naive values, or call .replace(tzinfo=None) on the library’s output to go back to naive.

# Mixing with naive datetimes — does NOT work:
submissions = collect_filers_submission_date_time(creds, ...,
                                                  date_output_format="python_format")
submissions[0]["datetime"] < datetime(2026, 1, 1)   # TypeError

# Compare tz-aware against tz-aware (recommended):
from datetime import datetime
from zoneinfo import ZoneInfo
submissions[0]["datetime"] < datetime(2026, 1, 1, tzinfo=ZoneInfo("America/New_York"))

# Or strip the tz if you really want naive:
submissions[0]["datetime"].replace(tzinfo=None) < datetime(2026, 1, 1)

10. except ConnectionError: may now miss some errors (3.0.0rc6)

rc6 narrowed the exception wrapping in methods_enhanced.py. Previously, an unexpected AttributeError, KeyError, or TypeError inside the REST code path would be caught and re-raised as ConnectionError — which misled users into thinking FFIEC was down when the real problem was a library bug or API shape drift. Typed FFIECError subclasses and (in default legacy error mode) their ValueError equivalents were also wrapped.

# v2 behavior:
try:
    data = collect_data(creds, ...)
except ConnectionError:
    handle_network_failure()   # caught AttributeError too!

# 3.0.0rc6 behavior:
try:
    data = collect_data(creds, ...)
except ConnectionError:
    handle_network_failure()   # only catches actual network issues now
except (AttributeError, KeyError, TypeError):
    # programming errors — no longer hidden behind ConnectionError
    raise

If your v2 code relied on the wrap (e.g., to suppress transient API hiccups that surfaced as AttributeError), add explicit handlers for those types.

Legacy-mode specific improvement. In default legacy error mode (FFIEC_USE_LEGACY_ERRORS=true), typed errors like ValidationError are surfaced as plain ValueError. Prior to rc6 the narrowing only applied to typed subclasses, so a legacy-mode ValueError (e.g. from the polars-missing check) got double-wrapped into "Failed to retrieve reporting periods via REST API: Polars not available" — a misleading message that sounds like a network failure. rc6 extends the narrowing so ValueError is re-raised untouched in legacy mode, surfacing the clean "Polars not available" message in both modes. If your v2 code matched on the "Failed to retrieve … via REST API" prefix for parameter-validation errors, it will need updating — legacy-mode validation errors now arrive without that prefix.

What’s still the same

  • rssd_id is still a string. Pass "480228", not 480228.
  • Reporting periods are still MM/DD/YYYY.
  • The output_type / date_output_format parameters are unchanged (but output_type gained "xbrl" and "pdf" in 3.0.0rc4).
  • collect_reporting_periods, collect_filers_on_reporting_period, and collect_filers_submission_date_time all keep their v2 signatures (minus the session).
  • Field-name duals ("rssd" and "id_rssd") are still both populated.

UBPR

UBPR access in v2 was SOAP-only. v3.0.0 adds REST UBPR:

from ffiec_data_connect import collect_ubpr_reporting_periods, collect_ubpr_facsimile_data

periods = collect_ubpr_reporting_periods(creds, output_type="list")
ubpr = collect_ubpr_facsimile_data(
    creds, rssd_id="480228", reporting_period=periods[-1], output_type="pandas",
)

What to delete

Search your codebase for:

  • WebserviceCredentials → replace with OAuth2Credentials.
  • FFIECConnection() → delete.
  • auth_method="oauth", client_id=, client_secret= — these were never real parameters. Any doc or example that used them was wrong.
  • FfiecDataConnect class — never existed in a real released version.
  • get_call_report(...), get_ubpr_ratio(...), client.get_* — those are fabricated method names. The real functions are collect_*.

If you’re porting from an older Call.Report doc that used those names, assume the entire code snippet is wrong and rewrite against the real library.

See also

navigate · open · Esc close