Developers
API reference

Transactions

get/v1/transactions

List transactions

transactions.listread tokenidempotent

The ledger's transactions, newest first. Daily market-value entries on investment accounts are balance facts, not transactions, and are never listed.

Parameters

limit
integer · query
Page size, 1–200.
cursor
string | null · query
The next_cursor of the previous page.
account_id
integer | null · query
Only transactions touching this account.
category_id
integer | null · query
Only transactions filed under this category account.
tag_id
integer | null · query
Only transactions carrying this tag.
since
date | null · query
On or after this date.
until
date | null · query
On or before this date.
search
string | null · query
Text in the description, the bank's statement line or the name the user gave the counterparty, or an amount (72.80, 72,80; 72 is any amount from 72.00 to 72.99).
uncategorized
boolean · query
Only transactions still waiting for a category.
purpose
string | null · query
personal or business only.
X-Organization-Id
string | null · header

Returns 200 · TransactionList

data
array of Transaction · required
has_more
boolean · required
next_cursor
string | null · required
Example
curl -X GET "https://api.lumaledger.app/v1/transactions" \
  -H "Authorization: Bearer $LUMALEDGER_TOKEN"
post/v1/transactions

Create a transaction

transactions.createwrite tokenidempotent

Post a hand-written transaction as a balanced journal entry: a cash purchase, a loan payment split into interest and principal, an opening balance. Send an Idempotency-Key so a retry cannot post it twice.

Parameters

Idempotency-Key
string | null · header
Any unique string (a UUID is ideal). A retry with the same key returns the first attempt's response instead of doing the work twice. Keys are kept for 24 hours.
X-Organization-Id
string | null · header

Body · TransactionCreate

date
date · required
description
string · required
lines
array of TransactionLineInput · required
memo
string | null

Returns 201 · Transaction

account_id
integer · required
The real account the transaction is on.
amount
Money · required
Negative when money left the account or debt grew, positive when money arrived or debt shrank. In the ledger's base currency.
category_id
integer | null · required
The income or expense account it is filed under. Null for a transfer between the ledger's own accounts.
counterparty
TransactionCounterparty | null · required
Who is on the other end. Null for transfers and unrecognized descriptions.
created_at
datetime · required
date
date · required
description
string · required
What the bank or the author called it.
id
integer · required
The journal entry id. The same id the app's transaction page uses.
ignored
boolean · required
Set aside by the user: listed, but left out of spending totals.
is_transfer
boolean · required
lines
array of TransactionLine · required
memo
string | null · required
purpose
string · required
personal or business.
source
string · required
Where it came from: plaid, import, manual, assistant, …
statement_line
string | null · required
The line as the bank printed it on the statement (SQ *BLUE BOTTLE 0412 OAKLAND CA), which search also matches. Null on an entry nobody imported.
updated_at
datetime · required
Example
curl -X POST "https://api.lumaledger.app/v1/transactions" \
  -H "Authorization: Bearer $LUMALEDGER_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "date": "2026-09-01",
    "description": "…",
    "lines": [
      {
        "account_id": 1,
        "amount": "42.50",
        "direction": "debit"
      }
    ]
  }'
delete/v1/transactions/{transaction_id}

Delete a transaction

transactions.deletewrite tokendestructiveidempotent

Delete a hand-written transaction. Imported ones cannot be deleted (the next sync would bring them back); set ignored instead.

Parameters

transaction_id
integer · path · required
X-Organization-Id
string | null · header

Returns 204 with no body.

Example
curl -X DELETE "https://api.lumaledger.app/v1/transactions/{transaction_id}" \
  -H "Authorization: Bearer $LUMALEDGER_TOKEN"
get/v1/transactions/{transaction_id}

Get a transaction

transactions.getread tokenidempotent

One transaction by its id (the journal entry id).

Parameters

transaction_id
integer · path · required
X-Organization-Id
string | null · header

Returns 200 · Transaction

