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

# Bank Account Transactions

> Retrieve the bank accounts and transactions connected to Bolta, request a sync, and page through changes with a cursor.

## What you can retrieve

Retrieve the bank accounts and transactions connected to your own business in Bolta. The API returns the transactions Bolta fetched from the bank as is, and you can request a sync when you need one. Each API key covers the one business it belongs to. You cannot retrieve another business's accounts.

| Path                                | Purpose                                             |
| ----------------------------------- | --------------------------------------------------- |
| `GET /v1/bankAccounts`              | Connected bank accounts and the sync status of each |
| `GET /v1/bankAccounts/transactions` | Loaded transactions (cursor based)                  |
| `POST /v1/bankAccounts:sync`        | Request a transaction sync                          |

## Requirements

These calls deduct no points. Instead, you need a **Standard plan or higher**. A free trial also qualifies when the trial plan is Standard or higher.

When your plan does not qualify, all three paths return `402` with `PLAN_UPGRADE_REQUIRED`. Upgrade your plan from the billing menu in the Bolta dashboard, then call again.

<Info>
  The API does not connect bank accounts. Connect them in the Bolta dashboard first.
</Info>

None of the three paths needs issuer registration, a certificate, or `Bolta-Client-Reference-Id`. Send `Basic {apiKey}` in the `Authorization` header. See the [authentication guide](/en/api-introduction/authentication) for details.

## Account list

```bash theme={"dark"}
curl https://xapi.bolta.io/v1/bankAccounts \
  -H "Authorization: Basic {apiKey}"
```

```json theme={"dark"}
{
  "items": [
    {
      "id": "1",
      "bank": "KB_STAR",
      "bankName": "국민은행",
      "accountNumber": "123456-01-234567",
      "name": "운영자금",
      "currencyCode": "KRW",
      "lastSyncedAt": "2026-09-18T00:00:12Z",
      "syncStatus": "IDLE"
    }
  ]
}
```

| Field           | Type           | Description                                                       |
| --------------- | -------------- | ----------------------------------------------------------------- |
| `id`            | string         | Account identifier                                                |
| `bank`          | string         | Bank code                                                         |
| `bankName`      | string         | Bank name                                                         |
| `accountNumber` | string         | Account number with hyphens                                       |
| `name`          | string         | Alias set in the dashboard. Falls back to the bank's account name |
| `currencyCode`  | string         | Currency code                                                     |
| `lastSyncedAt`  | string or null | Time of the last completed sync (UTC)                             |
| `syncStatus`    | string         | `IDLE`, `IN_PROGRESS`, `FAILED`                                   |

Treat `id` as a string even if it looks numeric. The identifier format may change. New bank codes may appear in `bank`, so display `bankName` in your UI.

## Transactions

```bash theme={"dark"}
curl "https://xapi.bolta.io/v1/bankAccounts/transactions?limit=100" \
  -H "Authorization: Basic {apiKey}"
```

| Parameter         | Required | Description                                                        |
| ----------------- | -------- | ------------------------------------------------------------------ |
| `from`            | No       | Start of the transaction date range. `YYYY-MM-DD` (KST, inclusive) |
| `to`              | No       | End of the transaction date range. `YYYY-MM-DD` (KST, inclusive)   |
| `bankAccountId`   | No       | Account `id` from the account list                                 |
| `transactionType` | No       | `DEPOSIT`, `WITHDRAW`, `OTHER`                                     |
| `cursor`          | No       | `nextCursor` from the previous response                            |
| `limit`           | No       | 1 to 500. Defaults to 100                                          |

Every filter is optional. Without a date range, the API returns every loaded transaction.

```json theme={"dark"}
{
  "items": [
    {
      "status": "ACTIVE",
      "id": "1",
      "bankAccountId": "1",
      "bank": "KB_STAR",
      "accountNumber": "123456-01-234567",
      "transactionAt": "2026-09-01T00:30:00Z",
      "transactionType": "DEPOSIT",
      "amount": 1100000,
      "balanceAfterTransaction": 5100000,
      "description": "주식회사볼타",
      "currencyCode": "KRW"
    },
    {
      "status": "REMOVED",
      "id": "6"
    }
  ],
  "nextCursor": "djE6MTc4ODU3MDAwMDAwMDAwMDo2",
  "hasMore": false
}
```

