Plaid Integration#
Sure integrates with Plaid as a banking and investment data provider. The integration covers depository, credit, investment, and loan accounts across US/CA and EU regions . The top-level API client lives in Provider::Plaid, which wraps the Plaid Ruby SDK. All orchestration flows through PlaidItem, the ActiveRecord model that represents a single Plaid Link connection (one access_token per institution) .
Key Models#
| Model | File | Role |
|---|---|---|
Provider::Plaid | app/models/provider/plaid.rb | Thin API client; all Plaid SDK calls live here |
PlaidItem | app/models/plaid_item.rb | One Plaid Link connection (access token, status, cursor) |
PlaidAccount | app/models/plaid_account.rb | Raw Plaid account data + link to Sure Account |
PlaidItem::Syncer | app/models/plaid_item/syncer.rb | Orchestrates the 5-phase sync pipeline |
PlaidItem::Importer | app/models/plaid_item/importer.rb | Fetches API data and stores raw payloads |
PlaidItem::AccountsSnapshot | app/models/plaid_item/accounts_snapshot.rb | Item-level fetch wrapper; scopes data per account |
PlaidAccount::Importer | app/models/plaid_account/importer.rb | Persists raw payloads to each PlaidAccount |
PlaidAccount::Processor | app/models/plaid_account/processor.rb | Maps Plaid data → Sure domain objects |
Sync Pipeline (5 Phases)#
PlaidItem::Syncer#perform_sync drives each sync in sequence :
- Import —
plaid_item.import_latest_plaid_datacallsPlaidItem::Importer, which fetches item metadata and all account data from the Plaid API and stores raw payloads . - Process accounts —
plaid_item.process_accountsrunsPlaidAccount::Processorfor everyPlaidAccount, creating or updating SureAccountrecords and theAccountProviderjoin . - Setup stats — counts unlinked accounts and sets
pending_account_setupon thePlaidItem. - Schedule account syncs — for every linked account, calls
account.sync_later(...)to calculate historical balances . - Collect stats — aggregates transaction, holdings, and health metrics onto the
Syncrecord .
Data Fetching & Pagination#
All Plaid API calls are made at the item level by PlaidItem::AccountsSnapshot, then scoped down per account .
Transactions (cursor-based)#
Regular transactions use Plaid's transactions_sync endpoint with a cursor: the loop runs until has_more is false, accumulating added, modified, and removed arrays . The cursor is saved to PlaidItem.next_cursor at the end of each successful import , so subsequent syncs are incremental. History at link time is bounded by MAX_HISTORY_DAYS — 730 days in production, 90 in development .
Fetching is gated: transactions are only requested if the item supports_product?("transactions") and has at least one account .
Investment Transactions (offset-based)#
Investment transactions use investments_transactions_get with offset pagination — a date window (start_date/end_date) is fixed at call time, and the loop increments offset until transactions.length >= response.total_investment_transactions . When no start_date is provided, it falls back to MAX_HISTORY_DAYS.days.ago . Holdings and their securities are fetched in the same get_item_investments call and merged with transaction securities .
Investment data is only fetched when supports_product?("investments") is true and there is at least one account of type == "investment" .
PlaidAccount → Account Linkage#
Each PlaidAccount can link to a Sure Account via two paths :
- New path:
has_one :account_provider, as: :provider→has_one :linked_account, through: :account_provider(theAccountProviderjoin record). - Legacy path:
has_one :account, foreign_key: :plaid_account_id(being migrated out).
current_account returns linked_account || account, preferring the new path .
Impact on sync failures: PlaidItem#accounts resolves only PlaidAccount records where current_account is non-nil . If process_account! fails for a PlaidAccount (e.g., due to a processing error), that account won't be linked and schedule_account_syncs will skip it — its balance history won't be recalculated. The Syncer tracks this by counting plaid_accounts where current_account.nil? and setting pending_account_setup: true on the item .
process_account! halts the entire processor on failure; process_transactions, process_investments, and process_liabilities each fail independently — exceptions are swallowed, reported to Sentry, and processing continues .
Error Handling#
PlaidItem::Importer catches Plaid::ApiError at the import boundary :
ITEM_LOGIN_REQUIRED: setsplaid_item.status = :requires_updateand swallows the error (sync is considered succeeded) .- All other errors: re-raised, propagating to the
Syncrecord and marking it failed.
PlaidItem#remove_plaid_item (called on before_destroy) ignores ITEM_NOT_FOUND, INVALID_API_KEYS, INVALID_CLIENT_ID, and INVALID_SECRET from Plaid when deleting, so local cleanup proceeds regardless .
PlaidItem#get_update_link_token catches ITEM_NOT_FOUND on re-link attempts, sets status: :requires_update, and returns nil so callers can render a friendly error instead of crashing .
Rate Limiting#
Provider::Plaid itself does not include Provider::RateLimitable. The RateLimitable concern is a shared mixin for providers that need interval-based throttling (e.g., enforcing a MIN_REQUEST_INTERVAL between calls via throttle_request). Rate limit handling for Plaid is instead governed by Plaid's API itself; errors surface as Plaid::ApiError and propagate through the standard error chain.
Products & Region Support#
Provider::Plaid supports three products: transactions, investments, and liabilities . The primary product selected at link time depends on the account type being connected — investment accounts get investments, credit/loan accounts get liabilities, everything else gets transactions . EU items are always linked with transactions as the primary product regardless of type .
Country codes differ by region: US/CA for the us region; 15 EU countries for the eu region . Webhooks are verified via JWT/ES256 with a 5-minute expiry window .
Related Topics#
- Banking Provider Integration — shared
Syncablemixin and sync infrastructure used by all providers including Plaid - Pending Transaction Reconciliation — how Plaid's
pending_transaction_idenables high-confidence pending→posted matching - Transaction Sync Windows and Lookback — cursor vs. date-window strategies across providers
- SnapTrade Integration — separate brokerage provider, distinct from Plaid's investment data pipeline