Journal entries
A journal entry records one transaction as a set of balanced debit/credit lines. This is the primary write operation in AI Accounting.
Anatomy of an entry
| Field | Notes |
|---|---|
date | The accounting date (required). |
description | Human-readable summary, 1–500 chars (required). |
reference | Optional external reference (e.g. invoice number). |
lines | 2 or more lines (required). Each line identifies its account by account_id (UUID) or account (code/name, resolved server-side) — exactly one — plus a debit or credit amount. |
auto_post | If true, the entry posts immediately; otherwise it's saved as a draft. |
metadata | Optional free-form JSON. |
Each line may also carry a currency and exchange_rate for
multi-currency entries.
Rules enforced by the engine: at least two lines; every line is a debit XOR a credit (greater than zero); and total debits must equal total credits.
Lifecycle: draft → post → reverse
Created without auto_post, an entry is a draft. It exists but does not
affect balances or reports yet, so agents can stage work for review.
The corresponding API operations:
POST /journal-entries— create (draft, or posted withauto_post)POST /journal-entries/{id}/post— post a draftPOST /journal-entries/{id}/reverse— reverse a posted entry
See them in the API reference. The post endpoints accept an
Idempotency-Key — a retried post returns the original posted entry rather than
failing because the entry is already posted.
Batch and bulk operations
- Create in bulk:
POST /journal-entries/batchaccepts up to 100 entries in one request. The response is compact by default —{ created_count, failed_count, errors[] }, with per-entry errors by index — and?include_entries=trueechoes the full created entries. - Post by id list:
POST /journal-entries/batch/postposts up to 100 draft ids (same compact shape,include_entriesopt-in). - Post by filter:
POST /journal-entries/post-by-filterposts all draft entries matching a date /reference/sourcefilter in one call — no id list, no 100 cap, safe to re-run. Ideal for committing a reviewed import. - Count first:
GET /journal-entries/countsizes a filter (e.g. how many drafts) without fetching rows.
For bulk loads from a CSV file, CSV import is strictly better — it passes the file through unchanged, groups rows into entries, and de-duplicates re-uploads so retries are safe.
Audit trail
Posting and reversal are append-only. Posted entries are immutable; corrections happen through reversals, never edits or deletes. This immutable audit trail is what makes balances and reports trustworthy and reproducible.
Next
- Periods & close — locking date ranges.
- Webhooks — react to
journal_entry.postedand more.