This guide walks you through linking your first bank account and pulling real transaction data using the LedgerSync API — from your first sandbox key to a live data response. The full flow takes about 15 minutes in sandbox.
Sandbox base URL: https://api-sandbox.ledgersyncappv2.com/v3
Auth: Authorization: Bearer sk_test_...
Tip: Every response includes an X-LS-Trace-Id header. Include it in any support request.
Four objects, one line of descent. A Client is your record of one end-user. Each Client owns one or more Connections (one per linked bank). Each Connection exposes Accounts, and each Account holds Transactions and Statements.
Two important things to know upfront:
source parameter.Log in to the developer portal at portal.ledgersyncappv2.com, go to API Keys, and create a sandbox key. Set it in your terminal:
export LS_KEY="sk_test_your_key_here" export LS_BASE="https://api-sandbox.ledgersyncappv2.com/v3"
Confirm authentication:
curl "$LS_BASE/institutions?q=chase" \ -H "Authorization: Bearer $LS_KEY"
A 401 response means a bad or missing key.
Bank connections complete asynchronously. Subscribe to webhooks so LedgerSync notifies you the moment a connection goes active — no polling required.
The easiest path is the Webhooks page in the portal. Or do it via API:
curl -X POST "$LS_BASE/webhooks/subscriptions" \
-H "Authorization: Bearer $LS_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/webhooks/ledgersync",
"event_types": ["connection.active", "connection.failed", "connection.disconnected", "account.refresh.completed"]
}'The response includes a signing_secret — shown only once. Save it immediately. You will use it to verify every incoming delivery.
A Client represents one end-user. Set external_id to your own internal user ID so you can cross-reference without storing LedgerSync IDs everywhere.
curl -X POST "$LS_BASE/clients" \
-H "Authorization: Bearer $LS_KEY" \
-H "Content-Type: application/json" \
-d '{ "external_id": "user_4820", "name": "Acme Bookkeeping LLC" }'Save the returned id (e.g., cli_01HXYZ...) — every subsequent read is scoped to it.
Search the institution catalog to get the institution_id you want to connect. Each result includes a capabilities block showing what the bank supports (transactions, statements, check images).
curl "$LS_BASE/institutions?q=FinBank" \ -H "Authorization: Bearer $LS_KEY"
For sandbox testing, search for FinBank — it supports transactions and statements with no MFA required.
Create a Connection under your Client with just the institution_id. No source selection, no credentials.
curl -X POST "$LS_BASE/clients/cli_01HXYZ.../connections" \
-H "Authorization: Bearer $LS_KEY" \
-H "Content-Type: application/json" \
-d '{ "institution_id": "ins_0a01a5430925d0b2" }'You receive a 202 Accepted and an operation_id. Poll it until status is succeeded, then extract the widget_url from the result.
Important: The connection ID at this stage is a placeholder. The canonical ID (e.g., con_FINICITY_41294) is only available after the user completes the widget and connection.active fires.
Open the widget_url in the user's browser — redirect, iframe, or webview. The user signs in to their bank and authorizes their accounts. Their credentials go directly to the bank; you never see them.
Don't want to build a bank picker? Use the hosted connect link instead: POST /v3/clients/{id}/connect-session returns a LedgerSync-hosted URL you can email to the user. They search, pick, and connect their own bank with no front-end work on your side.
When the user finishes, LedgerSync fires connection.active to your webhook endpoint with the canonical connection ID. Save that ID against your user record.
Then read accounts and transactions:
# List accounts curl "$LS_BASE/accounts?client_id=cli_01HXYZ...&connection_id=con_FINICITY_41294" \ -H "Authorization: Bearer $LS_KEY" # List transactions curl "$LS_BASE/accounts/acc_FINICITY_889201/transactions?client_id=cli_01HXYZ...&from=2026-01-01" \ -H "Authorization: Bearer $LS_KEY"
Transaction and account IDs encode their source (acc_FINICITY_..., txn_MX_...), but the response shape is identical regardless of which aggregator served them.
| Bank | Username | Password | Notes |
|---|---|---|---|
| FinBank (Finicity) | demo | go | No MFA. Flips to active immediately. Best for happy-path testing. |
| Ledgersync Bank (FDE) | See portal Testing page | See portal | Exercises FDE source. |
Every error returns the same envelope. Branch on code, not just the HTTP status:
| HTTP Status | Meaning |
|---|---|
| 400 | Validation error — something in your request body or params is incorrect |
| 401 | Authentication error — bad or missing key |
| 404 | Not found |
| 429 | Rate limited — back off and retry |
| 5xx | Server error — retry and quote X-LS-Trace-Id if it persists |
Import the LedgerSync Postman collection and run the full flow without leaving Postman. The Quickstart folder chains every step and captures IDs automatically.
Import URL: https://portal.ledgersyncappv2.com/ledgersync-v3.postman_collection.json
Set api_key to your key and base_url to the sandbox base URL, then run the Quickstart folder top-to-bottom.