> For the complete documentation index, see [llms.txt](https://docs.finqware.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.finqware.com/api/sync-errors.md).

# Sync errors

Return the errors from the latest bank synchronisations alongside accounts, balances or transactions data.

## Overview

`/v1/accounts/get`, `/v1/balances/get` and `/v1/transactions/get` can optionally return the errors from the client's most recent bank synchronisations alongside the normal data.

This solves a real integration problem: without it, an empty or stale-looking result is ambiguous — is there genuinely no data, or did the last sync to the bank fail? Today, answering that means reaching out to Finqware support or waiting for a webhook. With `include_errors`, the answer comes back on the same call, for free.

The feature is opt-in and fully backward compatible. If you omit the flag, or send it as `false`, the response is byte-for-byte what it is today — a plain array. Nothing changes unless you ask for it.

## Enabling it

Add `"include_errors": true` to the request body of any of the three endpoints:

```javascript
// POST /v1/accounts/get
{
  "client_id": "{{client_id}}",
  "client_secret": "{{client_secret}}",
  "credentials_id": "{{credentials_id}}",
  "access_token": "{{access_token}}",
  "include_errors": true
}
```

The flag works identically on `/v1/balances/get` and `/v1/transactions/get`, alongside whatever other parameters those endpoints already accept (`filter`, `batch`, etc.) — `include_errors` doesn't interact with or change any of that.

## Response shape

With the flag on, the response becomes an envelope with three top-level keys instead of a bare array:

```javascript
{
  "transactions": [
    { "id": "...", "account_id": "...", "utx_id": "...", "data": { "...": "..." } }
  ],
  "account_errors": [
    {
      "account_id": "8f2a1e10-...",
      "native_account_id": "GB33BUKB20201555555555",
      "type": "external_api_error",
      "code": "external_api_unauthorized",
      "http": 401,
      "message": "The bank rejected the request: unauthorized.",
      "job_id": "b6e2b9b0-...",
      "timestamp": "2026-08-04T09:12:03Z"
    }
  ],
  "consent_errors": [
    {
      "type": "external_api_error",
      "code": "external_api_forbidden",
      "http": 403,
      "message": "The bank rejected the connection: forbidden.",
      "job_id": "b6e2b9b0-...",
      "timestamp": "2026-08-04T09:12:03Z"
    }
  ]
}
```

Only the name of the data key changes per endpoint — `accounts`, `balances` or `transactions` — the rest of the envelope (`account_errors`, `consent_errors`) is identical in shape across all three. The data array itself is never altered by this feature: same items, same fields, as when the flag is off.

## Two kinds of errors

|                 | `consent_errors`                                                                                                                                           | `account_errors`                                                                                                                                                                                                                                                        |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **What failed** | The whole bank connection — e.g. the consent was revoked, credentials expired, or the bank rejected the request outright. Not specific to any one account. | One specific account failed for that endpoint's own operation. Other accounts on the same connection may have synced fine.                                                                                                                                              |
| **Appears on**  | All 3 endpoints — even an endpoint whose own operation never got a chance to run (see note below).                                                         | Only the matching endpoint — a balances failure only on `/balances/get`, a transactions failure only on `/transactions/get`. On `/accounts/get`, only for the subset of banks that fetch accounts one at a time (see note below); for the rest it's always empty there. |
| **Identifies**  | Nothing account-specific — the failure applies to the whole connection.                                                                                    | `account_id` + `native_account_id` (the affected account).                                                                                                                                                                                                              |

**Why a consent error can show up on `/balances/get` or `/transactions/get` even though balances/transactions were never attempted:** listing accounts is always the first step of a sync. If that step fails, nothing downstream can run either — so the failure is still relevant to every endpoint, not just `/accounts/get`. Treat any entry in `consent_errors` as "this connection is down", regardless of which endpoint you called it from.

**Note on `/accounts/get` specifically:** most banks refresh all accounts in a single call, so a failure there is genuinely whole-connection and always classified as `consent_errors` — `account_errors` stays empty on `/accounts/get` for those. A smaller set of banks instead refresh accounts one at a time (one call per already-known account); for those, a single account's failure populates `account_errors` on `/accounts/get` just like it does on the other two endpoints, while the other accounts' data still comes through normally. Either way, treat `account_errors` and `consent_errors` as mutually exclusive for a given failure — you don't need to know which fetching style a particular bank uses to consume the response correctly.

## Error object reference

| Field               | Type           | Present on            | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| ------------------- | -------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `code`              | string         | both                  | Machine-readable identifier for the specific failure (e.g. `external_api_unauthorized`, `external_api_forbidden`, `external_api_connection_error`, `external_api_timeout`, `external_api_deadline_exceeded`, `external_api_server_error`, `external_api_unavailable`, `external_api_unknown_error`). This set is not fixed — new codes are added as new failure modes are handled. Use it for logging/support, not as an exhaustive switch.                                                                    |
| `type`              | string         | both                  | Broad error category. In this context it's always `external_api_error` — these errors all originate from a call to the bank's own API failing.                                                                                                                                                                                                                                                                                                                                                                 |
| `http`              | integer        | both                  | The HTTP status returned by (or attributed to) the failed bank call — not just 4xx/5xx from the bank itself, but also locally-attributed statuses for connectivity issues: `408` if the call to the bank timed out, `504` if a deadline was exceeded. A timeout is reported exactly like any other failure — same envelope, same fields, no special case. Useful for coarse-grained routing (e.g. 401/403 → prompt the user to reconnect their bank; 408/5xx → transient, safe to just keep retrying/polling). |
| `message`           | string         | both                  | Human-readable description, safe to display or log as-is.                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `job_id`            | UUID           | both                  | The sync job this error was reported by.                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `timestamp`         | ISO 8601       | both                  | When that sync job completed.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `account_id`        | UUID or `null` | `account_errors` only | Finqware's internal identifier for the account. Can be `null` — see below.                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `native_account_id` | string         | `account_errors` only | The bank's own identifier for the account (e.g. IBAN or bank account number). Always present.                                                                                                                                                                                                                                                                                                                                                                                                                  |

## Where the data comes from, and how long it lasts

* No live bank call is made to answer this. The errors are read straight out of Finqware's own sync history for this `credentials_id` — specifically, the report each background sync job writes when it finishes. That keeps the response fast and means `include_errors: true` never adds latency or an extra bank round-trip.
* We look at the most recent 4 finished jobs, within the last 2 days.
* Jobs older than 2 days are pruned automatically by a maintenance task that runs every 10 minutes. There's no separate "error state" tracked anywhere: the moment a sync succeeds, the earlier failure simply ages out of the window and stops appearing on its own — nothing to acknowledge or clear.

## What implementers need to know

* **The same error can appear more than once — once per affected sync.** Within a single job, duplicate errors are already collapsed (same `code` for `consent_errors`; same `native_account_id` + `code` for `account_errors`). But that dedup does not carry across jobs: if a problem is still unresolved on the next sync, you'll see it again, with its own `job_id`/`timestamp`. In practice, this means an ongoing outage can show up up to 4 times in a row. Group by `code` (or `native_account_id` + `code`) and use the most recent `timestamp` as "still failing as of", rather than counting occurrences as separate incidents.
* **`account_id` can be `null`.** It's resolved by looking up the bank's `native_account_id` against Finqware's cache of already-synced accounts. If an account has never synced successfully — Finqware has no record of it yet — `account_id` will be `null`. `native_account_id` is always present and is the reliable key for matching an error back to a specific bank account, including ones you haven't seen in your data yet.
* **`account_errors` is reported once per affected account**, not repeated on every balance or transaction row belonging to that account.

## Backward compatibility

`include_errors` is entirely opt-in. Existing integrations that don't send the flag see no change: the response stays the plain array it is today, with no new fields anywhere in it.
