> ## 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.

# Revenue and Expense

> Retrieve the revenue and expense tax invoices stored in Bolta, request a collection, and page through changes with a cursor.

## What you can retrieve

Retrieve the revenue and expense records of your own business in Bolta, in the same units as the revenue and expense lists in the dashboard. Each API key covers the one business it belongs to. You can also ask Bolta to collect sooner.

| Path                     | Purpose                        |
| ------------------------ | ------------------------------ |
| `GET /v1/revenues`       | Revenue changes (cursor based) |
| `GET /v1/expenses`       | Expense changes (cursor based) |
| `POST /v1/revenues:sync` | Request a revenue sync         |
| `POST /v1/expenses:sync` | Request an expense sync        |

Responses carry tax invoices and invoices (`type` is `HOMETAX_TAX_INVOICE`). They include both documents issued through Bolta and documents collected from Hometax. Cash receipts are not included, even those issued through Bolta.

## 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 four paths return `402` with `PLAN_UPGRADE_REQUIRED`. Upgrade your plan from the billing menu in the Bolta dashboard, then call again.

<Info>
  Connect Hometax revenue and expense collection in the Bolta dashboard first.
</Info>

A business that only issues through Bolta without connecting Hometax can still retrieve the tax invoices it issued as revenue. After you disconnect, the records already stored remain available.

None of the four 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).

## Key limits

| Item                     | Limit                                                                                                   |
| ------------------------ | ------------------------------------------------------------------------------------------------------- |
| Sync request             | Once every 30 minutes and 12 times in 24 hours per business, counted separately for revenue and expense |
| Visibility               | About 5 minutes after the change in Bolta                                                               |
| Hometax documents        | Usually 1 to 2 hours after collection                                                                   |
| Items per call (`limit`) | Up to 200. Defaults to 100                                                                              |

## Revenue and expense lists

Both paths take the same parameters and return the same shape. The path decides whether an item is revenue or expense.

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

| Parameter | Required | Description                                                                                          |
| --------- | -------- | ---------------------------------------------------------------------------------------------------- |
| `from`    | No       | Start of the transaction date range. `YYYY-MM-DD` (KST, inclusive). For tax invoices, the write date |
| `to`      | No       | End of the transaction date range. `YYYY-MM-DD` (KST, inclusive)                                     |
| `type`    | No       | `HOMETAX_TAX_INVOICE`                                                                                |
| `cursor`  | No       | `nextCursor` from the previous response                                                              |
| `limit`   | No       | 1 to 200. Defaults to 100                                                                            |

Without a date range, the API returns every stored record.

The example below is the test key's revenue response without a cursor, with `102` and `103` omitted.

```json theme={"dark"}
{
  "items": [
    {
      "status": "ACTIVE",
      "id": "101",
      "type": "HOMETAX_TAX_INVOICE",
      "taxInvoice": {
        "ntsTransactionId": "20260901-10000000-00000101",
        "invoiceType": "TAX_INVOICE",
        "purpose": "CLAIM",
        "writtenDate": "2026-09-01",
        "issuedAt": "2026-09-01T05:12:00Z",
        "supplier": {
          "identificationNumberType": "BUSINESS",
          "identificationNumber": "1234567890",
          "taxRegistrationId": null,
          "organizationName": "주식회사 볼타테스트",
          "representativeName": "정대표",
          "address": "서울특별시 테스트구 테스트로 1",
          "businessType": "서비스",
          "businessItem": "소프트웨어 개발",
          "manager": {
            "name": "홍길동",
            "email": "tax@example.com",
            "telephone": "02-0000-0000"
          }
        },
        "supplied": {
          "identificationNumberType": "BUSINESS",
          "identificationNumber": "1111111111",
          "taxRegistrationId": null,
          "organizationName": "주식회사 테스트고객",
          "representativeName": "테스트대표",
          "address": "경기도 테스트시 샘플로 2",
          "businessType": "도매",
          "businessItem": "도소매",
          "manager": {
            "name": "김담당",
            "email": "buyer@example.com",
            "telephone": null
          }
        },
        "trustee": null,
        "totalSupplyCost": 1000000,
        "totalTax": 100000,
        "totalAmount": 1100000,
        "paymentMeans": {
          "cash": null,
          "check": null,
          "bankBill": null,
          "accountReceivable": 1100000
        },
        "description": null,
        "amendReason": null,
        "importDeclaration": null,
        "items": [
          {
            "date": "2026-09-01",
            "name": "컨설팅",
            "specification": null,
            "quantity": 1,
            "unitPrice": 600000,
            "supplyCost": 600000,
            "tax": 60000,
            "description": null
          },
          {
            "date": "2026-09-01",
            "name": "교육",
            "specification": null,
            "quantity": 2,
            "unitPrice": 200000,
            "supplyCost": 400000,
            "tax": 40000,
            "description": null
          }
        ]
      }
    },
    {
      "status": "REMOVED",
      "id": "104"
    }
  ],
  "nextCursor": "djI6MTc4ODU3MzYwMDAwMDAwMDoxMDQ6MXVpMmNn",
  "hasMore": false
}
```