account_id
integer · required
The real account the transaction is on.
amount
Money · required
Negative when money left the account or debt grew, positive when money arrived or debt shrank. In the ledger's base currency.
category_id
integer | null · required
The income or expense account it is filed under. Null for a transfer between the ledger's own accounts.
counterparty
TransactionCounterparty | null · required
Who is on the other end. Null for transfers and unrecognized descriptions.
created_at
datetime · required
date
date · required
description
string · required
What the bank or the author called it.
id
integer · required
The journal entry id. The same id the app's transaction page uses.
ignored
boolean · required
Set aside by the user: listed, but left out of spending totals.
is_transfer
boolean · required
lines
array of TransactionLine · required
memo
string | null · required
purpose
string · required
personal or business.
source
string · required
Where it came from: plaid, import, manual, assistant, …
statement_line
string | null · required
The line as the bank printed it on the statement (SQ *BLUE BOTTLE 0412 OAKLAND CA), which search also matches. Null on an entry nobody imported.
updated_at
datetime · required
Example
curl -X GET "https://api.lumaledger.app/v1/transactions/{transaction_id}" \
  -H "Authorization: Bearer $LUMALEDGER_TOKEN"
patch/v1/transactions/{transaction_id}

Update a transaction

transactions.updatewrite tokenidempotent

Correct a transaction's description, date or memo, refile it under another category, mark it a transfer between two of the ledger's own accounts (or take that back), or set it aside from spending totals. Only the fields sent change. Refiling or marking an imported transaction re-posts its entry, so the returned transaction may carry a new id.

Parameters

transaction_id
integer · path · required
X-Organization-Id
string | null · header

Body · TransactionUpdate

category_id
integer | null
Refile under this income or expense account. May give the transaction a new id (the entry is re-posted); the response carries it. A transaction marked as a transfer is un-marked first; a transfer the ledger paired from both of its sides cannot be refiled.
date
date | null
description
string | null
ignored
boolean | null
Leave it out of spending totals (imported transactions only).
memo
string | null
Null clears it.
stop_transfer_rule
boolean
With transfer_account_id: null: also stop the counterparty rule that files this payee as a transfer. What it already marked stays marked. Default: false.
transfer_account_id
integer | null
Mark it a transfer between the account it is on and this other balance-sheet account of the ledger's (see accounts.list), so it leaves income and spending. null takes a mark back and lets the categorizer file it. Imported transactions only; not with category_id. Re-posts the entry, so the response may carry a new id. Taking back a mark that a counterparty rule makes is refused with a 422 naming the rule, since the rule would mark it again at once: send stop_transfer_rule to stop the rule too, or category_id to file just this one.

Returns 200 · Transaction

account_id
integer · required
The real account the transaction is on.
amount
Money · required
Negative when money left the account or debt grew, positive when money arrived or debt shrank. In the ledger's base currency.
category_id
integer | null · required
The income or expense account it is filed under. Null for a transfer between the ledger's own accounts.
counterparty
TransactionCounterparty | null · required
Who is on the other end. Null for transfers and unrecognized descriptions.
created_at
datetime · required
date
date · required
description
string · required
What the bank or the author called it.
id
integer · required
The journal entry id. The same id the app's transaction page uses.
ignored
boolean · required
Set aside by the user: listed, but left out of spending totals.
is_transfer
boolean · required
lines
array of TransactionLine · required
memo
string | null · required
purpose
string · required
personal or business.
source
string · required
Where it came from: plaid, import, manual, assistant, …
statement_line
string | null · required
The line as the bank printed it on the statement (SQ *BLUE BOTTLE 0412 OAKLAND CA), which search also matches. Null on an entry nobody imported.
updated_at
datetime · required
Example
curl -X PATCH "https://api.lumaledger.app/v1/transactions/{transaction_id}" \
  -H "Authorization: Bearer $LUMALEDGER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
post/v1/transactions/categorize

Categorize transactions in bulk

transactions.categorizewrite tokenidempotent

File up to 200 transactions under one category in a single step. Nothing moves if any of them cannot be filed there (an income account for a purchase, say); ids that are not in this ledger, and transfers, which have no category, are returned in skipped.

Parameters

Idempotency-Key
string | null · header
Any unique string (a UUID is ideal). A retry with the same key returns the first attempt's response instead of doing the work twice. Keys are kept for 24 hours.
X-Organization-Id
string | null · header

Body · CategorizeRequest

