Transactions
Transactions represent bank transactions synced from Plaid, imported from Ramp, Xero or QuickBooks, uploaded from CSV, or created manually. They are the core data type for bank feeds, categorization, and reconciliation.
Transaction Lifecycle
- Sync — Transactions arrive from a connected bank (
source: plaid), a card program (ramp), a migration (xero,quickbooks), a CSV upload (bulk_upload) or the API (manual) - Clean — Merchant names are normalized into
counterpartyautomatically - Categorize — AI, bank rules, or manual assignment to an expense/revenue ledger via the transaction's journal entry
- Review — A user confirms the categorization (
review_status: reviewed) - Reconcile — Matched against bank statements (
reconciled: true) — see Reconciliation
List Transactions
curl "https://api.ondayzero.com/api/v1/transactions?limit=25&only_uncategorized=true&start_date=2026-03-01" \
-H "Authorization: Bearer dz_your_token_here" \
-H "x-business-id: YOUR_BUSINESS_ID"Filter Parameters
| Parameter | Type | Description |
|---|---|---|
start_date / end_date |
date | Transaction date range |
ledger_id |
UUID | Bank account ledger the transaction belongs to |
opposing_ledger_id |
UUID | Category ledger (the expense/revenue side of the journal entry) |
source_account_id |
UUID[] | Plaid source account(s) |
search |
string | Case-insensitive match on description or counterparty |
amount_direction |
string | inflow (money in) or outflow (money out) |
only_uncategorized |
boolean | Only transactions with no category yet |
missing_counterparty |
boolean | Only transactions with no counterparty |
review_status |
string | unreviewed, reviewed, or comma-separated |
reconciled |
boolean | true only reconciled, false only unreconciled |
posted_status |
string | posted (cleared) or pending (unsettled authorizations) |
source |
string | plaid, ramp, bulk_upload, manual, xero, quickbooks, or comma-separated |
rule_id |
UUID | Only transactions this bank rule categorized (categorized_by.rule_id) |
tag_id / journal_entry_id / id |
UUID | Exact-match filters |
Plus the standard pagination parameters.
Key Fields
| Field | Type | Description |
|---|---|---|
amount |
integer | Amount in cents (positive = money in, negative = money out) |
currency |
string | ISO currency code |
datetime |
datetime | When the transaction occurred |
description |
string | Raw bank description |
counterparty |
string | Cleaned merchant / payee name |
ledger_id / ledger_name |
UUID / string | The bank account ledger |
journal_entry_id / journal_entry |
UUID / object | The double-entry record that carries the categorization |
opposing_line_entries |
array | The category side(s) of that journal entry — where the money was booked |
posted_status |
string | not_posted, posted, deleted |
review_status |
string | unreviewed, reviewed |
reconciled |
boolean | Matched to a bank statement |
categorization_method |
string | ai, rules, similarity, transfer_between_accounts, user, historical, global_pattern, uncategorized |
categorized_by |
object | When a bank rule chose the category: rule_id, rule_name, field_matched (description, counterparty, amount, ...); otherwise null |
source |
string | Where the transaction came from (see filters) |
invoice_ids / bill_ids |
UUID[] | Invoices or bills this transaction pays |
Create a Transaction
For bank accounts that aren't connected to a feed:
curl -X POST "https://api.ondayzero.com/api/v1/transactions" \
-H "Authorization: Bearer dz_your_token_here" \
-H "x-business-id: YOUR_BUSINESS_ID" \
-H "Content-Type: application/json" \
-d '{
"amount": -5000,
"currency": "USD",
"datetime": "2026-03-15T00:00:00Z",
"description": "Office supplies",
"source_account_id": "SOURCE_ACCOUNT_UUID"
}'POST /api/v1/transactions/bulk creates many at once. For CSV/Excel statements use the upload flow: POST /api/v1/transactions/bulk-upload/headers (discover the columns), …/bulk-upload/preview, then …/bulk-upload with a column_mapping.
When the account already has part of the statement's period (a bank feed, an earlier upload), first call POST /api/v1/transactions/bulk-upload/diff with the s3_key and ledger_id. It is read-only and pairs each file row with at most one existing transaction — same amount and date and description, or same amount within date_tolerance_days (default 3) with a shared word or matching counterparty — and lists the rows the ledger is missing, ambiguous same-amount pairs, and ledger rows the file lacks. Then upload with only_missing_from_diff: true (or include_row_numbers) to import exactly the missing rows. Pass exclude_statuses: ["Pending"] when the export includes unsettled rows.
Categorization
Every transaction is categorized through its journal entry — the bank ledger on one side, the category ledger on the other. You rarely build that entry yourself:
- Recategorize one or many:
PATCH /api/v1/transactions/batch/categorywith{"transaction_ids": [...], "ledger_id": "CATEGORY_LEDGER_UUID"}moves the opposing line entries to the new ledger. - Everything for a merchant:
POST /api/v1/transactions/recategorize-by-counterpartywith{"counterparty": "Uber", "ledger_id": "..."}. - Let AI do it:
POST /api/v1/transactions/categorize-allruns the full categorization workflow (bank rules → history → LLM) over all uncategorized transactions, auto-applies high-confidence results, and returns a workflow ID immediately.POST /api/v1/transactions/suggest-categoriesreturns suggestions without applying them. - Split across categories:
PATCH /api/v1/transactions/{transaction_id}/split:
curl -X PATCH "https://api.ondayzero.com/api/v1/transactions/{transaction_id}/split" \
-H "Authorization: Bearer dz_your_token_here" \
-H "x-business-id: YOUR_BUSINESS_ID" \
-H "Content-Type: application/json" \
-d '{
"split": [
{ "ledger_id": "MEALS_LEDGER_UUID", "allocation_type": "percent", "value": 7000 },
{ "ledger_id": "TRAVEL_LEDGER_UUID", "allocation_type": "remainder" }
]
}'allocation_type is percent (value in basis points — 7000 = 70%), amount (cents) or remainder. Each leg may carry project_id, class_id or location_id dimensions.
If you do need a hand-built journal entry (for a transaction with no bank feed, an accrual, a correction), see Journal Entries.
Review and Reconcile
PATCH /api/v1/transactions/batch/review-statusmarks transactions reviewed;POST /api/v1/transactions/batch/mark-reviewed-by-perioddoes it for a whole period.PATCH /api/v1/transactions/batch/reconciliation-statussetsreconciledin bulk.PUT /api/v1/transactions/{transaction_id}updates a single transaction'scounterparty,description,review_status,reconciled,personalflag, and so on.GET /api/v1/transactions/{transaction_id}/suggested-matcheslists invoices and bills this transaction probably pays; link them from the Invoices and Bills payment endpoints.
Delete and Restore
DELETE /api/v1/transactions/{transaction_id} hides a bank-fed transaction (soft delete; POST …/restore brings it back) and removes a manual one. DELETE /api/v1/transactions/by-date-range?start_date=…&end_date=… clears an imported range — preview with GET …/by-date-range/preview first.