VRPlatformVRPlatform
Build a Product UI

Accounting Administration

Build tax, opening-balance, and books-closing workflows

Mental Model

Administrative accounting writes configure tax, establish opening positions, or move the first open accounting date. Their consequences span more data than a normal row edit.

Accounts and Assignments

An account's category and classification determine its reporting position. Use the category IDs returned by GET /accounts/categories; matching display names do not establish category identity. A ledger account can move between categories in the same classification. This updates report grouping without rewriting its historical journal entries. When a team moves between regions, category matching ignores surrounding name whitespace and requires the same classification. Existing clean category names take precedence over whitespace aliases in the destination. System assignments determine its operational role, such as accounts receivable, deferred revenue, accounts payable, payout distribution, opening balance, or the due-to/from-trust bridge.

Assignments are unique within a team: one assignment cannot be active on two accounts. Create or update the account with the complete intended assignments array and handle a duplicate assignment as a configuration conflict. The payout_bankAccount assignment requires a bank account. The openingBalance assignment requires an active ledger or bank account whose classification is asset or liability, or whose category is equity.

An account with type=nonPosting is a reference account. Use it for external values that formulas or source detail need to retain without affecting the live general ledger. Do not use a reference account for cash, a system balancing role, or an opening position that must contribute to reports.

Resources and Lifecycle

Tax rates remain renderable on historical fees and transactions after they become inactive. Opening balances are separate resources, one per accounting purpose. booksClosedAt is the first open date; earlier affected journal dates are closed.

Explicit opening balances carry the position immediately before the first live GL date and remain active even though their journal date precedes statementStartAt. Changing statementStartAt reclassifies other unlocked journals around that boundary while preserving active opening balances. Reapplying the current date does not reclassify other journals; it only restores explicit opening balances made inactive solely by the GL boundary. The first owner statement then includes those listing positions in Balance start.

Trust Opening Positions

Trust go-live is not one plug amount. Record each known position through its dedicated operation so attribution survives into reconciliation and statements:

PositionAPI workflow
Listing or owner balanceListing opening balance
Guest or channel amount still owedAccounts receivable
Cash received for future recognitionAdvance deposits and deferred revenue
Amount owed to owners or managerAccounts payable by party
Tax already collected and still payableTax payable
Remaining trial-balance account positionOpening trial balance

The API names the reservation-level advance-deposit resource advanced-deposits. Each non-zero row must identify a reservation and have listing context. Use the synced payment/refund candidates response as discovery, then submit only the position that exists at go-live. The mutation replaces the purpose-specific opening transaction and returns the selected account.

Do not force trust cash to zero with an unrelated journal. After recording the known asset and liability positions, inspect Trust Reconciliation. A remaining surplus or deficit is the difference between recorded trust assets and trust liabilities. Investigate missing cash, guest advances, owner balances, tax, and payables before changing the account assigned as openingBalance.

Read Model

Render current tax-rate status and usage, dedicated opening-balance responses, team booksClosedAt, operation issues, and affected-resource results. Each opening-balance type has its own operation and schema; use them as-is. Advanced-deposit updates preserve the submitted opening-balance account and return that account in the mutation response and subsequent reads. Concurrent writes to the same existing opening balance are serialized before its transaction lines are replaced. Ownership relinking uses the same journal lock order, so concurrent saves do not deadlock; if both succeed, the later opening-balance write wins. If a competing replacement cannot acquire the lock in time, the API returns 409 CONFLICT; re-read the current balance before offering a retry.

The trial-balance classification filter accepts a case-insensitive, comma-separated list of asset, liability, revenue, and expense. Unsupported values return HTTP 400 before the report query runs. The JSON and CSV endpoints use the same filter contract.

Decision Table

Tax and Opening Balances

WorkflowClient rule
Tax-rate removalArchive only when returned contract supports it
Opening trial balanceUse dedicated account and balancing contract
A/P, A/R, deferred, deposits, taxUse each dedicated operation
Reference-only source amountUse type=nonPosting; exclude from live totals
Trust surplus or deficitReconcile known assets and liabilities; do not post a plug
Listing or bank initializationKeep its own identity and posting rules
Clear listing balanceZero may cross statement period; attachments and books still block
Listing balance after historical importBefore stored live activity; normal locks apply
Close booksSet boundary; explain first-open-date semantics; confirm
Reopen all datesSend booksClosedAt=null through normal operation
x-ignore-books-closed-atNever expose as a normal product control