category_id
integer · required
The income or expense account to file them under.
transaction_ids
array of integer · required

Returns 200 · CategorizeResult

moved
array of MovedTransaction · required
skipped
array of integer · required
updated
integer · required
Example
curl -X POST "https://api.lumaledger.app/v1/transactions/categorize" \
  -H "Authorization: Bearer $LUMALEDGER_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "category_id": 1,
    "transaction_ids": [
      1
    ]
  }'

Objects

CategorizeRequest

Fields

category_id
integer · required
The income or expense account to file them under.
transaction_ids
array of integer · required

CategorizeResult

Fields

moved
array of MovedTransaction · required
skipped
array of integer · required
updated
integer · required

Money

An amount and its currency. `amount` is a base-10 string with two decimal places and an optional leading minus sign. Parse it with a decimal type, not a float.

Fields

amount
string · required
Pattern: ^-?\d+\.\d{2}$.
currency
string · required
ISO 4217 code. Pattern: ^[A-Z]{3}$.

MovedTransaction

Fields

from_id
integer · required
to_id
integer · required
The id the transaction has now (re-posting may replace it).

Transaction

A transaction: one balanced journal entry, read from the side of the real account (bank, card, loan) it moved money in.

Fields

account_id
integer · required
The real account the transaction is on.
amount
Money · required
Negative when money left the account or debt grew, positive when money arrived or debt shrank. In the ledger's base currency.
category_id
integer | null · required
The income or expense account it is filed under. Null for a transfer between the ledger's own accounts.
counterparty
TransactionCounterparty | null · required
Who is on the other end. Null for transfers and unrecognized descriptions.
created_at
datetime · required
date
date · required
description
string · required
What the bank or the author called it.
id
integer · required
The journal entry id. The same id the app's transaction page uses.
ignored
boolean · required
Set aside by the user: listed, but left out of spending totals.
is_transfer
boolean · required
lines
array of TransactionLine · required
memo
string | null · required
purpose
string · required
personal or business.
source
string · required
Where it came from: plaid, import, manual, assistant, …
statement_line
string | null · required
The line as the bank printed it on the statement (SQ *BLUE BOTTLE 0412 OAKLAND CA), which search also matches. Null on an entry nobody imported.
updated_at
datetime · required

TransactionCounterparty

Fields

key
string · required
Stable key of the merchant or person on the other end.
name
string · required

TransactionCreate

A hand-written journal entry. Debits must equal credits.

Fields

date
date · required
description
string · required
lines
array of TransactionLineInput · required
memo
string | null

TransactionLine

One side of the double entry.

Fields

account_id
integer · required
amount
Money · required
Always positive; direction says which side.
direction
string · required
debit or credit.

TransactionLineInput

Fields

account_id
integer · required
An account in this ledger (see accounts.list).
amount
string · required
Positive decimal string, at most two decimal places: "42.50". Pattern: ^\d{1,12}(\.\d{1,2})?$.
direction
string · required
debit or credit. Pattern: ^(debit|credit)$.

TransactionList

A page of transactions, newest first.

Fields

data
array of Transaction · required
has_more
boolean · required
next_cursor
string | null · required

TransactionUpdate

Only the fields sent are changed.

Fields

category_id
integer | null
Refile under this income or expense account. May give the transaction a new id (the entry is re-posted); the response carries it. A transaction marked as a transfer is un-marked first; a transfer the ledger paired from both of its sides cannot be refiled.
date
date | null
description
string | null
ignored
boolean | null
Leave it out of spending totals (imported transactions only).
memo
string | null
Null clears it.
stop_transfer_rule
boolean
With transfer_account_id: null: also stop the counterparty rule that files this payee as a transfer. What it already marked stays marked. Default: false.
transfer_account_id
integer | null
Mark it a transfer between the account it is on and this other balance-sheet account of the ledger's (see accounts.list), so it leaves income and spending. null takes a mark back and lets the categorizer file it. Imported transactions only; not with category_id. Re-posts the entry, so the response may carry a new id. Taking back a mark that a counterparty rule makes is refused with a 422 naming the rule, since the rule would mark it again at once: send stop_transfer_rule to stop the rule too, or category_id to file just this one.