Journal Entries
Journal entries are the foundation of double-entry accounting. Every entry contains debit and credit lines that must balance.
Core Rule
Total Debits = Total Credits — every journal entry must balance, and needs at least two lines. Unbalanced entries are rejected with 400.
Create a Journal Entry
curl -X POST "https://api.ondayzero.com/api/v1/journal-entries" \
-H "Authorization: Bearer dz_your_token_here" \
-H "x-business-id: YOUR_BUSINESS_ID" \
-H "Content-Type: application/json" \
-d '{
"description": "Client payment received",
"date": "2026-03-20",
"line_entries": [
{ "ledger_id": "CASH_LEDGER_UUID", "amount": 100000, "debit_credit": "debit" },
{ "ledger_id": "REVENUE_LEDGER_UUID", "amount": 100000, "debit_credit": "credit" }
]
}'description and date are required, and date is a plain YYYY-MM-DD (entry_date is accepted as an alias). Optional top-level fields: currency (USD, CAD, AUD, EUR, GBP), invoice_id (attach the entry to an invoice), inventory_order_id, and combine_duplicate_lines.
Validation, all returned as 400:
- At least two lines, and total debits equal total credits.
- The date must not fall in a closed accounting period.
- Two lines on the same ledger, side and counterparty are refused (
VALIDATION_206) unlesscombine_duplicate_linesistrue, which sums them into one line (the response reportscombined_line_count).
Response data (abbreviated):
{
"success": true,
"data": {
"id": "019ab37c-e5b3-7c49-96e1-7c3b4d9f0e2b",
"entry_number": "JE-0042",
"entry_date": "2026-03-20",
"description": "Client payment received",
"source": "manual",
"creation_method": "manual",
"line_entries": [
{ "ledger_id": "CASH_LEDGER_UUID", "amount": 100000, "debit_credit": "debit" },
{ "ledger_id": "REVENUE_LEDGER_UUID", "amount": 100000, "debit_credit": "credit" }
],
"warnings": []
}
}The created entry has source: manual. The response includes advisory warnings (saved regardless) when the description is only an id or has no month or year.
Line Entry Fields
| Field | Type | Required | Description |
|---|---|---|---|
ledger_id |
UUID | Yes | The account (ledger) affected |
amount |
integer | Yes | Amount in cents, never negative; the side comes from debit_credit |
debit_credit |
string | Yes | debit or credit |
description |
string | No | Line memo |
project_id |
UUID | No | Project dimension for job costing |
See Ledgers for finding ledger IDs and Tags for tagging.
Entry Sources
Every entry records where it came from (source) and whether a person typed it (creation_method: manual) or the system generated it (creation_method: ai):
source |
Description |
|---|---|
manual |
User-created adjusting entries (what POST /api/v1/journal-entries produces) |
invoice |
AR entries when invoices are finalized |
sales_receipt |
Cash sale / refund receipt entries (existing data; the Sales Receipts module is hidden) |
bank_deposit |
Deposits clearing undeposited funds |
bill |
AP entries when bills are received |
credit_memo / vendor_credit |
Customer and vendor credits |
transaction |
Bank transaction categorization |
stripe |
Payment entries from Stripe webhooks |
shopify / square / ramp / plaid |
Integration-generated entries |
xero / quickbooks |
Entries imported from a Xero or QuickBooks Online migration |
fixed_asset |
Depreciation and disposal entries from Fixed Assets |
fx_revaluation |
Unrealized FX gain/loss revaluation entries |
system |
Internal accounting-engine entries (opening balances and similar) |
created_method is finer than creation_method: hand_typed (a person keyed the lines), bulk_upload (posted from a spreadsheet; also source: manual) or system.
List Journal Entries
curl "https://api.ondayzero.com/api/v1/journal-entries?limit=25&start_date=2026-03-01&end_date=2026-03-31&source=manual" \
-H "Authorization: Bearer dz_your_token_here" \
-H "x-business-id: YOUR_BUSINESS_ID"Filters: start_date / end_date, ledger_id (entries touching an account), invoice_id, id (comma-separated entry IDs), source (one or more, comma-separated; also accepts hand_typed and bulk_upload), creation_method (ai or manual), created_by_user_id, linked_to (bill, invoice, credit_memo, vendor_credit or none), description (partial match), search (entry number such as JE-0015, description, line memo, account name or line amount), plus the standard pagination parameters.
Each entry in the response carries entry_number, entry_date, source, creation_method, created_method, its line_entries, created_by, file_ids (attachments), link IDs (invoice_id, inventory_order_id, credit_memo_id, vendor_credit_id, bill_ids), and reversed_by_id if it has been reversed. GET /api/v1/journal-entries/{journal_entry_id} returns one entry with its comment thread.
Update, Reverse, Delete
- Update:
PUT /api/v1/journal-entries/{journal_entry_id}changesdateanddescription, and edits lines throughline_entry_changes— an object withcreate,update(by line ID) anddelete(by line ID) arrays. The result must still balance, and the entry's old and new dates must be outside closed accounting periods. To detach an entry from its source document sendnullforinvoice_id,inventory_order_id,credit_memo_id,vendor_credit_idorproduction_run_id; links can be cleared here but not repointed. An empty body returns the entry unchanged. - Reverse:
POST /api/v1/journal-entries/{journal_entry_id}/reversewith{"reversal_date": "2026-04-01", "reason": "..."}creates an offsetting entry with flipped debits and credits — the audit-friendly way to undo a posted entry.reversal_dateis required (omitting it is a400,VALIDATION_695);reasonis optional and goes into the reversing entry's description. Both the original's date and the reversal date must be outside closed periods. Reversing is idempotent: a second call returns the existing reversal. The response hasoriginal_entry_id,reversal_entry_id,original_description,reasonandreversal_date, and the original getsreversed_by_id. - Delete:
DELETE /api/v1/journal-entries/{journal_entry_id}permanently deletes an entry (and its lines) that nothing else depends on — no linked transaction, bill, invoice, inventory order, credit memo or vendor credit — and whose date is outside a closed accounting period. Deleting a reversal clears the original'sreversed_by_id. Entries generated by a source record (invoice, bill, transaction…) should be changed through that record instead; if you must remove one, clear the link first or reverse it. - Attach a document:
PATCH /api/v1/journal-entries/{journal_entry_id}/attachment(multipartfile: PDF, PNG, JPG or JPEG). The S3 keys accumulate infile_ids.
Bulk Upload
Download the template with GET /api/v1/journal-entries/bulk-upload/template, upload the filled file (see File Uploads), preview with POST /api/v1/journal-entries/bulk-upload/preview and post with POST /api/v1/journal-entries/bulk-upload/post, both taking {"s3_key": "..."}.
Each row is one line; rows are grouped into entries by entry_number, or by consecutive rows sharing a date and description. Columns (aliases accepted): date, description, ledger_name or account_number, debit, credit (amounts in dollars in the file), optional line_entry_description and entry_number. The preview returns entries, errors, row_errors (unknown accounts with nearest ledger names), totals, is_valid and a plan_hash. The post is refused with 400 unless that s3_key was previewed without errors in the last 24 hours and the file is unchanged, or when a plan_hash you send differs from the preview's. It returns 202 with a workflow_id and status: started; for local-ledger businesses the file posts atomically (all entries or none).
Errors
All 400 (validation_error) unless noted.
code |
Cause |
|---|---|
GEN_002 |
Body failed schema validation: fewer than two line_entries, total debits differ from total credits, debit_credit not debit/credit, a negative amount, or a malformed ledger_id |
VALIDATION_030 / VALIDATION_031 |
Missing description / date |
VALIDATION_206 |
Duplicate lines (same ledger, side and counterparty) without combine_duplicate_lines; a date in a closed accounting period; or an update carrying an unsupported field or trying to repoint a source-document link |
VALIDATION_695 |
reverse without reversal_date |
BUSINESS_056 |
DELETE refused because a transaction, bill, invoice or other record still points at the entry |
VALIDATION_571 / 572 / 573 / 640 |
Bulk post without a clean, current preview of that s3_key (never previewed, file changed, preview had errors, plan_hash mismatch) |
NOT_FOUND_013 |
Journal entry not found in this business (404) |