Editability

Tax usage and historical journals can prevent deletion. Opening balances and books closing must respect statement attachments and other locks even when the x-ignore-books-closed-at header is available for controlled repair workflows.

An existing listing opening balance can be corrected to exactly zero when only the listing's statement-period boundary locks it. A non-zero edit can cross the boundary only when every stored statement for the listing is an imported snapshot ending at or before statementStartAt. Books closing, journal entries directly attached to a statement, and stored live statements still reject the write.

Books Closing

A business entity can have several journal dates. Compare the affected posting date, not just its top-level transaction or reservation date.

booksClosedAt is inclusive as the first open date. For example, booksClosedAt=2026-07-01 locks journal dates through 2026-06-30 while leaving 2026-07-01 open. This differs from a user-facing "closed through" date. Store and send the first open date exactly.

After a successful books-closing update, the new boundary applies immediately to subsequent writes and previews. Clients do not need to refresh their auth session before validating affected resources.

Closing also coordinates with journal writes already in progress. A write admitted before closing completes first; a write admitted after closing must satisfy the new boundary. A previously fetched editable state does not authorize a later write into the closed period.

Preview and Preflight

Tax-rate and opening-balance mutations expose dry run where the generated OpenAPI declares the dryRun parameter. The books-closing update intentionally has neither generic dry run nor a dedicated preview. Explain the boundary and require confirmation, then apply and handle the normal response.

Mutation Recipe

  1. Load current configuration and affected operation response.
  2. Pick the administrative workflow built for the change.
  3. Validate its exact generated schema and balancing requirements.
  4. Dry-run when the operation declares support.
  5. Explain broad journal or lock consequences.
  6. Confirm and apply.
  7. Re-read the team and affected resources.
  8. Surface any resulting issues or queued recalculation state.

Failure and Recovery

For locked historical input, show the statement or books boundary and keep the original payload. Do not recommend the x-ignore-books-closed-at header. For an unbalanced import, repair the opening workflow itself rather than posting an unrelated transaction to hide the difference. For a concurrent opening- balance conflict, re-read the dedicated resource and let the user confirm a new replacement.

Common Recipes

Close a month

Select the first date that should remain open, explain that every earlier affected posting date closes, confirm without dry run, apply, and re-read the team setting.

PUT /team/books-closing
Content-Type: application/json

{
  "booksClosedAt": "2026-07-01"
}

Import an opening trial balance

Load the current trial-balance response, skip rows where importable=false, map Operating balances for the remaining rows, dry-run the complete import, then apply through the dedicated operation. See Opening Trial Balance for the source ownership rules.

PUT /general-ledger/opening-trial-balance?dryRun=true
Content-Type: application/json

{
  "rows": [
    {
      "accountId": "22222222-2222-4222-8222-222222222222",
      "operatingCentTotal": 250000
    },
    {
      "accountId": "33333333-3333-4333-8333-333333333333",
      "operatingCentTotal": -40000
    }
  ]
}

Filter the current trial balance

Request one or more supported account classifications as a CSV query value.

GET /reports/trial-balance?classification=asset,liability

Establish reservation advance deposits

Read synced candidates, preserve reservation and listing identity, dry-run the replacement, and then apply it. Emptying the reservation array removes the purpose-specific opening transaction.

GET /general-ledger/opening-balances/advanced-deposits/synced-payments-refunds
PUT /general-ledger/opening-balances/advanced-deposits?dryRun=true
Content-Type: application/json

{
  "accountId": "22222222-2222-4222-8222-222222222222",
  "currency": "USD",
  "reservations": [
    {
      "reservationId": "33333333-3333-4333-8333-333333333333",
      "listingId": "44444444-4444-4444-8444-444444444444",
      "amount": 125000
    }
  ]
}

API Reference

On this page