VRPlatformVRPlatform
Build a Product UI

Requests, Locks & Issues

Shared validation, editability, preview, preflight, and recovery rules

Use this sequence for every API-backed create or edit workflow.

Status and Lifecycle

Status is business behavior, not just a label. A transition can activate journals, attach statement entries, queue work, change visibility, or preserve historical identity. Use returned status, available actions, permissions, locks, and issues together; never derive every transition from status alone.

Validation and Errors

Use four validation layers:

  1. Mirror deterministic required, allowed, default, and forced-value rules.
  2. Render response-owned locks, issues, statuses, and permissions.
  3. Preflight the exact payload with dry run or a dedicated preview.
  4. Handle the final write, since state can still change after preflight.

Errors use structured fields:

{
  "code": "FORBIDDEN",
  "message": "Human-readable summary",
  "issues": [],
  "context": { "lockReason": "statement" }
}

Branch on code, issues, and context. Treat message as display text and never parse it. Preserve user input, map issue paths to the nearest field or section, and re-read after a conflict.

Lock failures use two different context shapes. Mutation locks return context.lockReason with the value statement (journal entries attached to an owner statement) or period (books closed or a published statement period). Delete locks instead return context.lockReasons[], a list of human-readable blockers, together with context.suggestedOnLocked: "archive" when retrying with onLocked=archive would succeed. Both shapes are part of the API error contract.

Successful reads can also include issues[]. Each item carries a literal code, a severity of error or warning, a code-specific context object, and sometimes a display message:

{
  "code": "previousPeriodJournalEntries",
  "severity": "warning",
  "context": { "affected": 3 },
  "message": "3 journal entries belong to a previous period"
}

Use issue code, severity, and context for behavior. The message can change without changing the condition. All codes are listed in the issue catalog.

Editability

Lock objects always carry a top-level status of locked or unlocked, plus nested reason fields when locked:

{
  "lock": {
    "status": "locked",
    "statementPeriod": {
      "hits": [
        {
          "listingId": "9f695b33-e192-4373-be61-1f4a0b525d0b",
          "txnAt": "2025-12-14",
          "openFrom": "2026-01-01"
        }
      ]
    }
  }
}

Accounting locks can expose:

  • booksClosed.date, the first open accounting date
  • statementPeriod.hits, owner-side dates closed by persisted statements
  • ownerStatement.ids, entries attached to statements
  • reconciled.bankRecordIds, fields constrained by reconciliation

Use the narrowest returned lock. A transaction can have independent root, payment, and line locks. A reservation adjustment may stay editable through an open posting date, and a statement row can be limited to description edits.

Shared fields such as date, type, currency, listing, ownership, or status can affect several postings and still require whole-form preflight.

Some delete operations offer onLocked=archive. Archive preserves locked history and changes only the open portion. It is an explicit outcome, not a bypass for a failed delete.

See Locking and historical integrity for the accounting model.

Preview and Preflight

Use a dedicated /preview operation for calculated or consequence-oriented output that differs from the normal mutation response. Use dryRun=true only when the generated operation declares that query parameter.

Dry run executes normal synchronous validation and database work, maps the normal response, then rolls back. Returned IDs are provisional. It does not reserve state or dispatch audit effects, queues, webhooks, email, storage, or third-party work.

Generic dry run is not valid for uploads, OAuth, provider connections, syncs, invitations, email, or other irreversible effects. Books closing and journal regeneration also need more explicit consequence handling.

The full guarantee is in Dry Run Mode.

Mutation Recipe

  1. Load the entity and referenced accounts, contacts, listings, and periods.
  2. Apply deterministic rules locally.
  3. Disable controls from returned locks, issues, permissions, and status.
  4. Recalculate dependent options whenever context changes.
  5. Request dedicated preview output when needed.
  6. Dry-run the exact complete payload when the operation declares dryRun.
  7. Render structured failures and discard provisional identifiers.
  8. Ask for confirmation.
  9. Submit the same payload without dry run.
  10. Re-read and handle final-write conflicts.

External Side Effects

Treat accepted asynchronous work as pending until its returned state confirms completion. Store stable external references, poll or subscribe as documented, and do not assume database rollback can undo provider actions.

List Views

List endpoints share one convention set for pagination and filter parameters. Resources use either page/limit responses or opaque cursors; check the generated operation to see which one applies. Follow pagination and filtering instead of inventing per-endpoint paging; the same rules apply to every collection read in this lifecycle.

On this page