code.
Branch on the code, show your user something friendly, log the trace id, and
consult the catalog below for the exact action. That’s the whole game.
The error envelope
Every error response, validation, auth, not found, rate limit, or a bank-side failure, carries the same JSON body undererror:
code, message, doc_url, type, category and is_user_actionable are on
every error. The rest appear only when they apply.
HTTP status, at a glance
Error-code catalog
Every code the API can return. The catalog is also browsable at portal.ledgersyncappv2.com/errors, and eacherror.doc_url deep-links to one entry.
Authentication & authorization
The LedgerSync MCP also answers with
access_request_not_approved when the user already has a LedgerSync account and their application for
that environment is under review, needs more information or has not finished its payment step, or when
their access for that environment was turned off (a first application still under review answers
access_not_active), and with billing_not_configured when live access was approved but no card is on
file (the payment step was not completed). Its errors reach the AI app as a tool error carrying the same
code; access and rate-limit errors also carry a next_step sentence the AI reads out to the user. So do
the desktop app linking errors.
Request problems
Rate limit
Connection refresh
Outcomes ofPOST /connections/{id}/refresh. Like statement_extraction_failed,
these arrive inside the polled operation’s error rather than as the HTTP
response - the call itself answers 202 and the verdict lands later.
A refresh is one aggregation at the bank but reports per sub-account, so the
operation only turns terminal once every account on the connection has reported.
When it does, error.accounts[] lists each account with its own status
(succeeded / failed / pending) and, where it failed, its own nested error.
refresh_timed_out and refresh_outcome_not_observable look alike and mean
different things. The first is “the bank or our pipeline was slow”, and retrying
can help. The second is a configuration gap on our side that retrying will never
clear - chasing the institution over it would send you somewhere there is nothing
to find.Server
Connection & source errors (the bank side)
These come back on connection operations, most often inside the polled operation’serror, with the raw upstream code in error.source_diagnostic_code.
All carry type: api_error and HTTP 502, so you must branch on code, not
on type. They group cleanly by the action they require:
Send the end user back through the widget (re-authenticate):
Don’t re-auth, retry with backoff (credentials are fine):
Don’t retry, the capability isn’t there:
User must act at the bank (not in your widget):
The firm’s LedgerSync desktop app is needed (FDE banks only, the bank was not called):
Some banks LedgerSync reaches through FDE need the LedgerSync desktop app on the firm’s computer for
refreshes. Those institutions and connections carry
requires_desktop_app: true. Both codes below are
CONNECTION_ERROR with is_user_actionable: true. A refresh stopped by either one fails at once and does
not count against the daily refresh budget.
Desktop app linking (LedgerSync MCP only)
These come only from the LedgerSync MCP’sledgersync_pair_desktop_app
tool, which links the LedgerSync desktop app with the code the app shows. Each one reaches the AI app as a
tool error with a next_step the AI reads out. None of them repeats the code.
A handling pattern
The same steps work in any language: check the status, parse the envelope, branch oncode, log the trace id.
Rate limiting
A429 (rate_limit_exceeded) just means slow down. Use exponential backoff
with jitter, and honor Retry-After when present.
The trace id
Every response, success or error, includes anX-LS-Trace-Id header (also
mirrored as error.trace_id in error bodies):
Getting help
When you file a ticket, include: the
X-LS-Trace-Id (or error.trace_id),
the error.code, the endpoint, and whether you were in sandbox or live.
That’s everything we need to find your exact request.Testing in sandbox
Reproduce every error path on purpose with sandbox test banks.
Connection lifecycle
Most bank-side codes surface as a
connection.failed transition.