The item shape depends on `status`.

| `status`  | Meaning                                                                                           | Fields                         |
| --------- | ------------------------------------------------------------------------------------------------- | ------------------------------ |
| `ACTIVE`  | A transaction visible in the Bolta dashboard                                                      | Every field in the table below |
| `REMOVED` | A transaction hidden from the dashboard, for example by disconnecting the account or archiving it | `status`, `id`                 |

| Field                             | Type           | Description                                                                |
| --------------------------------- | -------------- | -------------------------------------------------------------------------- |
| `items[].status`                  | string         | `ACTIVE`, `REMOVED`                                                        |
| `items[].id`                      | string         | Transaction identifier                                                     |
| `items[].bankAccountId`           | string         | Account `id`                                                               |
| `items[].bank`                    | string         | Bank code                                                                  |
| `items[].accountNumber`           | string         | Account number with hyphens                                                |
| `items[].transactionAt`           | string         | Transaction time (UTC)                                                     |
| `items[].transactionType`         | string         | `DEPOSIT`, `WITHDRAW`, `OTHER`                                             |
| `items[].amount`                  | number         | Transaction amount. Zero or greater; `transactionType` gives the direction |
| `items[].balanceAfterTransaction` | number         | Balance after the transaction                                              |
| `items[].description`             | string or null | Bank memo                                                                  |
| `items[].currencyCode`            | string         | Currency code                                                              |
| `nextCursor`                      | string         | Cursor to pass on the next call                                            |
| `hasMore`                         | boolean        | Whether more items are available right now                                 |

`amount` and `balanceAfterTransaction` omit trailing decimal zeros. `OTHER` marks a transaction where the bank reported zero for both the deposit and withdrawal amounts, so `amount` is 0.

## Order and cursor

The API returns transactions **in the order they changed in Bolta**, not in transaction time order. Newly loaded, visible-again, and hidden transactions all join the end of the feed at the time they change.

Bolta re-fetches one day of overlap with the previous sync window, so a transaction with an earlier time can be loaded later. Reconnecting an account can also bring old transactions back with the same `id`. Following the change order keeps you from missing these. To show the newest transactions first, sort by `transactionAt` after you receive them.

The same `id` can arrive more than once. Apply items by `id` in the order you receive them.

* `ACTIVE`: Overwrite the item with the same `id`, or add it if none exists.
* `REMOVED`: Delete the item with the same `id`, or ignore it if none exists.

### Collection steps

1. Call without a cursor, then keep calling with `nextCursor` until `hasMore` is `false`.
2. Save the last `nextCursor`. The API fills `nextCursor` even when `hasMore` is `false`.
3. On your next collection, call with the saved cursor. You receive only the transactions that changed since then.

<Warning>
  `hasMore` can be `true` even when `items` is empty. When few transactions match your filters, the API stops at the end of the range it scans in one call and returns that position as `nextCursor`. Decide whether to continue from `hasMore`, not from `items`.
</Warning>

Changes in Bolta appear in responses **about 5 minutes later**. To avoid skipping changes that are still being saved, the API holds back transactions changed in the last 5 minutes until a later call. Right after a sync finishes, wait about 5 minutes before you continue.

Do not parse or build cursors. Send the value exactly as received. The API rejects a modified cursor with `400` and `INVALID_CURSOR`. If you change the filters (`from`, `bankAccountId`, and so on), start over without a cursor.

When a Bolta operator cleans up incorrectly loaded transactions directly, those transactions can disappear without a `REMOVED` item. This is rare, and Bolta notifies you separately when it happens.

## Sync request

Bolta fetches transactions periodically. Request a sync only when you need the latest transactions sooner.

```bash theme={"dark"}
curl -X POST https://xapi.bolta.io/v1/bankAccounts:sync \
  -H "Authorization: Basic {apiKey}"
```

```json theme={"dark"}
{
  "acceptedAt": "2026-09-18T03:00:00Z",
  "nextAvailableAt": "2026-09-18T03:30:00Z"
}
```

The API only accepts the request with `202 Accepted`, and the bank lookup runs asynchronously. Check progress with `syncStatus` in the account list. When `IN_PROGRESS` ends, continue fetching transactions with your saved cursor.

