Skip to content

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

bash
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) unless combine_duplicate_lines is true, which sums them into one line (the response reports combined_line_count).

Response data (abbreviated):

json
{
  "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

bash
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} changes date and description, and edits lines through line_entry_changes — an object with create, update (by line ID) and delete (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 send null for invoice_id, inventory_order_id, credit_memo_id, vendor_credit_id or production_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}/reverse with {"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_date is required (omitting it is a 400, VALIDATION_695); reason is 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 has original_entry_id, reversal_entry_id, original_description, reason and reversal_date, and the original gets reversed_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's reversed_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 (multipart file: PDF, PNG, JPG or JPEG). The S3 keys accumulate in file_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)