/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.curl and jq:
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.
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_idsreplaces every tag. An empty list removes them. Withouttag_ids, the tags stay.- An empty string in
notesremoves the notes.nullis 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.