Ledger (Accounting v2)
Get Dashboard Summary
The Accounting v2 section landing summary — the whole dashboard in one call. Composed from the existing managers/repositories so it never diverges from the tab it summarizes.
List Outbox Failures
Paginated terminally-failed `ledger_outbox` rows for the dashboard failures drill-down, newest failure first (`processed_at` carries the failure timestamp for failed rows).
Retry Outbox Failures
Re-queue terminally-failed outbox rows for another drain pass: reset them to pending, clear the attempt counter (a fresh max-attempts budget — otherwise the next failure would immediately re-terminate them), make them claimable now, and kick the drain.
List Journal Entries
Paginated journal entries listing. Discarded regeneration drafts are excluded; reversed entries stay visible so the reversal chain is browsable.
List Needs-Attention Groups
The needs-attention inbox feed: non-discarded entries whose display status needs a human (needs_attention, failed, conflict, removed_in_provider — capped at the latest 1000), grouped server-side by error cause so the UI can offer fix-once-retry-all.
List Stale Pre-Cutover Entries
Imported pre-cutover journal entries whose SOURCE document was edited after the entry was imported. Automatic generation deliberately never rewrites frozen pre-cutover history, so these entries' figures no longer match their documents — this feed makes that drift visible so it can be fixed deliberately with Rebuild Journal Entry (single) or Bulk Entry Action with action `rebuild`.
List Source Types
Distinct source-document origins present on live (non-discarded, non-batch-aggregate) journal entries — powers the entries-list 'Source' quick filter so it offers only origins the tenant actually has (e.g. 'Amazon Settlement', 'Sales Order', 'Bill'). Lets users isolate settlement-derived bills/invoices that have no batch.
List Provider Statuses
Distinct provider transaction statuses (sync link remote_status — Xero DRAFT/AUTHORISED/PAID/VOIDED, QBO Paid/Open, …) present for the tenant, as {value, label} options for the entries table's Provider Status filter (filter[provider_status]=...). Values are provider-native raw statuses; labels are Title Case.
Get Journal Entry
Journal entry detail: everything from the index row plus the detail-only sections —
Get Journal Entry Lines
The entry's GL-impact journal lines — a lightweight cousin of **Get Journal Entry** that returns only the `lines` array (no sync_links, operations or reversal_chain). Drives the entries table's inline 'Lines' row expansion.
Get Journal Entry Attachment Sync
The sync state of each source-document file attachment across every connection. When the entry's source document (purchase invoice, vendor credit, or vendor deposit) has attachments, this returns one row per (attachment × connection) describing whether the file was uploaded to the matching Xero/QBO document.
Rebuild Journal Entry
Rebuild the entry's SOURCE document INLINE (single-entry action): the user clicked Rebuild on the entry detail page and expects the regenerated entry to be current the instant the request returns, not after the async drain catches up. The rebuild writes the same `updated` outbox row a real source mutation rides, then processes JUST that row synchronously through the shared outbox processor (build → persistSet → batch-assign → sync-select) — so posted-immutability (reversal + replacement) holds exactly as on the async path.
Refresh Journal Entry
Refresh this entry's amounts from its SOURCE document IN PLACE — the light counterpart to Rebuild. Where Rebuild corrects a posted entry by writing a reversal + a replacement, Refresh re-derives the entry's lines and totals and writes them onto the SAME entry: its id, status and accounting-provider link are all kept, so a posted, already-synced entry whose local figure drifted is corrected WITHOUT creating a new entry and WITHOUT touching the linked Xero / QuickBooks document. After the rewrite the sync state is re-graded — a still-editable remote is offered an update, while a settled/locked or voided remote is left as-is (nothing is pushed).
Resync Entry to Provider
Push this SINGLE entry to the connected accounting provider SYNCHRONOUSLY (InlineLedgerSyncRunner): the provider round-trip runs inline within the request, so the response already reflects the real Synced / Failed outcome — there is no interim 'queued' state to poll. This powers the entry detail page's 'Resync Now' button and its live 'talking to the provider' animation. The bulk equivalent — `POST /api/ledger/entries/bulk` with action `sync_now` — stays an async tracked job.
Preview Bill Settlement With Vendor Credits
Preview the plan to settle a bill (a purchase-invoice entry) using the vendor credits already allocated against it — the read side of the 'sync & mark paid' flow. Reports whether the entry is a settleable bill, whether the bill itself is synced to the accounting provider, and the state of each applied vendor credit: `missing_transaction` (its accounting transaction has not been generated yet), `unsynced` (generated but not pushed to the provider), or `synced` (its credit note is pushed and its allocation applied). Use it to show exactly what will happen before committing.
Settle Bill With Vendor Credits
Settle a bill (a purchase-invoice entry) using the vendor credits allocated against it so the connected accounting provider marks the bill PAID — the missing half of two-way sync when the allocations exist here but were never pushed. Dispatches a tracked background job that (1) syncs the bill so its remote document exists, (2) for each applied vendor credit generates its accounting transactions if missing, syncs its credit note, then pushes the allocation (Xero credit-note allocation / QuickBooks zero-dollar bill payment), and (3) re-reads the bill so its link re-grades to Synced, Locked (paid). Idempotent — re-running skips work already done, so a partial failure is safe to retry.
Clear Sync Errors
Dismiss THIS single entry's parked sync errors (needs_action / failed_retryable) SYNCHRONOUSLY by superseding its open sync operations across every connection, then return the refreshed entry so the detail page drops the error banner without a page refresh. This powers the entry detail page's 'Clear errors' button.
Recreate Accounting Transaction
'Recreate document' for an entry whose remote document was VOIDED or DELETED in the accounting provider (display_status `voided` — 'Synced, Voided' — or `removed_in_provider` — 'Synced, Deleted'). Once a provider document is voided/deleted there is nothing left to update on it, so this reverse+replaces the entry into a fresh UNLINKED draft replacement and mints it from the same source, even though the source itself never changed.
List Relink Candidates
Provider documents this voided or deleted entry could be linked to INSTEAD of recreating one — the confidence-ranked 'link to an existing document' picker. Use this when the accounting document was voided in the provider and a replacement already exists there (e.g. a bill was voided and re-raised): rather than minting a duplicate, adopt the document that is already in the provider.
List Document Link Candidates
Provider documents an unmatched ledger entry could be linked to for the first time — the confidence-ranked picker for pointing a never-linked SKU entry at the document it already corresponds to in the provider (e.g. a bill that was imported into the provider some other way), instead of syncing a duplicate. This is the sibling of List Relink Candidates, which recovers a voided/deleted link; this one is for an entry that has never been linked.
Link Entry to Existing Document
Re-link a voided or deleted entry to an EXISTING provider document — the replacement already re-created in the accounting provider — instead of minting a fresh one. Repoints the entry's voided link onto the chosen document in place and records it so the NEXT sync pushes an update to that existing document, never a duplicate create.
Fetch Latest From Provider
The inbound mirror of 'Sync Now' (— two-way sync, on demand): instead of PUSHING local changes out, it re-reads this entry's remote document(s) FROM the accounting provider (Xero / QuickBooks) and re-grades the sync link RIGHT NOW. For each supported, still-linked connection it fetches the current remote truth via the same InboundChangeProcessor funnel the webhook + nightly sweep use — picking up drift (→ linked_conflict), a void/delete (→ remote_gone), a settled/paid lock (→ remote_locked), or a previously settled lock that has re-opened for edits — the paying payment / credit note was reversed at the provider, e.g. Xero PAID → AUTHORISED — which un-parks the link (→ linked_stale) so it is ready to resync — and refreshes `last_remote_seen_at`. A 404 at the provider degrades to a synthetic-deleted snapshot so the link still parks remote_gone rather than silently staying Synced.
Get Entry Sync Inspection
Explain why a journal entry is (or is not) syncing to the accounting provider. Read-only — never writes.
Set Sync Override
Set or clear the entry's durable sync override (Layer A). This endpoint is THE sanctioned writer of `ledger_journal_entries.sync_override` — the override is durable user intent, and regeneration never touches the column, so it survives rebuilds (fixes the legacy is_sync_enabled clobber bug).
Set Reference Override
Set or clear the entry's durable **reference override**. The override is the custom reference shown in SKU and SENT TO THE ACCOUNTING PROVIDER (Xero/QBO) as the reference / invoice number, in place of the auto-derived `reference`. Like `sync_override` it is durable user intent — carried across draft regeneration and posted reversal+replacement — and the original `reference` is always retained for traceability.
Set Nominal Override
Pin a durable manual **nominal-code (GL account) override** on the entry: 'when this entry would post to `from_nominal_code_id`, post to `to_nominal_code_id` instead.' The override is keyed by the entry identity and **re-applied during derivation on every rebuild**, so it survives regeneration the way `sync_override` does — while amounts, dates and lines keep tracking the source document (it pins ONLY the account, NOT a whole-entry lock).
Remove Nominal Override
Remove the nominal-code override for one originally-derived account (`from_nominal_code_id`) so the line reverts to its source-derived account on the next rebuild.
Bulk Entry Action
Dispatch one tracked bulk action over ledger entries. Eligible/excluded counts are computed server-side via EligibilityResolver BEFORE dispatch, so the response counts are accurate. The job reports progress via the tracked job log endpoints.
Export Journal Entries
Download the journal-entry list as an XLSX or CSV file (the Export modal). Honours the same filters as the index plus `format` (xlsx/csv) and `columns` (visible-column subset). Scope is expressed by the filters sent: 'all' sends only the base scope, 'filtered' sends the active filters, 'selected' adds filter[ids]. Returns a binary file download (Content-Disposition), not JSON. Requires a Bearer token.
Get Selection Summary
Per-nominal-code debit/credit/line-count breakdown of a selection of entries plus grand totals — powers the table's expandable Selection Summary panel (parity with the legacy SelectedTransactionsSummary), sourced from the real journal lines.
Repost All Entries
Repost every current (non-discarded, non-reversed) ledger entry so posted GL lines pick up a changed chart of accounts — the onboarding backfill after adopting a provider's chart. Reuses the bulk rebuild pipeline: posted entries auto-reverse + replace with the new nominal codes, drafts regenerate in place, batch aggregates route through the batch state machine. Requires the `accounting.sync` permission. Returns the tracked job log id + eligible/excluded counts.
Get Entry by Source
The current v2 ledger journal entry for a source document (or data:null). Lets a source-doc accounting tab (Purchase Invoice, PO, Sales Order, etc.) show its v2 ledger entry as the accounting transaction — linked to /v2/ledger/entries/{id} — instead of the legacy v1 accounting_transaction. source_type is the source model FQN.
List Entries by Source
Every live (non-discarded, non-reversal, non-reversed) v2 ledger journal entry for a source document, as a lightweight list. Where /by-source returns only the latest single entry, a source can post more than one current entry — an Amazon settlement posts both an invoice AND a bill — so a source-doc accounting list (e.g. the Amazon bank-transaction detail page) needs the full set. Each item links to /v2/ledger/entries/{id}. source_type is the source model FQN.
List Payments
SKU-side payments (App\Models\Payment) paginated, each with its source document and the linked accounting-provider payment (Xero/QBO) cross-link. Backs the Accounting v2 Payments tab. `provider_payment` is null when the payment has not been synced to a provider; `status` is synced / error / not_synced.
Sync Payment to Provider
Manually push (or re-push) one SKU payment to the connected accounting provider (two-way payment sync) — the row action on the Payments tab. Runs inline: eligibility rejections and provider validation errors are persisted to the payment's last_sync_error and returned as 422; transport/auth failures return 502 with no state change.
List Batches
Paginated ledger batches listing: the grouping key (type, period, channel, currency), lifecycle status with the derived `is_dirty` flag, members_count and the aggregate journal entry summary.
Get Batch
Batch detail: the batch resource (under the `batch` key) plus `totals_by_account` — the aggregate journal entry's GL impact grouped by nominal code, one row per account with its debit and credit sums (4-decimal strings).
List Batch Members
Paginated member documents of a batch — the A2X-style lineage view. Each member row carries:
Get Batch History
Batch membership + correction history. Returns two keys:
Get Batch Activity Log
The batch's standard LogsActivity feed (the detail page Activity tab): creation plus every status transition (open, dirty, closing, posted, reopened) with old/new values, causer and timestamp. Guarded query-update transitions are logged manually by the repository, so the feed covers the full state machine.
Rebuild Batch
Manually rebuild the batch's aggregate journal entry: marks the batch dirty (posted batches flip to `reopened` — the post-post correction pathway, where the rebuild reverses + replaces the posted aggregate entry) and dispatches a background job immediately (no debounce for a manual request; the job's per-batch uniqueness still collapses doubles).
Get Account Overview
GL account drill-down header (the nominal code detail page): the account's identity plus debit/credit/net totals and line count. Accepts the SAME filter[...] and `filter_groups` params as the lines endpoint, so the KPIs always describe exactly what the lines table shows.
List Account Lines
Paginated journal lines hitting one GL account — every line of every non-discarded entry, reversals included (originals and reversal mirrors both appear and net to zero, as a general ledger account view should read).
Update Account Type
Reclassify a GL account's type (the account-class enum).
Get Ledger Account Activity Log
Returns a paginated audit trail for a single general-ledger account, newest first. Each entry records the event, the affected record, a snapshot of changed properties, who made the change, and when. Supports free-text search across the actor name, event and properties.
Get Granularity Matrix
Read the type × channel posting granularity matrix stored on the accounting connection (— the Cin7-style settings matrix), consumed by BatchGranularityResolver.
Update Granularity Matrix
Validate, clamp and persist the type × channel granularity matrix. Returns the matrix AS SAVED — the client must re-render from the response because of the server-side clamp.
Get Generation Status
Effective state of the ledger-generation kill switch plus outbox health (the Settings → Generation card).
Update Generation Switch
Flip the per-tenant generation kill switch (`AccountingSettings::$ledger_generation_enabled`) and return the refreshed status.
List Connections
The connections panel view-model: one row per supported accounting integration instance (QuickBooks Online / Xero).
Get Setup Status
Accounting setup wizard state: which steps are done and what's left.
Apply Suggested Chart
Manual accounting setup in two parts: create the chart of accounts, then wire each NominalCodeMappingSettings role to one of those accounts. Gated by the `accounting.manage_settings` permission.
Get Accounting Dimensions
Read the tracking-category (Xero) / class (QBO) settings for the accounting connection: the active provider, its dimension capabilities, the mirrored tracking categories/options (or classes) available to map, the sales channels (including a synthetic 'SKU.io (internal)' pseudo-channel for internal orders), and the saved per-channel slot configuration. Used to build P&L segmentation such as COGS by sales channel.
Update Accounting Dimensions
Save the per-channel slot configuration. Up to two dimension slots are allowed; each slot must reference a distinct provider dimension (a 422 is returned otherwise).
Refresh Accounting Dimensions
Re-pull the connected provider's tracking categories/options (Xero) or classes (QBO) into the local mirror, then return the refreshed settings payload. Runs synchronously; the datasets are small.
Create Accounting Dimension Values
Create one or more tracking options (Xero) or classes (QBO) in the connected provider and mirror them locally. Names are matched case-insensitively against the mirror first, so an existing value is reused instead of duplicated.
Create Accounting Dimension
Create a new Xero tracking category in the connected organisation. Only succeeds when a free active category slot is available (Xero allows at most two active tracking categories).
Get Lock Dates
The accounting lock-date control surface: the effective lock (later of your lock date and enforced provider locks), the internal lock with audit info, per-connection provider locks + enforcement, and the locked-period policy.
Apply Provider Lock Suggestion
Adopt a connection's imported provider lock date as the internal accounting lock date.
Toggle Provider Lock Enforcement
Set whether a connection's imported provider lock is enforced locally.
Get Opening Balance Overview
The opening balance is an INTERNAL journal entry seeding each GL account's position as of the tenant's books-start date — it is NEVER synced to the accounting provider (non-bypassable `internal_only` eligibility), which already holds these balances. The inventory line is derived from the valuation at end of day prior to the Inventory Start Date and snapshotted at apply time. Spec: docs/plans/ledger-opening-balance-entry.md.
Upsert Opening Balance Draft
The opening balance is an INTERNAL journal entry seeding each GL account's position as of the tenant's books-start date — it is NEVER synced to the accounting provider (non-bypassable `internal_only` eligibility), which already holds these balances. The inventory line is derived from the valuation at end of day prior to the Inventory Start Date and snapshotted at apply time. Spec: docs/plans/ledger-opening-balance-entry.md.
Delete Opening Balance
The opening balance is an INTERNAL journal entry seeding each GL account's position as of the tenant's books-start date — it is NEVER synced to the accounting provider (non-bypassable `internal_only` eligibility), which already holds these balances. The inventory line is derived from the valuation at end of day prior to the Inventory Start Date and snapshotted at apply time. Spec: docs/plans/ledger-opening-balance-entry.md.
Preview Opening Balances from Provider
Reads the selected connection's Trial Balance 'as at' the snapshot date and proposes opening-balance lines mapped onto SKU's chart of accounts. Works for both QuickBooks Online and Xero connections — the trial balance is fetched from whichever provider the connection belongs to. Read-only — nothing is persisted; the frontend uses the result to seed the opening-balance draft editor, which the user then reviews and applies.
Create Opening Balance Equity Account
Create the provider-conventional 'Opening Balance Equity' account when the chart of accounts has none, so the opening-balance form's balancing-equity selector has something to point at. The created account is type `Equity` and is a never-synced internal plug. Its `code` is the first free preferred code (970, then 3000, then 9999, then numerically incremented past the last candidate), checked against ALL codes including archived ones since `code` is the unique key.
Apply Opening Balance
The opening balance is an INTERNAL journal entry seeding each GL account's position as of the tenant's books-start date — it is NEVER synced to the accounting provider (non-bypassable `internal_only` eligibility), which already holds these balances. The inventory line is derived from the valuation at end of day prior to the Inventory Start Date and snapshotted at apply time. Spec: docs/plans/ledger-opening-balance-entry.md.
Rebuild Opening Balance
The opening balance is an INTERNAL journal entry seeding each GL account's position as of the tenant's books-start date — it is NEVER synced to the accounting provider (non-bypassable `internal_only` eligibility), which already holds these balances. The inventory line is derived from the valuation at end of day prior to the Inventory Start Date and snapshotted at apply time. Spec: docs/plans/ledger-opening-balance-entry.md.
Get Pending Start Date Transition
Returns the pending guarded accounting-start-date change awaiting confirmation, or `null` under `start_date_transition` when the books are settled (no change in flight).
Apply Start Date Transition
Confirms the parked accounting-start-date change. The opening balance is re-imported IN PLACE from the replacement trial balance at the new anchor (the same opening-balance row is kept — preserving its notes, created-by, equity account, applied status and history), its entry is reversed and reposted through the standard outbox pipeline, and the reconciliation is re-anchored + re-backfilled via a tracked Sync Provider GL Journals job.
Rollback Start Date Transition
Abandons the parked accounting-start-date change while it is still pending. The effective accounting start date is reverted to its old value and the transition settles to `cancelled` — the posted books are left completely untouched (no reversal, no rebuild, no reconciliation re-anchor). Safe because while pending the books were never changed.
List Generation Candidates
Preview of the source documents eligible to have ledger entries generated — the entries page 'Generate' modal. A document is a candidate when it passes the same readiness gate the legacy generation used (`scopeAccountingReady`: drafts, unmapped lines, Amazon channel orders, … are excluded) AND it has no current (non-discarded, non-reversed) ledger entry.
Run Generation Now
Trigger on-demand entry generation: every candidate in scope (see List Generation Candidates) is enqueued as an `updated` ledger outbox row and the drain job is dispatched immediately — the SAME pipeline the per-minute schedule runs, just started now. The posting service's source_version_hash makes already-current documents a free no-op, so the trigger is idempotent.
Get Reconciliation Window
The connection's reconcile window and per-kind cache freshness — 'documents cached from X through Y, last refreshed Z'.
Reconciliation Matches
The three reconcile buckets for a connection over the (optional) document-date window.
List Excluded SKU Entries
The SKU ledger entries that are excluded from sync for a connection over the (optional) document-date window — unlinked entries that are not eligible to sync, each with the reason(s) it's blocked.
List Matched Entries
The SKU ledger entries already linked to a provider document on a connection, as a paginated, filterable, sortable list. Each row also carries the linked provider document's snapshot in `remote` (remote total, status, and a deep link) so a variance between the two sides can be spotted.
List Unlinked Entries
The SKU ledger entries in the window that are NOT yet linked to a provider document on a connection — both the sync-eligible ones and the ineligible (excluded) ones — as a paginated, filterable, sortable list. Each row carries its live per-connection sync `eligibility` (`eligible` plus the block `reasons`, e.g. `type_disabled`, `channel_disabled`) so the ineligible ones can be flagged.
List Provider-only Documents
The provider documents (Xero/QBO) that have NOT been linked to a SKU ledger entry and have not been dismissed, over a connection's window — a normalized, paginated, filterable, sortable list across every document kind (invoices, bills, credit notes, journals).
List Dismissed Documents
The provider documents previously dismissed from reconciliation on a connection, as a paginated, filterable, sortable list — the 'show dismissed' view for reviewing and restoring documents that were hidden. Restore them with POST /api/ledger/reconciliation/restore or /restore/bulk.
Get Reconciliation Counts
The unfiltered counts for a connection: matched (linked), SKU-only (eligible unlinked), excluded (ineligible unlinked), and provider-only (unlinked provider documents).
List Entry Link Candidates
The SKU ledger entries a provider-only document could be linked to — the confidence-ranked picker for the reverse direction of the manual link (pick the SKU entry that matches a provider document, identified by `remote_type` + `remote_id`).
Reconciliation Report
The period-comparison report for a connection over the (optional) document-date window: monthly SKU-ledger totals vs the provider-mirror, with per-period and grand-total variance highlighting. Read entirely off the local provider document cache — no provider API calls.
Preview Auto-Match
Compute the confident auto-match pairs for a connection WITHOUT linking anything — the preview behind the reconcile Auto-Match confirmation. Returns one row per proposed pairing of an unmatched SKU entry (SKU-only or excluded) to a provider-only document whose reference, amount (within tolerance) and date (within window) confidently match. Review the rows, then send the ones you want to keep to Run Auto-Match to link them.
Run Auto-Match
Link the auto-match pairs confirmed from Preview Auto-Match. Send a `matches` array of `{entry_id, remote_type, remote_id}` rows; the eligible set is recomputed and only the intersection is linked, so a pairing that has since become ineligible is skipped rather than force-linked. Returns the number of links forged; each is recorded linked_stale so the next sync pushes an Update. An empty `matches` array links nothing.
Backfill Provider Cache
Populate the local provider document cache (the xero_/qbo_ mirror) for a connection by dispatching a tracked background job that downloads every remote document over the reconcile window. The reconcile screen, matching, and the period-comparison report all read off this local cache, so this is the on-demand, UI-triggered equivalent of the `ledger:cache:backfill` artisan command. Returns the tracked job log id so the frontend can follow progress via the tracked job log endpoints.
Link Remote Document
Manually link a SKU entry to an existing provider document — the operator's 'this is the same document' action. Recorded linked_stale so the next sync pushes an Update bringing the remote into line.
Dismiss Provider Document
Dismiss a provider-only document — mark a remote document as 'not a SKU document' so it stops surfacing in the provider-only bucket. Idempotent on the remote identity.
Bulk Dismiss Provider Documents
Dismiss many provider-only documents from reconciliation in one action (idempotent). Provide either `ids` (an array of composite '{remote_type}:{remote_id}' identifiers — the selected rows) or `apply_to_all: true` with `filters` to dismiss every provider-only document matching those filters. An optional `reason` is stored on each.
Bulk Restore Provider Documents
Restore (un-dismiss) many provider documents in one action, returning them to reconciliation. Provide either `ids` (composite '{remote_type}:{remote_id}' identifiers — the selected rows) or `apply_to_all: true` with `filters` to restore every dismissed document matching those filters.
Restore Provider Document
Undo a dismissal — restore a previously dismissed provider document to the reconcile screen.
List Provider Payments
Provider payments (Xero/QBO) from the local mirror for a connection, each flagged with whether it has already been imported as a SKU payment. Backs the Reconciliation > Payments view.
Import Provider Payments
Import provider payments into SKU — create the matching SKU payment on each payment's source document, via the same idempotent pathway as the automatic inbound pull. With remote_ids, only those; omit remote_ids to import every not-yet-imported provider payment. Requires accounting.sync permission.
Get Inventory Divergence
Inventory sub-ledger reconciliation (Lane 2). SKU is the authoritative subsidiary ledger for inventory, so this returns one row per inventory control account comparing SKU's per-account inventory value against the provider's live GL balance (pulled from the Xero Trial Balance) — most divergent first.
Inventory Recon Accounts (Earmark Suggestions)
Candidate inventory control accounts for the daily reconciliation earmark step. Pulls SKU's current-asset nominal codes (plus any warehouse-mapped account), cross-references the provider's cached current-asset chart (Xero `xero_accounts`, matched by code — **no live provider call**), computes each account's point-in-time SKU inventory value as-of now, and flags which look like inventory accounts. Suggested-then-code ordered.
Save Earmarked Inventory Accounts
Replace the earmarked inventory control account set and return the refreshed candidate list. The submitted set replaces existing earmarks wholesale — an empty array clears them. The earmarked accounts are what the daily series and rebuild job operate on.
Inventory Daily Divergence Series
The materialized daily reconciliation series, one entry per earmarked account (or per `nominal_code_ids[]` when given): SKU's point-in-time inventory value vs the provider GL balance for each end-of-day, and the stored divergence — the 'which days diverge' view. Reads materialized rows only (**no live provider or valuation call**); the SKU side is built by the rebuild job and the provider side by capture-provider.
Get Inventory Reconciliation Day Detail
For one earmarked inventory control account on one day, returns the reconciled figures for that day (SKU inventory value, provider GL balance, and their divergence) plus the underlying detail that produced them: the provider journal lines posted to the account that day and the inventory movements that changed SKU's value that day. Use it to trace a divergence to its source documents — reads materialized/cached data only, no live provider call.
Get Inventory Reconciliation Awaiting-Sync Items
Per earmarked inventory account, the SKU ledger activity not reflected in the provider balance, split by sync disposition: `pending_ledger`/`pending_count` WILL reach the provider once it syncs (self-resolving), while `excluded_ledger`/`excluded_count` will NEVER sync (dated before the books' sync start date, or internal-only) — a permanent mismatch a measure-from baseline sets aside. Batch-aggregate aware (a batched document counts once, via its batch). Requires connection_id.
Get Inventory Reconciliation Opening Breakdown
The warehouse-first opening breakdown for reconciling inventory to the accounting provider. For each earmarked inventory control account, returns the provider general-ledger balance and its divergence from the SKU inventory value at the opening date, broken down into the individual warehouses that hold that account's stock. The provider balance and divergence are reported at the account level (a provider account has no warehouse dimension); the SKU side sums standard and Amazon FBA inventory per warehouse. Warehouses that currently hold no stock are still listed so an account's full warehouse footprint is visible. The opening date is the reconciliation baseline when one has been set, otherwise the configured inventory start date. Reads materialized/cached data only — no live provider call.
Get Inventory Reconciliation Baseline
The reconcile-opening context for an inventory reconciliation connection: the current recon baseline (a 'line in the sand' date the divergence view is measured from — null when none is set), the inventory start date the series otherwise opens from, and the accounting provider's lock date.
Set Inventory Reconciliation Baseline
Draw the reconciliation baseline: measure divergence from `baseline_date` forward and freeze the history before it out of the view. View-only and reversible — no reconciled figure, opening balance, or start date is changed. Returns the refreshed context.
Clear Inventory Reconciliation Baseline
Remove the reconciliation baseline; the full history returns to the divergence view. Returns the refreshed context.
Get Inventory Reconciliation Adjustment Preview
The proposed adjusting journal that would move the provider's inventory control account to match SKU as of a date — computed read-only so you see exactly what would post. `divergence = provider − SKU`; a positive gap credits the inventory control account (reducing it) and debits the counter, both for the same amount. When the date is inside a locked provider period `postable` is false: post the journal dated on or after `earliest_postable_date` instead. Accounts backed by Amazon warehouses (FBA/AWD) are Amazon-sourced — their stock comes from Amazon and cannot be adjusted in SKU (`sku_side_adjustable` is false), so they can only be reconciled provider-side; other accounts may instead correct SKU's opening for the warehouses in `warehouses`.
Get Inventory Reconciliation Adjustment Journal
Where a posted inventory reconciliation adjustment landed in SKU's ledger — so a client can deep-link the user to the journal entry the adjustment created. The adjustment posts asynchronously, so immediately after posting this returns `pending: true` with null ids; poll it with the returned `adjustment_id` until `pending` is false, then use `journal_entry_id` to open the journal.
Ensure Adjustment Counter Account
Ensure a conventional 'Opening Balance Equity' counter account exists (creating it if the chart of accounts has none), so the adjusting journal's balancing leg has a target. Idempotent — reuses an existing opening-balance-named account.
Post Inventory Reconciliation Adjustment
Post the adjusting journal to bring the provider's inventory control account into line with SKU. The amount and direction are recomputed server-side from the divergence (the client can't post an arbitrary figure); the balanced journal is posted to the provider as a manual journal. An already-aligned or locked-period account is rejected 422.
Reverse Inventory Reconciliation Adjustment
Reverse (delete) a posted inventory reconciliation adjustment while its journal has not yet synced to the accounting provider, discarding the draft journal so the opening can be re-reconciled (for example, at a corrected date). Once the journal has synced to the provider this returns 422 — undoing it then requires posting a reversing entry in the provider.
Provider GL Sync Estimate
A **pre-flight, zero-API** estimate for the unified provider GL sync (Lane 2): how many Xero calls the operation would cost for the chosen window against how many remain in today's API budget, so the user can weigh it before spending any of it. Computed locally from the cached journal cadence (distinct journals over their date span) plus the one Trial Balance anchor call — it makes **no** calls to the provider.
Rebuild Inventory Daily Series
Kick the background tracked job that materializes the **SKU side** of the daily series (point-in-time inventory value per account per day). Free — no provider calls. Returns the tracked job log id so the UI can follow it in the global progress tray.
Capture Provider Inventory Balances
Capture the provider's GL balance for the earmarked inventory accounts as-of a day onto the daily series — the metered counterpart to the (free) SKU-side rebuild. **Makes one live Xero Trial Balance call** (needs `accounting.reports.read`), so callers drive the cadence; the provider series is intentionally sparse (only captured days get a row). Days/accounts with no matching provider account are skipped.
Sync Provider GL Journals
Kick the background tracked job that pulls the provider's **GL journals feed** (Xero Journals) into the local cache — the metered half of the daily reconciliation. One paginated pull per run; resumes incrementally from the highest JournalNumber already cached. Once cached, **Rebuild Inventory Daily Series** reconstructs the daily provider balance from the cache with no further provider call. Returns the tracked job log id so the UI can follow it in the global progress tray.
List Statement Imports
Paginated list of prior bank-statement screenshot extractions (most recent first), each with its parsed line count and spent/received totals.
Upload Statement Screenshot
Upload a screenshot of bank statement lines and **queue** its OCR extraction (Azure Document Intelligence prebuilt-layout). Returns immediately with the import in the `extracting` state plus a top-level `tracked_job_log_id`; the parsed lines arrive once the background a background job completes (watch it via the tracked job log endpoints, then re-fetch the import via GET `.../bank-statements/{id}`). Deferring the OCR keeps an unreachable provider from hanging the upload request.
Get Statement Import
A single statement-screenshot extraction with all its parsed lines.
Delete Statement Import
Delete a statement extraction and its parsed lines.
List Line Match Suggestions
For each parsed line in an import, candidate SKU ledger entries it could reconcile against. Money-in lines (Received) match AR documents (Sales Order Invoice, Vendor Credit); money-out lines (Spent) match AP documents (Bill, Purchase Order Invoice, Sales Credit). Candidates are entries of the matching type whose total is within $0.01 of the line amount and whose effective date is within 60 days of the statement date, ranked by date proximity then amount closeness. `synced` flags entries already pushed to the provider.
Confirm Line Match
Confirm one or more ledger entries as the match for a line. Persists a denormalized snapshot of each match (so it survives ledger rebuilds) and marks the line `matched`. Idempotent — re-matching the same entry is a no-op.
Clear Line Match
Remove all confirmed matches for a line and mark it `unmatched`.
Dismiss Line Match Suggestion
Declare a suggested candidate 'not a match' for this line. The candidate's identity is pinned to the line (order-independent fingerprint), so the same document/combination is never re-offered by Line Match Suggestions or confirmed by Auto-match — and a dismissed group combination's per-supplier slot frees up for the next-best combination on the next refresh. Undone by the Restore Dismissed Suggestions endpoint.
Restore Dismissed Suggestions
Clear every 'not a match' verdict on the line, putting all dismissed candidates back into circulation — they reappear on the next Line Match Suggestions call. Returns how many dismissals were removed.
Auto-match Single-candidate Lines
Confirm every still-unmatched line in the import that has EXACTLY ONE candidate (an unambiguous match). Lines with zero or multiple candidates are left for the user. Optional body `line_ids` scopes it to a selection (bulk-match the ticked rows); omit to match all.
Unmatch All Lines
Clear the confirmed matches across the import (bulk unmatch). Only lines whose `match_status` is `matched` are affected; the linked SKU documents are untouched — only the bank-line links are removed. Optional body `line_ids` scopes it to a selection (unmatch the ticked rows); omit to unmatch every matched line.
Push Matched Lines to Xero
Push the matched line(s)' SKU document(s) to the accounting provider (Xero) via the shared ledger sync pipeline (the same dedup-aware path the rest of the app posts through). Only lines whose `match_status` is `matched` are pushed; each matched entry that is already linked-current in Xero is skipped, others are enqueued as a Create/Update sync operation. Optional body `line_ids` scopes it to a selection (a single line, or a bulk pick); omit to push every matched line.
Enable Sync on Matched Lines
Force-enable sync on the matched line(s)' document(s) whose sync is currently **disabled-but-enableable** — a settings opt-out (e.g. this entry type isn't synced, or it's before the sync start date) or a per-entry `force_disabled` override. Writes a `force_enabled` override on each such entry (via the same path as the per-entry override endpoint) so a subsequent **Push to Xero** actually reaches the provider instead of silently no-opping.
Resume Provider GL Journals Sync
Manually **resume a budget-paused** provider GL journals sync from its stored cursor, instead of waiting for the hourly auto-resume sweep (`ledger:reconciliation:resume-provider-sync`).
List Close Periods
Every month in the closable range (most recent first) with lifecycle status (open, in_review, closing, closed), checklist progress, blocking-failure and warning counts, and close sign-off metadata. Months are materialized lazily — a month that has never been interacted with reports status open with is_started false.
Get Close Period
Full detail for one period (period key format YYYY-MM): the close checklist with each item's latest evaluation, lock state (active lock date plus per-connection provider lock dates), report snapshot index, and close/reopen eligibility flags. For a month that has not been started, the checklist is a live read-only preview.
Start Period Close
Begin the close for a month: materializes the period, seeds its checklist (automated checks, built-in review items, and any custom items configured in accounting settings), and evaluates every automated check. Returns the full period detail.
Refresh Close Checklist
Re-evaluate every automated check on a started, non-closed period and append any newly configured custom checklist items. Completed manual items are never reset.
Complete Checklist Item
Check a manual checklist item off, recording who completed it and when. Automated checks cannot be checked off — re-evaluate them with Refresh Close Checklist instead.
Uncomplete Checklist Item
Undo a manual checklist item's completion on a non-closed period.
Close Period
Close and lock the period. Only the earliest non-closed month can close (periods close strictly in order). Every failing blocking check refuses the close; every warning-level failure and incomplete manual item must be listed in acknowledged_keys. The close runs as a background job that re-validates all gates, snapshots the report pack, inserts the period lock (new entries dated on or before the period end are refused; corrections are redated to the first open day), and records the sign-off. Returns the background job id.
Reopen Period
Reopen the most recently closed period (a reason is required and audit-logged). Removes the period's own lock, flags its report snapshots stale, and returns the period to review. When the accounting provider still has the month locked, residual_provider_locks lists those connections — entries stay locked provider-side until the books are reopened there too.
Get Close Report
One report of the month-end pack. Report keys: income_statement, balance_sheet, trial_balance, inventory_valuation, cogs, ginr, reconciliation_summary. On a closed period this returns the snapshot frozen at close time (source: snapshot); otherwise a live build flagged preliminary. Payloads carry statement-level rows plus a totals map of headline figures.
Verify Closed Period
Drift check for a closed period: recomputes each snapshotted report live and diffs the totals against the snapshot with a $0.01 tolerance. Any difference means the closed month's numbers moved after close (for example via data repairs) and lists the changed figures. The reconciliation summary is informational — it legitimately changes as reconciliation work continues and never trips the period-level verdict.
Capture Provider Trial Balances
Capture the accounting provider's trial balance as of the period end (period key format YYYY-MM), for every connected accounting integration. This is the one metered provider pull behind the Trial Balance Reconciliation report and the trial_balance_reconciled close check — both read only the cached snapshot this produces. Synchronous: one trial-balance call per connection; connections whose provider exposes no trial-balance report are skipped, not failed. Re-running re-captures in place so provider-side edits are picked up.
Sync Breakdown (why not synced)
The why-not-synced breakdown. For the period (key format YYYY-MM), returns every journal entry that has NOT reached the accounting provider, grouped by reason (display status), with an entry count, a dollar total, and a per-reason deep-link filter that lands the entries list on exactly those rows. The synced total rides along as context (how much DID sync). Read-only and entirely local — no provider call.
Export Close Pack
Generate the month-end close pack workbook (XLSX) as a background job: a cover sheet with the period's status, sign-off and checklist outcomes, plus one sheet per report of the pack. Closed periods export their frozen snapshots; open periods export live figures flagged PRELIMINARY. The finished file is delivered as a download from the job result.
List Lock Date Exceptions
Paginated journal entries displaced out of a locked accounting period (booked on the first open day, source date on `original_effective_at`).
Get Lock Date Exceptions Summary
Displaced-entry counts and net document-total impact grouped by entry type, plus grand totals. Honours the same original_from/original_to + type/source_type filters as the list.