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
- SOAP is gone. The FFIEC shut down the SOAP webservice on February 28,
2026. v3.0.0 removes client-side SOAP support entirely.
WebserviceCredentialsandFFIECConnectionnow raiseSOAPDeprecationErroron instantiation. - New calling convention. Old code called
collect_data(session, creds, ...). New code callscollect_data(creds, ...)— nosessionargument. The old form still works but emits aDeprecationWarning.
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:
httpxnow<2.0.0(was<1.0.0)polarsnow<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 behavior | 3.0.0 behavior | Fix |
|---|---|---|
collect_data(..., output_type="bytes") → None returned | Raises ValidationError (rc4) | Use output_type="xbrl" or "pdf" |
collect_reporting_periods(..., output_type="bytes") (and the four other list-returning methods) → list returned, "bytes" ignored | Raises 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 ignored | Returns 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_idis still a string. Pass"480228", not480228.- Reporting periods are still
MM/DD/YYYY. - The
output_type/date_output_formatparameters are unchanged (butoutput_typegained"xbrl"and"pdf"in 3.0.0rc4). collect_reporting_periods,collect_filers_on_reporting_period, andcollect_filers_submission_date_timeall keep their v2 signatures (minus thesession).- 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 withOAuth2Credentials.FFIECConnection()→ delete.auth_method="oauth",client_id=,client_secret=— these were never real parameters. Any doc or example that used them was wrong.FfiecDataConnectclass — never existed in a real released version.get_call_report(...),get_ubpr_ratio(...),client.get_*— those are fabricated method names. The real functions arecollect_*.
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
- Upstream
CHANGELOG.md. - Upstream
MIGRATION.md— detailed SOAP→REST walkthrough. - Authentication — JWT setup.