> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ledgersyncappv2.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Changelog

> New features, improvements, and fixes to the LedgerSync v3 API. Newest first.

<Update label="2026-07-21" tags={["Connections"]}>
  ## Reauthorize (fix) a broken connection in place

  New `POST /v3/connections/{connection_id}/reauthorize` — repair a connection
  that reports `status: requires_action` **without** minting new ids.

  * Returns a `reauthorize` action with a hosted `reauth_url`; redirect the end
    user there to re-enter credentials or re-consent at the institution, then
    wait for `connection.active` (or `connection.failed`).
  * Keeps the **same** `con_` / `acc_` / `txn_` ids, unlike starting a fresh
    connection — so a delta re-sync over the overlap window dedupes cleanly
    against transactions you already have.
  * Available for `FINICITY`, `MX`, and `FDE`. Uploaded-statement (`PDF`)
    sources are read-only and have no connection to reauthorize.

  See [Connection lifecycle](/guides/connection-lifecycle#handling-failed-and-disconnected).
</Update>

<Update label="2026-07-17" tags={["Checks", "FDE"]}>
  ## Check images are now readable from the API

  The `check_images` capability has had a read path since day one on the data
  side, but no route to fetch it. Three endpoints now serve it, nested under the
  owning account:

  ```
  GET /v3/accounts/{account_id}/checks
  GET /v3/accounts/{account_id}/checks/{check_id}
  GET /v3/accounts/{account_id}/checks/{check_id}/download
  ```

  * Requires the new **`read:checks`** scope. Existing keys don't have it — tick it on a new key in the portal, or `PATCH /v3/api-keys/{id}` to widen the key you already have without rotating its secret.
  * **FDE-only.** Non-FDE accounts return an empty list rather than an error, so you can call this uniformly across sources.
  * **There is no back image** — LedgerSync captures the front of a check only, so there is no `side` field.
  * Bank-reported fields and `ocr.*` are kept separate, so you can always tell what the bank said from what OCR read off the paper.
  * The sandbox **Ledgersync Bank** (`ins_a7397a8d0656e1b7`) returns real check images, so the whole flow is testable end-to-end.

  See [Check images](/guides/checks).
</Update>

<Update label="2026-07-15" tags={["Connections", "MX"]}>
  ## On-demand refresh now works for MX connections

  `POST /v3/connections/{id}/refresh` now supports MX (`con_MX_*`) the same way
  it already supported Finicity and FDE: it returns `202 Accepted` with an
  `operation_id` and triggers a fresh aggregation. (Previously MX returned
  `502 refresh_not_supported`.)

  * Completion is asynchronous — listen for the `account.refresh.completed`
    webhook (or `account.refresh.failed` on terminal failure), then re-read
    `GET /v3/accounts` for the updated balances and transactions.
  * All three connectable sources (Finicity, MX, FDE) now answer `/refresh`
    uniformly, so you no longer need to special-case MX.
</Update>

<Update label="2026-07-10" tags={["Accounts", "Transactions"]}>
  ## PDF: a read-only fourth data source

  Accounts created from **uploaded bank statements** now surface through the API. Their accounts and transactions come back with `source: "PDF"` (ids like `acc_PDF_42`, `txn_PDF_8837`).

  * Read-only: `PDF` isn't linked through the widget and never appears under `GET /v3/connections` — a `PDF` account's `connection_id` is `null`.
  * Exposed on `GET /v3/accounts` and `GET /v3/accounts/{id}/transactions`. Statements aren't served for `PDF` accounts.
  * Everything else — pagination, filtering, the response shape — reads exactly like an aggregator source.

  See [the data model](/introduction#the-data-model).
</Update>

<Update label="2026-07-08" tags={["Pagination"]}>
  ## Cursor pagination on all list endpoints

  Every list endpoint now returns an opaque `next_cursor` and a `has_more` flag. Pass `next_cursor` back as the `cursor` query param and loop until `has_more` is `false`.

  * Keyset (seek) based, so it stays correct even as rows are added or removed between pages — no skipped or double-counted items.
  * `limit` defaults to `100` (max `500`; higher values are capped, not rejected).
  * Cursors are opaque and query-scoped — reusing one against a different account, filter, or date window returns `400 invalid_request`.

  Full guide: [Pagination](/guides/pagination).
</Update>

<Update label="2026-07-02" tags={["Statements"]}>
  ## Extract transactions from a bank-statement PDF

  New `POST /v3/statements/extract` — upload a bank-statement PDF and get back the OCR-extracted transactions, powered by the same engine behind the LedgerSync app's Bank Statement Converter.

  * Customer-level and standalone: no `client_id`, and the upload never attaches to a connection, account, or stored statement.
  * Async: returns `202` with an `operation_id`; poll `GET /v3/operations/{id}` (most statements finish in under a minute). The result carries `kind: statement_extraction`. There's no webhook for this one — polling is the completion signal.
  * PDF only, up to 30 MB (larger uploads are rejected with `413`). Requires the `write:statements` scope. Per-customer concurrency and daily budgets apply on top of the standard rate tiers — exceeding them returns `429`.
</Update>
