Skip to main content
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

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 returns one page, newest first. The order is fixed: transacted_at descending, then id. 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.
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:
With the visor 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 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} returns one transaction.

Record a transaction

POST /v1/spaces/{space_id}/transactions records a manual transaction. It needs the pfm:transactions:write scope and an Idempotency-Key header. 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.

Record transactions in a batch

POST /v1/spaces/{space_id}/transactions/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.
A valid batch returns 200 OK. Its data array has one result per input row, in the same order: 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.

Edit a transaction

PATCH /v1/spaces/{space_id}/transactions/{transaction_id} changes only the fields in the body. It needs the pfm:transactions:write scope.
  • 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.

Delete a transaction

DELETE /v1/spaces/{space_id}/transactions/{transaction_id} 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.