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

# Transactions

> Export your history, record transactions, and edit or delete them.

Every transaction operation acts on one space: `/v1/spaces/{space_id}/transactions`. A transaction outside that space receives `404 NOT_FOUND`, the same as one that does not exist.

## The transaction object

| Field | Meaning |
| - | - |
| `amount` | Absolute amount as a decimal string, such as `"42.90"`, in `currency_code` |
| `type` | `debit` for money out, `credit` for money in |
| `currency_code` | The currency of the account, such as `BRL` |
| `source` | `manual` for a transaction you recorded, `connected` for one a bank sent, `apple_pay` for one the Apple Pay shortcut imported |
| `status` | `pending` or `posted`. A manual transaction dated in the future is `pending`. |
| `ignored` | `true` when the transaction does not count in totals |
| `category_id`, `tag_ids` | IDs from [Spaces and reference data](https://docs.visorfinance.app/reference-data.md) |
| `transacted_at`, `created_at`, `updated_at` | RFC 3339 times in UTC |

Read `amount` with a decimal type, not a float, so no cent is lost.

## List and export your history

[`GET /v1/spaces/{space_id}/transactions`](https://docs.visorfinance.app/api-reference/transactions/list-transactions) returns one page, newest first. The order is fixed: `transacted_at` descending, then `id`.

| Parameter | Effect |
| - | - |
| `from_date`, `to_date` | Days to include, `YYYY-MM-DD`, in Brasília time. Without `to_date`, the list ends today. |
| `account_id`, `category_id`, `tag_id` | Only transactions with that account, category, or tag |
| `type` | `debit` or `credit` |
| `ignored` | `true` for only ignored transactions, `false` for only counted ones. Omit it for both. |
| `search` | Text in the description, the merchant name, or the notes |
| `limit` | Transactions per page, 1 to 100. The default is 50. |
| `after` | The `next_cursor` of the previous page |

Each response has `next_cursor`. Pass it as `after` to read the next page, with the same filters. On the last page `next_cursor` is `null`. The cursor is opaque: do not build or change it.

> [!NOTE]
>
> A future-dated transaction appears only when `to_date` includes its date.

This script exports one year of history to a JSON Lines file. It needs `curl` and `jq`:

```bash theme={null}
#!/usr/bin/env bash
set -euo pipefail

url="https://api.visorfinance.app/v1/spaces/$SPACE_ID/transactions"
query="from_date=2026-01-01&to_date=2026-12-31&limit=100"
cursor=""

: > transactions.jsonl

while :; do
  page=$(curl --silent --fail-with-body \
    --header "Authorization: Bearer $VISOR_TOKEN" \
    "$url?$query${cursor:+&after=$cursor}")

  echo "$page" | jq --compact-output '.data[]' >> transactions.jsonl
  cursor=$(echo "$page" | jq --raw-output '.next_cursor // empty')
  [ -z "$cursor" ] && break
done
```

> [!TIP]
>
> With the [`visor` CLI](https://docs.visorfinance.app/cli), the same export is one line: `visor tx list --from 2026-01-01 --to 2026-12-31 --all --json | jq --compact-output '.data[]' > transactions.jsonl`

Each page counts toward the [rate limit](https://docs.visorfinance.app/rate-limits.md) of 60 requests per minute, so 100 transactions per page export 6,000 transactions per minute.

A transaction that arrives while you page through the list does not move the rows that you did not read yet. If a sync adds an older transaction behind your position, read that period again.

## Get one transaction

[`GET /v1/spaces/{space_id}/transactions/{transaction_id}`](https://docs.visorfinance.app/api-reference/transactions/get-a-transaction) returns one transaction.

## Record a transaction

[`POST /v1/spaces/{space_id}/transactions`](https://docs.visorfinance.app/api-reference/transactions/create-a-transaction) records a manual transaction. It needs the `pfm:transactions:write` scope and an `Idempotency-Key` header.

| Field | Required | Notes |
| - | - | - |
| `account_id` | Yes | An account of the space. The transaction takes its currency. |
| `category_id` | Yes | A category of the space |
| `description` | Yes | 1 to 255 characters |
| `amount` | Yes | Positive decimal string with up to 2 decimal places, such as `"42.90"` |
| `type` | Yes | `debit` or `credit` |
| `transacted_at` | Yes | RFC 3339 time with an offset, such as `2026-10-01T08:30:00-03:00` |
| `merchant_name` | No | Up to 255 characters |
| `notes` | No | Up to 500 characters |
| `ignored` | No | `true` to leave the transaction out of totals |
| `tag_ids` | No | Up to 20 tags of the space |

Record expenses in a manual account. A connected account already receives every transaction from its bank, so a manual entry there can count twice.

A field outside this table receives `400 BAD_REQUEST`. An invalid value or an ID from another space receives `422 VALIDATION_FAILED`, and Visor records nothing. The quickstart has a [complete example](https://docs.visorfinance.app/quickstart.md).

## Record transactions in a batch

[`POST /v1/spaces/{space_id}/transactions/batch`](https://docs.visorfinance.app/api-reference/transactions/create-transactions-in-a-batch) records 1 to 100 transactions. It needs the `pfm:transactions:write` scope. Send an object with a `transactions` array. Each row accepts the single-create fields and requires its own `idempotency_key`. The key accepts 1 to 128 letters, digits, or `-` `_` `.` `:`, like the single-create header. No `Idempotency-Key` header is required for the batch.

```json theme={null}
{
  "transactions": [
    {
      "account_id": "ACCOUNT_ID",
      "category_id": "CATEGORY_ID",
      "description": "Padaria",
      "amount": "42.90",
      "type": "debit",
      "transacted_at": "2026-10-01T08:30:00-03:00",
      "idempotency_key": "import-row-1"
    }
  ]
}
```

A valid batch returns `200 OK`. Its `data` array has one result per input row, in the same order:

| Field | Meaning |
| - | - |
| `index` | Zero-based position in the input array |
| `status` | `created`, `replayed`, or `failed` |
| `transaction` | The transaction object for a successful create or replay; `null` for a failed row |
| `error` | `null` on success; an object with `code`, `message`, and `details` on failure |

Each row commits independently. A failed row does not undo successful rows or stop later rows. An invalid value returns `VALIDATION_FAILED` with `details.errors`; a malformed row returns its own error. The envelope must contain only `transactions`, with 1 to 100 rows. A malformed envelope, an empty batch, or more than 100 rows returns `400 BAD_REQUEST` before Visor records anything.

Retry with each row's original key. The same account and key share the single-create idempotency record, so a retry returns `replayed` without creating another transaction. A changed amount, type, description, date, or category returns `IDEMPOTENCY_CONFLICT` for that row. A key in another account can create another transaction. Each batch counts as one request toward the token's [rate limit](https://docs.visorfinance.app/rate-limits.md).

## Edit a transaction

[`PATCH /v1/spaces/{space_id}/transactions/{transaction_id}`](https://docs.visorfinance.app/api-reference/transactions/edit-a-transaction) changes only the fields in the body. It needs the `pfm:transactions:write` scope.

| Field | Manual transaction | Connected or Apple Pay transaction |
| - | - | - |
| `category_id`, `notes`, `ignored`, `tag_ids` | Yes | Yes |
| `description`, `amount`, `type`, `transacted_at` | Yes | No: `409 IMMUTABLE_PROVIDER_FIELD` |

* `tag_ids` replaces every tag. An empty list removes them. Without `tag_ids`, the tags stay.
* An empty string in `notes` removes the notes. `null` is not accepted.
* The edit is atomic. If one value is invalid, nothing changes.
* The account of a transaction cannot change.

```bash theme={null}
curl https://api.visorfinance.app/v1/spaces/SPACE_ID/transactions/TRANSACTION_ID \
  --request PATCH \
  --header "Authorization: Bearer $VISOR_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{"category_id": "CATEGORY_ID", "tag_ids": ["TAG_ID"]}'
```

## Delete a transaction

[`DELETE /v1/spaces/{space_id}/transactions/{transaction_id}`](https://docs.visorfinance.app/api-reference/transactions/delete-a-transaction) returns `204 No Content`. It needs the `pfm:transactions:write` scope.

* Visor removes a manual transaction and reverses its effect on the account balance.
* Visor hides a connected transaction, so the next bank sync does not bring it back.
* A transaction that another record depends on, such as a paid installment, receives `409 TRANSACTION_CONFLICT`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.