The item shape depends on `status`.

| `status`  | Meaning                                                                   | Fields                                                     |
| --------- | ------------------------------------------------------------------------- | ---------------------------------------------------------- |
| `ACTIVE`  | A record visible in the Bolta dashboard                                   | `status`, `id`, `type`, and the object that matches `type` |
| `REMOVED` | A record hidden from the dashboard because it was archived or deactivated | `status`, `id`                                             |

| Field                | Type    | Description                                           |
| -------------------- | ------- | ----------------------------------------------------- |
| `items[].status`     | string  | `ACTIVE`, `REMOVED`                                   |
| `items[].id`         | string  | Revenue or expense identifier. Unique within the path |
| `items[].type`       | string  | Evidence type. `HOMETAX_TAX_INVOICE`                  |
| `items[].taxInvoice` | object  | The tax invoice when `type` is `HOMETAX_TAX_INVOICE`  |
| `nextCursor`         | string  | Cursor to pass on the next call                       |
| `hasMore`            | boolean | Whether more items are available right now            |

`id` identifies one revenue or expense record, not the tax invoice.

`HOMETAX_TAX_INVOICE` means a tax invoice or invoice registered with the National Tax Service (NTS). Documents issued through Bolta use the same value.

### Tax invoice

`taxInvoice` has the same shape for revenue and expense.