Each business can request a sync **once every 30 minutes and up to 12 times in 24 hours**. `nextAvailableAt` reflects only the 30-minute interval, not the 24-hour limit. Bolta processes bank lookups one account at a time, so frequent requests do not make the sync faster.

| Status code | Error code                   | Meaning                                                           |
| ----------- | ---------------------------- | ----------------------------------------------------------------- |
| `202`       | -                            | Accepted                                                          |
| `409`       | `SYNC_IN_PROGRESS`           | A sync is already running. Request again after it ends            |
| `422`       | `BANK_ACCOUNT_NOT_CONNECTED` | No bank account is connected                                      |
| `429`       | `SYNC_RATE_LIMITED`          | Request limit exceeded. Wait for the `Retry-After` seconds        |
| `503`       | `SYNC_UNAVAILABLE`           | Temporarily unable to evaluate the request. Request again shortly |

A request rejected because a sync is already running (`409`) does not count toward the limit.

## Testing with a test key

Test keys return fixed samples regardless of your subscription. They do not query real accounts. A sync request sends no bank lookup and uses no quota.

| Account `id` | Bank      | Account number     |
| ------------ | --------- | ------------------ |
| `1`          | `KB_STAR` | `123456-01-234567` |
| `2`          | `SHINHAN` | `110-123-456789`   |

| Transaction `id` | Account | Transaction time (UTC) | Type       | Amount    | Status    |
| ---------------- | ------- | ---------------------- | ---------- | --------- | --------- |
| `1`              | `1`     | 2026-09-01T00:30:00Z   | `DEPOSIT`  | 1,100,000 | `ACTIVE`  |
| `2`              | `1`     | 2026-09-01T06:10:00Z   | `WITHDRAW` | 55,000    | `ACTIVE`  |
| `3`              | `2`     | 2026-09-02T01:00:00Z   | `DEPOSIT`  | 330,000   | `ACTIVE`  |
| `4`              | `1`     | 2026-09-03T08:45:00Z   | `WITHDRAW` | 2,200,000 | `ACTIVE`  |
| `5`              | `2`     | 2026-09-04T02:20:00Z   | `DEPOSIT`  | 770,000   | `ACTIVE`  |
| `6`              | `2`     | 2026-09-04T03:00:00Z   | `WITHDRAW` | 12,000    | `REMOVED` |

Transaction `6` is archived, so only `status` and `id` come back. Call with `limit=3` to see cursor paging across two pages. Filters and cursors follow the same rules as live keys, and test keys skip the 5-minute delay.

## Errors

| Status code | Error code                   | Condition                                                                               |
| ----------- | ---------------------------- | --------------------------------------------------------------------------------------- |
| `400`       | `INVALID_REQUEST`            | Invalid date format, `from` later than `to`, or an invalid `transactionType` or `limit` |
| `400`       | `INVALID_CURSOR`             | Modified or malformed cursor                                                            |
| `401`       | -                            | API key authentication failed. Empty response body                                      |
| `402`       | `PLAN_UPGRADE_REQUIRED`      | Plan below Standard                                                                     |
| `404`       | `BANK_ACCOUNT_NOT_FOUND`     | `bankAccountId` that does not exist or belongs to another business                      |
| `409`       | `SYNC_IN_PROGRESS`           | A sync is already running                                                               |
| `422`       | `BANK_ACCOUNT_NOT_CONNECTED` | No bank account is connected                                                            |
| `429`       | `SYNC_RATE_LIMITED`          | Sync request limit exceeded. Comes with a `Retry-After` header                          |
| `500`       | `INTERNAL_SERVER_ERROR`      | Internal server error                                                                   |
| `503`       | `SYNC_UNAVAILABLE`           | Temporarily unable to evaluate the sync request                                         |
| `503`       | `SERVICE_UNAVAILABLE`        | Temporary internal API communication error                                              |

See [Error codes](/en/api-introduction/error-codes) for the full list.

## Related documents

* [List Bank Accounts API](/api-reference/bank-account-transactions/list-bank-accounts)
* [List Bank Account Transactions API](/api-reference/bank-account-transactions/list-bank-account-transactions)
* [Request Bank Account Sync API](/api-reference/bank-account-transactions/request-bank-account-sync)
* [Authentication guide](/en/api-introduction/authentication)
