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

# Transactions

> List, create, update, delete, and restore transactions — including transfers and splits

## The transaction object

```json theme={null}
{
  "id": "k1730s...",
  "date": "2026-07-05",
  "amount": "-84.20",
  "account_id": "k4859a...",
  "account_name": "Chase Checking",
  "merchant_id": "k9921m...",
  "merchant_name": "Costco",
  "category_id": null,
  "category_name": null,
  "ready_to_assign": false,
  "notes": "Weekly groceries + household",
  "status": "cleared",
  "review_status": "reviewed",
  "type": "regular",
  "transfer_account_id": null,
  "splits": [
    {
      "amount": "-64.20",
      "category_id": "k5511c...",
      "category_name": "Groceries",
      "ready_to_assign": false,
      "notes": null
    },
    {
      "amount": "-20.00",
      "category_id": "k5512c...",
      "category_name": "Household",
      "ready_to_assign": false,
      "notes": null
    }
  ],
  "deleted": false,
  "created_at": "2026-07-05T18:22:10.000Z",
  "updated_at": "2026-07-05T18:22:10.000Z"
}
```

* `amount` is the transaction total. Negative = money out, positive = money in.
* Single-category transactions have `category_id`/`category_name` set and `splits: null`. Split transactions have `splits` and a `null` top-level category.
* `ready_to_assign: true` marks income that flows straight to Ready to Assign instead of a category.
* `status` is `pending`, `cleared`, or `reconciled`. `review_status` is `reviewed` or `needs_review`.
* Transfers come in pairs: each side is a `type: "transfer"` transaction whose `transfer_account_id` points at the other account.

## List transactions

```text theme={null}
GET /v1/workspaces/{workspace_id}/transactions
```

| Query parameter | Type   | Description                                            |
| --------------- | ------ | ------------------------------------------------------ |
| `since_date`    | date   | Only transactions on or after this date (`YYYY-MM-DD`) |
| `until_date`    | date   | Only transactions on or before this date               |
| `account_id`    | string | Only transactions in this account                      |
| `category_id`   | string | Only transactions with a split in this category        |
| `status`        | string | `pending`, `cleared`, or `reconciled`                  |
| `review_status` | string | `reviewed` or `needs_review`                           |
| `limit`         | int    | Page size, 1–500 (default 100)                         |
| `cursor`        | string | Opaque pagination cursor from a previous response      |

Results are ordered newest-first. The response includes `next_cursor`; pass it back as `cursor` to fetch the next page, and stop when it is `null`.

<Note>
  Pages can contain fewer than `limit` items even when more results remain — always follow `next_cursor` rather than
  counting rows.
</Note>

## Get a transaction

```text theme={null}
GET /v1/workspaces/{workspace_id}/transactions/{transaction_id}
```

Deleted transactions are returned with `"deleted": true`.

## Create transactions

```text theme={null}
POST /v1/workspaces/{workspace_id}/transactions
```

Send a single object under `transaction`, or up to 100 under `transactions`. The batch is atomic — if any row is invalid, nothing is created.

```bash theme={null}
curl -X POST https://api.kualia.com/v1/workspaces/WORKSPACE_ID/transactions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transaction": {
      "account_id": "k4859a...",
      "date": "2026-07-05",
      "amount": "-12.34",
      "merchant_name": "Starbucks",
      "category_id": "k5511c..."
    }
  }'
```

| Field             | Type    | Description                                                                                        |
| ----------------- | ------- | -------------------------------------------------------------------------------------------------- |
| `account_id`      | string  | **Required.** Account to book the transaction in                                                   |
| `date`            | date    | **Required.** `YYYY-MM-DD`                                                                         |
| `amount`          | string  | **Required** (unless `splits`). Signed decimal total                                               |
| `category_id`     | string  | Category for the whole amount. Optional — see auto-categorization below                            |
| `ready_to_assign` | boolean | `true` books the amount as income to Ready to Assign (instead of a category)                       |
| `splits`          | array   | Split lines `{ amount, category_id?, ready_to_assign?, notes? }` — replaces `amount`/`category_id` |
| `merchant_id`     | string  | Existing merchant id                                                                               |
| `merchant_name`   | string  | Merchant by name — matched case-insensitively or created if new                                    |
| `notes`           | string  | Free-text memo                                                                                     |
| `status`          | string  | `pending` or `cleared` (default `cleared`)                                                         |
| `review_status`   | string  | `reviewed` or `needs_review` (default `needs_review`)                                              |

<Note>
  **Auto-categorization.** If you omit `category_id` (and don't set `ready_to_assign` or `splits`) on a transaction that
  has a merchant, Kualia categorizes it automatically — the same way imported and AI-chat transactions are handled: your
  active rules, then your past categorizations for that merchant, learned merchant→category mappings, reviewed history,
  and finally an AI prediction for merchants it hasn't seen before. Anything it can't confidently categorize is left
  uncategorized. Auto-categorized rows still default to `needs_review` so you can confirm them in the app. Pass an
  explicit `category_id` to skip this entirely.
</Note>

### Transfers

Set `"type": "transfer"` to move money between two accounts. Both sides of the pair are created and returned.

```json theme={null}
{
  "transaction": {
    "type": "transfer",
    "from_account_id": "k4859a...",
    "to_account_id": "k7731b...",
    "amount": "500.00",
    "date": "2026-07-05"
  }
}
```

`amount` must be positive for transfers. An optional `category_id` categorizes the outflow side (useful for credit-card payments).

## Update a transaction

```text theme={null}
PATCH /v1/workspaces/{workspace_id}/transactions/{transaction_id}
```

Send only the fields you want to change: `date`, `amount`, `category_id`, `ready_to_assign`, `splits`, `merchant_id` (or `merchant_name`), `notes` (string or `null`), `account_id`, `status`, `review_status`.

* Changing `amount` on a single-category transaction keeps its current category; changing `category_id` keeps the amount.
* To edit a split transaction's amounts or categories, send the full `splits` array.
* `review_status` never changes unless you send it explicitly.

## Delete & restore

```text theme={null}
DELETE /v1/workspaces/{workspace_id}/transactions/{transaction_id}
POST   /v1/workspaces/{workspace_id}/transactions/{transaction_id}/restore
```

Deletes are soft: balances and category activity are rolled back, but the transaction can be restored. Deleting one side of a transfer deletes both sides.
