current_balance, plus its transactions through GET /accounts/{id}/transactions. This guide explains how fresh that data is, how to tell, and how to pull fresher data on demand. For what current_balance means and how it is signed, see Asset vs liability.
available_balance is reported by MX only, and only for some accounts: it is common on checking and rare on credit cards. Read realized_capabilities.available_balance rather than assuming it is present.
How fresh is GET /accounts?
The balances you read are a cached snapshot from the last successful refresh — not a live call to the bank on every request. LedgerSync refreshes every active connection automatically about once a day, so under normal conditions the numbers are less than 24 hours old.
Every Account carries a last_refreshed_at timestamp — the moment its data was last refreshed. Read it to decide whether you already have fresh-enough data or need to force a refresh.
For most use cases you do not need to call
/refresh. The daily auto-refresh keeps active connections current, and GET /accounts always returns the latest snapshot. Reach for /refresh only when you need fresher-than-daily data.How far back does the history go?
Freshness is one axis; depth is the other. Every Account carries anoldest_transaction_date: the oldest transaction we actually hold for it.
It is measured from the transactions themselves, not copied from what the bank or the aggregator says it has. So it is exactly the minimum date you get if you page that account’s full history, and the two can never disagree.
What we request at connect
After that first pull, history grows forward with each refresh. It does not reach further back, so an account linked a year ago holds about a year.
What actually arrives inside the requested window is up to the institution, and it is not something we can tell you before the connection exists. That is what the field is for: read
oldest_transaction_date per account instead of assuming a window. It is null when we hold no transactions for that account yet, which is normal between linking a connection and its first transaction pull finishing.
oldest_transaction_date can move backwards. It is a fact about what we hold right now, and a backfill landing earlier transactions lowers it. Re-read it rather than caching it as a fixed property of the account.Transaction.date, it is a calendar day (YYYY-MM-DD) with no time component, and it is truncated by the same code, so comparing the two is safe.
Forcing a refresh
When you need up-to-the-moment data (a user tapped “refresh”, you’re reconciling something time-sensitive), callPOST /connections/{id}/refresh:
202 Accepted with an operation_id and starts the refresh asynchronously, so balances are not updated by the time the call returns. All three connectable sources (Finicity, MX, and FDE) answer this call the same way: 202 now, the outcome later on a webhook.
1
POST /connections/{id}/refresh
You get
202 and an operation_id.2
Wait for the account.refresh.completed webhook
It fires when the fresh data is in (or
account.refresh.failed on a terminal failure). Listen for the webhook — don’t poll GET /accounts in a loop.3
Re-read GET /accounts (and /transactions)
The snapshot now reflects the fresh pull and
last_refreshed_at has advanced.account.refresh.completed / account.refresh.failed payload shapes, and the Refresh a connection endpoint in the API Reference for the full contract.
What the operation tells you
The webhook is the signal to build on, but theoperation_id is a real fallback
when you can’t run a webhook handler: GET /v3/operations/{operation_id} reaches a
terminal succeeded or failed, and always terminates even if the events never
arrive.
A refresh is one aggregation at the bank but reports per sub-account, so the
operation stays queued until every account on the connection has reported. That
matters for how you read the verdict:
succeeded- every account refreshed.result.accounts[]lists them, andresult.accounts_refreshedcounts them.failedwithrefresh_partially_failed- the aggregation ran, but at least one account didn’t come back. This is deliberately a failure rather than a partial success, so branching onstatusalone can’t quietly ship a connection that’s missing an account’s transactions.error.accounts[]breaks it down per account.failedwithrefresh_timed_out- nothing reported in time. The refresh may still have run; checklast_refreshed_at.
estimated_seconds on the 202 is
a best-effort floor, not a deadline.
See the connection refresh error codes for the
full list.
MX aggregates on its own schedule, several times a day, and refreshes through this same async webhook flow. A refresh you request triggers an MX aggregation, and the result arrives on the
account.refresh.completed webhook, so the shape of the call matches Finicity and FDE.What is not the same is what comes back. An MX aggregation re-reads the member’s full account list, so it also picks up accounts the client opened after linking. FDE does the same on its extraction run. A Finicity refresh does not, and no amount of refreshing will surface an account the client never shared. See Adding a new account.Refreshing many connections at once
You can refresh all of a customer’s banks in one batch - each connection is a separatePOST /connections/{id}/refresh call, and that’s well within the request-rate limit (live keys allow 60 requests/minute).
Refresh also carries a daily budget of its own, separate from that request rate, because every refresh is a real aggregation at the institution rather than just another API call. Exceed it and the call returns 429 rate_limit_exceeded without queueing anything or touching the bank.
- Fire the batch, then wait on the
account.refresh.completedwebhooks — don’t poll operations or/accountsin a tight loop. - Watch the
X-RateLimit-*headers and back off before you hit zero — see Rate limits. - Each refresh triggers a real aggregation at the bank, so space large batches out rather than firing hundreds at once.
- Don’t put
/refreshon a timer. Active connections already refresh once a day on their own, and our upstream provider’s terms don’t permit client apps automating calls to the refresh services. Refresh when something asked for it - a user tapped refresh, or you’re reconciling something time-sensitive.