| Field                                 | Type            | Description                                                                                                             |
| ------------------------------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `ntsTransactionId`                    | string          | NTS approval number                                                                                                     |
| `invoiceType`                         | string          | NTS document type. See [Document types](#document-types)                                                                |
| `purpose`                             | string or null  | `RECEIPT` or `CLAIM`                                                                                                    |
| `writtenDate`                         | string          | Write date `YYYY-MM-DD` (KST)                                                                                           |
| `issuedAt`                            | string          | Issuance time (UTC). For documents whose details cannot be filled, 00:00 (KST) on the issuance date                     |
| `supplier`                            | object          | Supplier. See [Parties](#parties)                                                                                       |
| `supplied`                            | object          | Recipient                                                                                                               |
| `trustee`                             | object or null  | Trustee. Only on brokered documents                                                                                     |
| `totalSupplyCost`                     | integer         | Total supply cost                                                                                                       |
| `totalTax`                            | integer or null | Total tax. `null` for documents without tax, such as invoices                                                           |
| `totalAmount`                         | integer         | Total amount                                                                                                            |
| `paymentMeans.cash`                   | integer or null | Cash                                                                                                                    |
| `paymentMeans.check`                  | integer or null | Check                                                                                                                   |
| `paymentMeans.bankBill`               | integer or null | Promissory note                                                                                                         |
| `paymentMeans.accountReceivable`      | integer or null | Accounts receivable                                                                                                     |
| `description`                         | string or null  | Remarks                                                                                                                 |
| `amendReason`                         | string or null  | NTS amendment reason. See [Amendment reasons](#amendment-reasons). `null` unless the document is an amended tax invoice |
| `importDeclaration`                   | object or null  | Import declaration. Only on import tax invoices                                                                         |
| `importDeclaration.declarationNumber` | string or null  | Import declaration number                                                                                               |
| `importDeclaration.periodStartDate`   | string or null  | Start of the taxable period (KST)                                                                                       |
| `importDeclaration.periodEndDate`     | string or null  | End of the taxable period (KST)                                                                                         |
| `importDeclaration.itemCount`         | integer or null | Number of imports                                                                                                       |
| `items`                               | array           | Line items. Empty for documents whose details cannot be filled                                                          |
| `items[].date`                        | string or null  | Item supply date (KST)                                                                                                  |
| `items[].name`                        | string or null  | Item name                                                                                                               |
| `items[].specification`               | string or null  | Specification                                                                                                           |
| `items[].quantity`                    | number or null  | Quantity                                                                                                                |
| `items[].unitPrice`                   | number or null  | Unit price                                                                                                              |
| `items[].supplyCost`                  | integer         | Supply cost                                                                                                             |
| `items[].tax`                         | integer or null | Tax                                                                                                                     |
| `items[].description`                 | string or null  | Remarks                                                                                                                 |

Amounts are integers in KRW and can be negative on amended tax invoices. Quantity and unit price come without a decimal point when they are whole numbers, and with their decimal places otherwise.

### Parties

`supplier`, `supplied`, and `trustee` share the same shape.

| Field                      | Type           | Description                                                   |
| -------------------------- | -------------- | ------------------------------------------------------------- |
| `identificationNumberType` | string         | `BUSINESS`, `RESIDENT` (individual), or `FOREIGN` (foreigner) |
| `identificationNumber`     | string         | Identification number without hyphens                         |
| `taxRegistrationId`        | string or null | Sub-business place code                                       |
| `organizationName`         | string or null | Company name                                                  |
| `representativeName`       | string or null | Representative                                                |
| `address`                  | string or null | Address                                                       |
| `businessType`             | string or null | Business type                                                 |
| `businessItem`             | string or null | Business item                                                 |
| `manager`                  | object or null | Manager. `null` when name, email, and phone are all missing   |
| `manager.name`             | string or null | Name                                                          |
| `manager.email`            | string or null | Email                                                         |
| `manager.telephone`        | string or null | Phone number                                                  |

| `identificationNumberType` | `identificationNumber`                |
| -------------------------- | ------------------------------------- |
| `BUSINESS`                 | 10-digit business registration number |
| `RESIDENT`                 | 13-digit resident registration number |
| `FOREIGN`                  | Foreigner identification number       |

The supplier and trustee are always `BUSINESS`. Only the recipient can be `RESIDENT` or `FOREIGN`, and then `taxRegistrationId`, `businessType`, and `businessItem` are `null`. When the recipient has two managers, `manager` holds the first.

<Warning>
  For `RESIDENT`, `identificationNumber` is the raw resident registration number. Encrypt it at rest and keep it out of your logs.
</Warning>

### Document types

| `invoiceType`            | Document                                                   |
| ------------------------ | ---------------------------------------------------------- |
| `TAX_INVOICE`            | Electronic tax invoice                                     |
| `ZERO_RATED_TAX_INVOICE` | Zero-rate electronic tax invoice                           |
| `INVOICE`                | Electronic tax-exemption invoice                           |
| `IMPORT_TAX_INVOICE`     | Import electronic tax invoice (including deferred payment) |

Brokered documents have no separate type. A document is a brokered document when `trustee` is set.

### Amendment reasons

| `amendReason`            | Reason                                     |
| ------------------------ | ------------------------------------------ |
| `MISSPELLED`             | Correction of Error in Entries             |
| `CHANGE_SUPPLY_COST`     | Change in Supply Cost                      |
| `RETURNED`               | Return of Goods                            |
| `TERMINATION`            | Cancellation of Contract                   |
| `LOCAL_LETTER_OF_CREDIT` | Local Letter of Credit Opened after Supply |
| `DOUBLE_ISSUANCE`        | Double Issuance by Mistake                 |

Correction of Error in Entries and Local Letter of Credit Opened after Supply amendments are issued as a pair: a cancelling document and a corrected document. The API sends both with the same reason.

### Documents in the response

The API does not return documents whose details are still being filled. For documents collected from Hometax, Bolta receives the approval number, parties, and amounts first, and fills the issuance time, addresses, business types, managers, and line items later.

Tax invoices issued through Bolta appear right away. Documents collected from Hometax appear after their details are filled, newest write date first, so with many past documents the older ones keep arriving later.

When the detail lookup fails for good, the API sends the document with list values only and an empty `items`. Addresses, business types, business items, and managers can also be `null`. If the details are filled later, the document arrives again with the same `id`. `ntsTransactionId`, `issuedAt`, and `invoiceType` are always set.

Management data that exists only in Bolta, such as payment status, memos, labels, and assignees, is not included.

## Order and cursor

The API returns records in the order they changed in Bolta, not by write date or collection order. Follow this order so you do not miss past-dated documents, detail updates, archiving, or unarchiving that happen later.

To show records by write date, sort by `writtenDate` 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.

An expense also moves back in the order when Bolta re-evaluates its payment status, so the same expense can arrive again with identical content. Overwrite it.

### 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`. Save separate cursors for revenue, expense, and each set of filters.
3. On your next collection, call with the saved cursor. You receive only the records that changed since then.

<Warning>
  `hasMore` can be `true` even when `items` is empty. Decide whether to continue from `hasMore`, not from `items`.
</Warning>

Send the cursor exactly as received. When the path, filters, or key mode differ from when the cursor was issued, the API returns `400` with `INVALID_CURSOR`. A cursor issued with `type` and one issued without it count as different filters. After you change the filters or move from a test key to a live key, start over without a cursor. You can change `limit` on any page.

### Handling new values

Bolta can add new `type` values, new fields, and new `invoiceType` or `amendReason` values without notice. New evidence types such as cash receipts arrive on the same paths with a new `type` and a matching object.

* Skip items with an unknown `type` and keep processing.
* Ignore unknown fields.
* Keep processing when you receive an unknown enum value.

`REMOVED` arrives without `type`, so apply it by `id` as usual.

## Sync request

Bolta collects revenue and expense records periodically, and starts the first collection as soon as you connect in the dashboard. Request a sync only when you need a document just registered with the NTS.

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

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

| Path                     | Collects                                |
| ------------------------ | --------------------------------------- |
| `POST /v1/revenues:sync` | All revenue connected in the dashboard  |
| `POST /v1/expenses:sync` | All expenses connected in the dashboard |

The API only accepts the request with `202 Accepted`, and the collection runs asynchronously. There is no path to check collection status. Instead of waiting for it to finish, continue fetching the list with your saved cursor. See [Key limits](#key-limits) for the request limits. Requests rejected with `409` or `422` do not count toward the limit. `nextAvailableAt` reflects only the 30-minute interval, not the 24-hour count.

| Status code | Error code            | Meaning                                                                                                              |
| ----------- | --------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `202`       | -                     | Accepted                                                                                                             |
| `409`       | `SYNC_IN_PROGRESS`    | A collection for the same path is already running, including Bolta's regular collection. Request again after it ends |
| `422`       | `NO_SOURCE_CONNECTED` | No revenue or expense connection in the dashboard                                                                    |
| `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                                                    |

## Test key

Test keys return fixed samples regardless of your subscription, without the 5-minute delay. Sync requests do not collect anything, only return an accepted response, and use no quota.

The sample's own business is `주식회사 볼타테스트` (`1234567890`).

**Revenue** (`GET /v1/revenues`)

| `id`  | Changed at (UTC)     | Write date | `invoiceType`            | Recipient  | Total     | Status    |
| ----- | -------------------- | ---------- | ------------------------ | ---------- | --------- | --------- |
| `101` | 2026-09-01T05:12:30Z | 2026-09-01 | `TAX_INVOICE`            | 주식회사 테스트고객 | 1,100,000 | `ACTIVE`  |
| `102` | 2026-09-04T00:00:00Z | 2026-09-03 | `ZERO_RATED_TAX_INVOICE` | 테스트수출 주식회사 | 500,000   | `ACTIVE`  |
| `103` | 2026-09-05T02:00:00Z | 2026-09-05 | `TAX_INVOICE`            | 주식회사 테스트고객 | -220,000  | `ACTIVE`  |
| `104` | 2026-09-05T02:00:00Z | 2026-09-02 |                          |            |           | `REMOVED` |

* `101`: Tax invoice issued through Bolta, with two line items
* `102`: Zero-rate tax invoice collected from Hometax
* `103`: Change in Supply Cost amendment (`amendReason` is `CHANGE_SUPPLY_COST`). Negative amounts, and `quantity` and `unitPrice` are `null` on the line item
* `104`: Revenue archived in the dashboard. It changed at the same time as `103` and comes after it by `id`

**Expense** (`GET /v1/expenses`)

| `id`  | Changed at (UTC)     | Write date | `invoiceType` | Supplier   | Total   | Status   |
| ----- | -------------------- | ---------- | ------------- | ---------- | ------- | -------- |
| `201` | 2026-09-01T09:00:00Z | 2026-09-01 | `TAX_INVOICE` | 주식회사 테스트공급 | 110,000 | `ACTIVE` |
| `202` | 2026-09-03T00:00:00Z | 2026-09-02 | `INVOICE`     | 테스트농산      | 180,000 | `ACTIVE` |
| `203` | 2026-09-06T00:30:00Z | 2026-09-05 | `TAX_INVOICE` | 주식회사 테스트물류 | 330,000 | `ACTIVE` |

* `201`: Tax invoice collected from Hometax
* `202`: Tax-exemption invoice. `totalTax` and the line item `tax` are `null`
* `203`: Tax invoice whose details cannot be filled. Address, business type, business item, and `manager` are `null`, and `items` is empty

Call with `limit=2` to see cursor paging across two pages. Date filters and cursors follow the same rules as with a live key.

## Errors

| Status code | Error code              | Condition                                                                                              |
| ----------- | ----------------------- | ------------------------------------------------------------------------------------------------------ |
| `400`       | `INVALID_REQUEST`       | Invalid date format, `from` later than `to`, or an invalid `type` or `limit`                           |
| `400`       | `INVALID_CURSOR`        | Modified or malformed cursor, or the path, filters, or key mode differ from when the cursor was issued |
| `401`       | -                       | API key authentication failed. Empty response body                                                     |
| `402`       | `PLAN_UPGRADE_REQUIRED` | Plan below Standard                                                                                    |
| `409`       | `SYNC_IN_PROGRESS`      | A collection for the same path is already running                                                      |
| `422`       | `NO_SOURCE_CONNECTED`   | No revenue or expense connection                                                                       |
| `429`       | `SYNC_RATE_LIMITED`     | Sync request limit exceeded. Comes with a `Retry-After` header                                         |
| `500`       | `INTERNAL_SERVER_ERROR` | Internal server error                                                                                  |
| `503`       | `FEED_UNAVAILABLE`      | Temporarily unable to load records. The cursor did not advance, so call again with the same cursor     |
| `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 Revenues API](/api-reference/revenue-and-expense/list-revenues)
* [List Expenses API](/api-reference/revenue-and-expense/list-expenses)
* [Request Revenue Sync API](/api-reference/revenue-and-expense/request-revenue-sync)
* [Request Expense Sync API](/api-reference/revenue-and-expense/request-expense-sync)
* [Bank Account Transactions](/en/api-introduction/bank-account-transactions)
* [Authentication guide](/en/api-introduction/authentication)
