Skip to main content

List Journal Entries

GET 

/api/ledger/entries

Paginated journal entries listing. Discarded regeneration drafts are excluded; reversed entries stay visible so the reversal chain is browsable.

Authorization

Any valid API token can call this endpoint — no specific scope required. Manage tokens.

Authentication: Requires Bearer token (Personal Access Token).

Filters (Spatie QueryBuilder):

  • filter[type] — exact entry type (AccountingTransactionTypeEnum, ~30 values, e.g. sales_order_invoice, fba_shipments, bill)
  • filter[status] — exact lifecycle status: draft, posted, reversed
  • filter[display_status] — exact, comma-multi supported. Values: excluded, pending_sync, syncing, synced, update_pending, retrying, needs_attention, failed, removed_in_provider, conflict, voided
  • filter[sync_routing] — Routing/cadence axis (individual | daily | monthly)
  • filter[source_type] — exact source morph class
  • filter[batch_id] — exact batch ID
  • filter[date_from] / filter[date_to] — m/d/Y on effective_at (invalid dates silently ignored)
  • filter[connection] — connection ID; entries holding a sync link on that connection
  • filter[search] — partial match on reference, description, amount (total_debit), id, the resolved counterparty name and the synced provider document id (remote_id — e.g. a Xero invoice ID); scope with search_columns (e.g. search_columns=remote_id) and force exact match with search_strict_columns

Sorts: id, effective_at, reference, total_debit, created_at, type, counterparty, due_date, display_status, sync_routing (default: -effective_at). type sorts the stored entry-type enum; counterparty sorts the resolved supplier/customer/marketplace/provider name across polymorphic sources; due_date sorts the source purchase invoice's due date (other sources sort NULL).

Pagination: per_page default 10, clamped to max 100.

Each row carries a display_status object (key/label/color/reason). A batch member has no standalone status — its display_status mirrors its batch aggregate's live status at read time (Pending Sync / Syncing / Synced / Failed …); there is deliberately no batched status value. reason is the chip-tooltip hint: batch_member for batch members, comma-joined eligibility exclusion reasons (or no_accounting_connection) for excluded entries, null otherwise. Detail-only sections (lines, sync_links, operations, reversal_chain) are NOT included on the index — use the show endpoint.

Status is null for historical entries: the display_status and routing objects each report null key/label/color when an entry has left the live books — it is discarded (superseded by a regenerated replacement: replaced_by_id set + discarded_at) or a reversed original (status = reversed). Such rows can never sync (the batch rollup, reports and live lists all exclude them), so a frozen synced status would mislead; the supersession marker carries the why instead. A live reversal contra entry (reversal_of_id set, still posted) is NOT historical and keeps its status.

Advanced filters (filter_groups): base64-encoded JSON tree of AND/OR groups, applied via GroupedFilterSupport (same stack as Purchase Orders). Decoded shape:

{
"conjunction": "and",
"children": [
{"type": "condition", "condition": {"column": "reference", "operator": "contains", "value": "INV"}},
{"type": "group", "group": {"conjunction": "or", "children": [...]}}
]
}

Filterable columns: reference, description, entry_type, entry_status, entry_display_status, currency_code, counterparty (text: is, is_not, contains, does_not_contain, starts_with, ends_with, is_empty, is_not_empty, is_one_of); id, total_debit, total_credit, entry_batch_id (numeric: is, is_not, is_one_of, greater_than, less_than, greater_than_or_equal, less_than_or_equal, between, is_empty, is_not_empty); nominal_code_id (text, relationship — matches entries posting to the nominal code on any line); effective_at (date), posted_at, created_at (datetime: is, is_not, before, after, on_or_before, on_or_after, between, today, yesterday, past_week, past_month, …). counterparty has no stored column — it filters on the resolved supplier/customer/marketplace name via the same correlated subquery used for search and sort.

Unregistered columns in the tree are rejected with HTTP 400 (InvalidFilterQuery).

Each entry includes provider_link — the accounting-provider document it is synced to (the Xero/QBO column): { provider, remote_type, remote_id, link_type_label, label, link_route (internal detail page, or null when none exists yet), remote_url (provider deep link) }. It is null for entries not yet synced to a provider.

Per-lens columns (lens query param): the scoped Transactions tabs (Invoices / Journals / Purchase Orders) are thin wrappers over this one endpoint and request extra columns via ?lens=. The All tab, dashboard, detail and export paths send no lens, so the response keeps the shared columns only with zero extra queries. When a lens is present each row gains a derive-on-read block:

  • lens=invoices adds counterparty ({ name, id, link_type_label, link_route }; a CRM party has link_type_label "Supplier"/"Customer" + numeric id + link_route; an AR source (sales order / sales credit) with no CRM customer falls back to a marketplace customer — name "{Integration} Customer" e.g. "Amazon Customer", link_type_label "Customer", id + link_route null; a settlement source (Amazon financial-event group) falls back to a bare marketplace label — name the integration name e.g. "Amazon", link_type_label "Marketplace", id + link_route null; a ShipMyOrders provider invoice (a 3PL fulfilment-provider bill, no CRM party) falls back to a fixed provider label — name "SMO", link_type_label "Provider", id + link_route null; null/absent when the source resolves no party or was deleted), due_date (YYYY-MM-DD date-only, no timezone conversion; null unless the source carries one — only purchase invoices do), and tax_total (scale-4 string summed across the entry's lines, or null when no line carries tax).
  • lens=journals adds debit_account and credit_account — each is either { split: false, nominal_code_id, code, name, link_route } for a side posting to one account, or { split: true, count } when the side fans out across several codes, or null when the side has no lines — plus batch ({ id, label, status, period_start, period_end, link_route } or null for unbatched entries).
  • lens=purchase-orders adds counterparty (the supplier, same shape as above) and stage ({ ordered: true, received: none|partial|full, invoiced: none|partial|full }) derived from the source purchase order's receipt_status / invoice_status.

Each row carries line_count — the number of GL-impact journal lines on the entry (cheap withCount subquery) — powering the entries table's inline expandable "Lines" column. Fetch the lines themselves via Get Journal Entry Lines (/api/ledger/entries/{entry}/lines).

can_sync_now (boolean) — whether a manual "Sync Now" can push this entry right now. The per-transaction-type Sync toggle, granularity opt-out and the per-type start date only gate automatic/scheduled sync; a manual push bypasses them, so can_sync_now stays true for a benign "Auto-off" entry. It is false only when a structural blocker applies (connection inactive, entry is a batch member / open batch, internal-only, or before the accounting provider's conversion/start date) — those block even a manual push. Drives the UI split between a grey "Auto-off · push anytime" chip (display_status=excluded + can_sync_now=true) and an amber "Blocked" chip (display_status=excluded + can_sync_now=false).

provider_status (response field) — the provider's own transaction status on the synced document (primary sync link remote_status); null for unsynced entries. Backs the Provider Status column + filter.

Request

Responses

OK

Response Headers
    Content-Type