VRPlatformVRPlatform
Build a Product UI

Transactions

Build transaction forms with valid types, party, payment, and locks

Last Updated: 2026-09-05

Version: 1.5

Mental Model

Transactions record financial activity that is not represented solely by a reservation. Their lines provide the account, amount, attribution, and related resource context from which VRPlatform creates balanced journal entries.

party means accounting attribution. It does not identify who created, paid, or approved a transaction.

Resources and Lifecycle

Create transaction (POST /transactions) accepts four public types:

TypePurposePrimary shape
depositMoney received or matched to guest/channel activityOne or more lines
expenseCost or billContact, optional payment, markup, attachments
transferMovement between internal banksOne line plus root source account
payoutOwner or manager distributionOne line, optional statement link

Transactions can be active or inactive and remain in history after archive. Recurring transaction templates create scheduled expenses and have their own active state, recurrence, preview, and run lifecycle. A template is not a realized transaction and has no journal entries of its own; related listing, account, and tax-rate changes refresh only generated transactions.

Read Model

Render the returned transaction, payment, latest providerPayment, line attribution, reconciled bank records, attachments, calculated totals, and granular locks. Use resolved listing context from a linked reservation rather than preserving a competing manual listing selection.

Use integer cents. Transaction totals are the sum of line amounts. The server normalizes transfer source direction, and a transfer without a description gets a generated one ("Transfer from ... to ..."); every other type requires an explicit description.

Recurring-template lists filter the complete result set before pagination. Use contactId for the root vendor/contact, accountId for the root payment account or any line account, listingIds with listingScope=exact|withChildren, frequency, dateRange with isDateRangeEndInclusive, and signed-cent amount comparisons or ranges. Omitted listing scope includes direct child listings. Date ranges match schedule active-period overlap: endDate is inclusive, count ends the period on its final recurrence, and a schedule without either boundary remains open-ended. Search matches the template description and reference, root and line account/category names, and line listing names and references. All supplied filters combine with AND semantics.

Owner Payout History

Use List contact payouts (GET /contacts/{contactId}/payouts) for an Owner Portal payment history. The endpoint is paginated across statement periods and returns active payout transactions newest first. A payout appears as soon as its latest provider payment is requested; no provider status is hidden.

Owners may request only contact IDs in their server-derived owner access. A different contact returns an opaque not-found response. Property managers and owner-preview sessions may request a selected contact in the current team.

Each row contains only the payout identity, date, description, signed cent amount, currency, optional statement reference, and latest owner-facing provider payment. The provider payment includes the masked destination account suffix captured for that attempt and the requested arrival date when available. Later ACH changes do not alter an earlier payout's destination. A missing historical snapshot returns destinationAccountLast4: null.

A statement reference includes its month and listing: { id, name }, so payment history can identify the property without exposing journal lines. A payout linked to more than one statement returns ownerStatement: null rather than selecting a potentially misleading listing. A recorded payout without provider dispatch returns providerPayment: null. Full bank details, reconciliation, journals, connection IDs, provider references, sync IDs, and attempt metadata are intentionally absent.

Returned Owner Payouts

Record a full or partial external owner-payout return with POST /transactions/{id}/payment-returns. The original payout stays active and keeps its payment history. The operation creates a positive compensating payout through the normal transaction and journal path and links it with returnedFromTransactionId.

The operation requires transactions:read, transactions:payouts:write, and transactions:mark-paid because it reads the original payout and records its compensation as paid.

The body requires a stable idempotencyKey, positive cent amount, timezone-qualified effectiveAt, and explicit open-period postingDate. The API never moves a return out of a closed period. An optional incoming bankRecordId is reconciled to the compensation atomically; it must belong to the original bank account and exactly match the returned amount.

Version one accepts an active, paid, one-line owner payout linked to an owner statement. It rejects manager payouts, inactive payouts, return compensations, ambiguous allocations, and cumulative amounts above the original payout. The effective time cannot precede the original payment date. Reusing an idempotency key with the same input returns the existing compensation; changing the input returns a conflict with the differing field names.

After the first compensation, the original and every compensation are accounting facts: they cannot be archived, deleted, or financially edited. Description-only and bank-record reconciliation changes remain available.

Outgoing payout reads expose returnSummary with none, partial, or full status, cumulative and remaining amounts, active compensation IDs, and the direct replacement ID. A compensation exposes returnedFromTransactionId and returnEffectiveAt; it does not have its own return summary. A replacement continues to use recreatedFromTransactionId and may not exceed the amount already returned.

Ramp returned remains provider evidence, not an accounting write. The current stored Ramp evidence has no distinct returned amount or effective timestamp, so VRPlatform does not manufacture either value or automatically post compensation. Use the provider-neutral operation with verified evidence. Archiving a returned Ramp bill leaves payout accounting active. Failed and canceled recovery continues to archive payout accounting.

Transaction CSV

When GET /transactions/csv or POST /transactions/csv is filtered with type=payout, each exported row includes the payout line's listing name in a Listing column. A payout without listing attribution leaves the value blank.

Decision Table

Party Defaults and Options

Transaction and contextDefaultValid public options
Deposit without listing or reservationmanagermanager
Deposit with listing or reservationownersBoth unless a forced rule below applies
Opening-balance depositContext defaultBoth
ExpensemanagerBoth unless a forced rule below applies
Strict internal transferParty-neutral bank postingsHide control
PayoutownersBoth

"Context default" means the same default as a normal deposit in that context: manager without listing or reservation context, owners with it.

owners participates in ownership allocation, owner balances, statements, and owner-side locks. manager records manager-side activity and normally avoids owner-statement period and inactive-ownership guards. The internal journal party none is not valid transaction-line input.

Recalculate visible options whenever type, account, system assignment, category, listing, reservation, or owner-statement context changes. A reservation matcher is not owner context until it resolves.

Forced Party Rules

  • Lodging or occupancy tax payable forces owners for deposits and expenses.
  • deposit_coHostPayout forces owners when the line has a listing or reservation. Without that owner context it defaults to manager.
  • Resolved system accounts-receivable and deferred-revenue accounts force owners.
  • deposit_reserve forces manager.
  • Merchant-fee, channel-fee, and VAT deposits default to manager; an explicit owners value needs listing or reservation context.

Custom accounts with similar categories do not automatically inherit a forced system-assignment rule.

Security Deposit Accounting

The PMS reservation financial line is the source of refundable guest-deposit principal. Map that line to Security Deposits Payable. It creates the guest receivable and equal liability independently of where the channel sends the cash.

For example, when Hostaway reports 2,000 USD rent plus a 500 USD refundable deposit but remits only 2,000 USD, the settlement Deposit clears 2,500 USD of reservation receivable, posts 2,000 USD to the bank, and posts 500 USD to Reserved Funds withheld by Merchant Processor. The settlement reconciles to the 2,000 USD BankRecord. The retained 500 USD is a processor receivable, not trust cash, until remitted.

deposit_reserve posts that processor receivable and forces the manager party. It is not the guest liability and does not prove that every processor reserve is security-deposit principal. Remittance moves the asset from the processor receivable to the trust bank without releasing the liability.

Refund or forfeit the deposit with open-period reservation adjustments. A full refund reverses the liability; a forfeiture reverses it and reclassifies the same value to the explicitly selected revenue account and owners or manager party. The cash refund or processor release remains a Transaction against the asset that moved. There is no beneficiary default, top-level security-deposit resource, or persisted lifecycle chain.

These rules cover a chargeback of refundable deposit principal.

Base Reservation Chargebacks

For a chargeback against rent or another base reservation charge, record and reconcile the cash withdrawal as a refund Transaction. Keep the disputed amount in accounts receivable while the outcome is unresolved. Record any processor chargeback fee separately as a bank-fee expense.

If the processor returns the cash, record the incoming cash against accounts receivable. Do not change the reservation.

If the chargeback is lost, use negative owner-party reservation adjustment entries on an explicit valid posting date for the amount being written off. The amount can cover part or all of the reservation. Choose the treatment that matches the team's accounting policy:

  • Keep the original rent and cleaning amounts and post a separate offset to a selected Chargeback expense or contra-revenue account. A team can map a custom chargeback line type to that account.
  • Reduce the original revenue categories with adjustments mapped to the affected rent, cleaning, or other accounts.

The Chargeback account and line mapping are optional team configuration. chargeback is not a reserved system line type. This example writes off 1,250 USD using the separate-offset treatment:

POST /reservations/{reservationId}/adjustments?dryRun=true
Content-Type: application/json

{
  "type": "chargeback",
  "amount": -125000,
  "party": "owners",
  "description": "Chargeback lost",
  "generalLedgerPostingAt": "2026-09-04"
}

The adjustment reduces the reservation financials and clears the corresponding receivable in the open period. Apply the same request without dryRun=true after reviewing the returned financials and posting date. It does not rewrite the original payout, published statement, or closed month.

Review the original management commission, cleaning fees, and other fees separately. Decide which remain earned and which must be reduced or reversed. A separate Chargeback offset leaves the original rent and cleaning fee bases intact. Adjusting those original accounts can produce fee corrections, but the result depends on each fee's formula, conditions, and locks. Check the fee calculation rules and preview the resulting financials; do not assume all original fees reverse.

For example, 1,000 USD rent plus 250 USD cleaning, less 200 USD commission and 250 USD cleaning fees to the manager, leaves 800 USD for the owner. An owner-borne 1,250 USD Chargeback offset leaves the owner at -450 USD. If both manager fees must also be refunded, corrections totaling 450 USD bring the owner to zero. If those fees remain earned, zero is not the intended result.

The owner-party adjustment assigns the loss to the owner. To assign some or all of it to the manager, configure a recurring fee whose formula reads the Chargeback account. Use owners on the fee debit and manager on the fee credit. A 100% rate transfers the full adjustment to the manager; a lower rate splits it. This fee allocates the chargeback loss; it does not reverse the original commissions or cleaning fees. The fee must accept negative Chargeback totals because a positive-only account condition excludes the adjustment. Preview the fee against a representative reservation before assigning its listing period.

This mapping and fee are team accounting policy. VRPlatform does not select the loss recipient or apply the adjustment while the dispute is unresolved. Owner-statement grouping and visibility are presentation choices. Hiding a chargeback line does not remove its effect on the owner's balance.

Type Constraints

  • Deposits do not accept line markup. Non-expense transactions, including opening-balance deposits, are always marked paid on the transaction date (the stored date when an update omits it); only expenses carry a separate payment state.
  • Expenses require contactId unless they are opening balances. They alone accept attachmentIds; their lines can carry markup and tax application.
  • Applied-amount tax is always additive (excluded). Send only appliedAmount.taxRateId; applied-amount tax has no public behavior setting. Markup tax remains separate and may specify included or excluded behavior.
  • Applied amount and markup tax postings remain balanced when the configured tax account forces the owner party; no account-category workaround is required.
  • Recurring expense templates require a root contactId. Template line party accepts only owners or manager; other values are rejected before the template is written.
  • Transfers require exactly one line, two different internal bank accounts, no statement link, and no markup. The root account is the source and line account is the destination.
  • Payouts require exactly one line. Statement linkage belongs on the line as ownerStatementId.

Editability

Transaction reads expose:

  • transaction.lock for root changes
  • transaction.payment.lock for expense payment changes
  • transaction.lines[].lock for individual line changes

An expense can contain both locked and unlocked lines. Keep independent line controls available, but preflight shared date, currency, type, status, listing, or payment changes because they can affect all postings.

Owner-side listing lines are rejected in an inactive ownership period. Manager-side lines can retain listing attribution when the operation permits it.

The recurring-expense scheduler skips an occurrence when an owner-side line falls inside an inactive ownership period. It creates no transaction for that date. Interactive transaction creates and recurring-template runs still return the inactive-listing rejection so the request can be corrected.

For deposits and expenses, a bank-record match is a field-level constraint. The aggregate transaction.lock.status remains locked and transaction.lock.reconciled.bankRecordIds identifies the reason. Do not use the aggregate status alone to disable the complete edit form.

Bank match is the only lockEditability
Transaction and line descriptionsEditable
Line account and system assignmentEditable
Line party (the form's bill-to attribution)Editable
Line listing and reservationEditable
Expense line markupEditable
Line amountLocked; unmatch first
Transaction dateLocked; unmatch first
Root bank or credit-card accountLocked; unmatch first
Add or remove linesLocked; unmatch first
Other transaction or line fieldsLocked unless another documented flow allows them

The rule applies to each touched line. A books-closed period, attached owner statement, or saved statement period still wins and returns its normal lock reason. Transfers are outside this field-level exception. Saving the editable fields preserves the bank-record link.

Changing a deposit line's reservation moves it between reservation and non-reservation items. A bank match alone does not block either direction, and an owner-statement lock on another line does not block the move. Preflight both the source and destination listing periods: the API rejects the move if it would pull an owner posting into or out of a saved statement period.

Preview and Preflight

Create, update transaction (PUT /transactions/{id}), remove, batch, update transaction line (PUT /transactions/{transactionId}/lines/{id}), and recurring-template create/update/remove/run support dry run when the generated OpenAPI declares the dryRun parameter. Transaction journal regeneration intentionally does not; it needs a preview of inserts, updates, deletes, skips, locks, and amount deltas.

Use Preview recurring transactions (POST /transactions/recurring-templates/preview) for occurrence dates and proposed transactions. A dry-run Run recurring template (POST /transactions/recurring-templates/{id}/run) returns provisional generated transactions and rolls them back.

Mutation Recipe

For a deposit line edit, send only lines_update to PUT /transactions/{id}. Unlisted source lines and their journal postings stay unchanged, including historical party attribution. The selected lines and shared bank postings are validated before success. Books-closed, statement-attachment, and source or destination P&L period locks still apply.

  1. Load eligible accounts, assignments, contacts, listings, reservations, and current granular locks.
  2. Choose type and apply its exact shape.
  3. Resolve owner context and recompute party default, forced value, and options.
  4. Validate linked resource ownership, currency, unique reference, and totals.
  5. Validate payment and reconciliation records.
  6. Request a schedule preview for recurring templates when relevant.
  7. Dry-run the exact complete payload.
  8. Render structured lock or rule failures and discard provisional IDs.
  9. Confirm and apply the same payload.
  10. Re-read transaction, payment, lines, attachments, and locks.

Failure and Recovery

Do not preserve an invalid previous party after a dependent field changes. A value that matched the old default can move to the new default. Valid explicit overrides remain in place, and a forced rule replaces anything that conflicts.

Linked accounts, contacts, listings, reservations, bank records, connections, and statements must belong to the selected team. Re-read after a concurrency failure. On a locked delete, offer archive only when structured context supports it. Background transaction journal refreshes preserve statement-attached rows. If the stored journal and complete desired journal are each balanced but applying only the unlocked changes would make the stored journal unbalanced, the refresh finishes as locked and leaves every journal row unchanged.

For POST /transactions/journal-entries, exactly one explicit ID completes before success. Multiple IDs or an omitted selection queue a transaction-journal-refresh operation. Follow the returned operation until it is terminal; an empty ID collection is invalid. If two requests delete the same transaction concurrently, one removes it and the other returns the standard NOT_FOUND response. Treat that response as an already-removed resource and re-read the collection.

uniqueRef identifies a transaction within its team. Every line uniqueRef must also be unique inside that transaction; omitted line references are assigned deterministic line1, line2, and later values. Avoid explicit references that collide with those generated values.

Creating or updating a transaction with a uniqueRef already used by another transaction returns a structured user error without changing either record.

Connection-scoped transaction batches resolve an existing row by explicit id, then uniqueRef, then sourceId. Keep both external references stable when they are available. Concurrent retries sharing either reference are serialized. A repeated transaction, repeated sourceId inside one batch, or duplicate line reference is returned as that item's structured issue rather than aborting the batch with a database constraint error.

Payments and Reconciliation

Expense payment state lives under payment. Linked bank-record totals must equal the transaction total. Transfer bank records must each match the transfer magnitude. A paid expense without a payment date uses transaction date.

Provider Payments

POST /transactions/{id}/pay requires transactions:mark-paid for both offline recording and ACH requests. Ramp execution additionally requires ach-payments:execute, including when called after saving the expense form or from a bulk payment action. Cancel uses POST /transactions/{id}/provider-payments/{providerPaymentId}/cancel and requires both permissions. These checks follow the team's authorization mode.

Creating or updating an expense, including a batch payment-field update, records accounting data only. A Ramp-linked account alone does not request ACH; execution requires the explicit provider: "ramp" payment call.

Choose the provider explicitly:

  • provider=null records an active expense as paid outside Ramp through the normal transaction update path. Books-closed, statement, and reconciliation locks still apply. Pass the selected GL bank account as fundingAccountId. The payment date defaults to today. After a terminal expense ACH attempt and permanent Ramp bill archive, this endpoint can record an offline payment, including after unarchiving the expense. It preserves the archived bill and payment history without scheduling another Ramp payment.
  • provider="ramp" requests ACH execution for an active vendor expense or an existing owner payout. Expenses dispatch transaction.updated to expensesToBills; payouts dispatch their linked ownerStatement.updated to transfersToBills. An expense becomes paid when the request is accepted, with an accounts-payable debit and cash credit on the selected account. A reconciliation effect follows each expense request. If dispatch fails, it resets the expense to Unpaid and refreshes the journal so the payment entries no longer remain active.

Ramp execution requires exactly one eligible matching Ramp flow, a recipient contact with paymentMethods.ach.status=ready, and a ready selected funding account identified by fundingAccountId. A matching flow with no status is unconfigured and eligible; the first otherwise-valid payment activates it. Dry runs and failed validation leave the status absent. An explicitly inactive flow remains unavailable, and multiple eligible connections return 409. The selected AccountConnection must resolve to one imported Ramp entity and source bank account; those exact provider IDs are pinned to the dispatch instead of choosing a default Ramp entity or account. A request returns syncId plus the latest provider attempt. Reusing an idempotencyKey returns the same request only when its transaction and funding account match; using it for a different payment returns 409.

The selected funding AccountConnection must link to a GL bank Account. Before the provider attempt is recorded, the API moves the expense or payout root accountId to that linked Account and refreshes its journal cash leg through the normal locked transaction path. A books-closed lock still rejects the change and no Ramp request is created.

Each outbound Ramp bill line uses the account assigned to that VRPlatform transaction line as its Ramp Category. Payout and payout-style transfer lines without an explicit account use the team's payout_distribution account, matching the journal engine. These accounts must exist in Ramp through the VRPlatform chart-of-accounts push flow. Before dispatch, the API requires a completed pushRampAccounts run that started after each used account was created or last changed. A full run covers the complete chart; an account event covers only that exact account. Missing evidence returns 422 with Ramp Category account is not ready and creates no provider-payment request. Run the Ramp chart-of-accounts flow and retry the payment.

Transaction reads expose the latest attempt as providerPayment with requested | processing | paid | failed | returned | canceled, provider status detail, payment reference and method, requested arrival target, masked destination account snapshot, scheduled and actual initiation timestamps, completion timestamp, Transfer API return evidence, Ramp bill archive timestamp, failure reason, and stable attempt ID. The requested arrival is the date submitted to Ramp; Ramp does not return a revised estimated-arrival field. Use GET /transactions/{id}/provider-payments for complete attempt history.

Transaction reads also expose providerPaymentActions. This server-owned matrix covers pay, cancel, cancelAndArchive, cancelAndRecreate, archive, archiveAndRecreate, and unarchive. Each action contains available plus a static disabled reason. Clients must still enforce their permission model, and every mutation revalidates state after the read. For expenses, Cancel deschedules the payment while preserving the Ramp bill. Archive permanently archives the bill. Failed requests that never created a Ramp bill can still be archived locally.

When the latest request failed or was canceled before its Ramp result was recorded, Cancel remains unavailable. Its disabled reason describes that terminal request instead of asking the client to wait for reconciliation. Read the action matrix for available Pay and Archive actions; earlier bill history does not make the latest request cancelable.

A payout can be submitted from VRPlatform only once. Ramp users may retry its failed payment in Ramp on the same bill; VRPlatform accepts the same bill moving from Failed back to Requested or Processing and retains the earlier attempt. To pay a payout again from VRPlatform after Failed or Canceled, archive it and create a replacement with recreatedFromTransactionId. After a Returned payout, record compensation and create a replacement against the still-active original payout.

An expense request that fails before a Ramp bill is created returns to Unpaid, and Pay becomes available again. A terminal Ramp payment failure also returns the expense to Unpaid. After Failed, Returned, or Canceled, Pay reschedules the same Ramp bill unless it has been permanently archived.

Once any Ramp ACH attempt is recorded, ordinary financial edits remain locked. The offline Pay endpoint permits a payment-only update after permanent bill archive as described above. It does not unlock other expense fields. An expense may change from Paid to Unpaid when Ramp reports Failed, Returned, or Canceled, and that terminal state permits its next same-bill Pay request. For this Unpaid reset, PUT /transactions/{id} accepts payment: {status: "unpaid"} with optional accountId: null and payment.date: null; it removes the payment posting subject to accounting locks. Status-only archive or unarchive is allowed only after durable Ramp bill archive or verified no-bill recovery evidence. DELETE /transactions/{id}/provider-payments/{providerPaymentId} is deprecated and always rejects; payment history cannot be unlinked.

Use POST /transactions/{id}/provider-payments/{providerPaymentId}/cancel for a Requested or Processing payment. The body must state archivePayout. Ramp, not initiatedAt, decides whether the payment and bill can still be archived. When archivePayout=false, the provider operation can proceed even if local accounting is locked. Expense cancellation uses expensesToBills, cancels only the payment, keeps the Ramp bill active, and returns the expense to Unpaid after Ramp confirms cancellation. Payout cancellation retains its existing behavior. The field retains its payout-oriented name for client compatibility.

Use POST /transactions/{id}/provider-payments/{providerPaymentId}/archive for a Failed, Returned, or Canceled payout or expense. Ramp bill archive completes before the requested local accounting archive. Returned payouts retain paid accounting and use a separate compensation for the returned cash. Failed and canceled transactions, and returned expenses, archive local accounting. When a failed attempt has only a durable request sync and no Ramp bill source or reference, the API records verified no-bill evidence and archives locally without calling Ramp. A provider-success/local-lock failure keeps the provider result and can resume the accounting archive later. Paid provider payments cannot use recovery archive.

Cancel and Archive return the standard queued operation acknowledgement. Poll GET /operations/{operationId} until the provider-payment-recovery operation becomes completed, failed, or canceled, then refresh the transaction and provider-payment history as the canonical outcome. A local no-bill archive can already be completed on the first poll. This status read requires operations:read, not integrations:read; sync details remain internal.

A replacement expense or failed or canceled payout must use an inactive source of the same type. A returned payout remains active and can be replaced only up to its active compensated amount. Existing replacement links remain unique. Unarchive does not call Ramp and cannot create another active chain member. Returned is assigned only from Ramp Transfer API RETURNED_BY_RDFI, not from undocumented Bill status aliases.

When a replacement payout omits uniqueRef, the API assigns a new PAY-YYYYMM-XXXXX reference from the replacement date. It never reuses the source payout reference. Ramp dispatch rejects a payout without a stored reference before provider work is queued, and transaction reads expose the Pay action as unavailable.

POST /transactions/{id}/pay supports dryRun=true; dry runs persist no sync or accounting change and dispatch no provider work. Provider recovery endpoints do not support dry run because a database-only preview cannot simulate Ramp.

Attachments

Create an expense attachment upload with POST /transactions/attachments and send filename, contentType, fileSize, and isOwnerAccessible. The response contains attachmentId, a 24-hour opaque uploadRef, its expiresAt, and an authenticated regional uploadUrl. PUT the original file to uploadUrl with the declared Content-Type, then send attachmentId in attachmentIds. The request must arrive with a Content-Length matching fileSize; browsers provide it when the original File is the request body. Files must be non-empty and at most 100,000,000 bytes. Upload is an external storage operation and is not part of mutation dry run.

The browser must pass the original File as the PUT body. Do not read it into an ArrayBuffer, construct a multipart form, decode uploadRef, or send a storage completion request. The staged attachment's 24-hour orphan window starts before storage begins. Failed uploads remain unavailable and are eligible for cleanup. Retrying the same completed upload returns success without overwriting the stored object. JSON media types are streamed unchanged. Deleting an attachment also invalidates retries through its still-live upload reference.

On update, omit attachmentIds to keep the set, send an empty array to remove all, or send the complete desired set. Owner visibility is stored per attachment. Only expenses accept attachments.

Recurring Templates

Templates need a contact, expense and line configuration, payment status, start and optional end, plus daily, weekly, monthly, or yearly recurrence. Inactive templates and dates that are not due cannot run.

Template lines preserve the applied-amount tax rate and markup configuration. Every generated expense receives those settings; applied-amount tax remains additive while markup retains its configured tax behavior.

Running a template queues its due occurrences. If the template is deleted before a queued occurrence starts, that stale occurrence completes without creating a transaction. A direct run request for an unknown template still returns NOT_FOUND.

Deleting a template waits for generated-transaction writes already in flight. Generated transactions that commit first remain as independent expenses with their template link detached; the template delete then completes normally.

Common Recipes

Owner expense

Select an expense account and listing, offer both parties unless an account rule narrows them, choose owners, set payment data if known, then dry-run the complete expense.

POST /transactions?dryRun=true
Content-Type: application/json

{
  "type": "expense",
  "date": "2026-07-18",
  "currency": "usd",
  "description": "Pool cleaning",
  "contactId": "33333333-3333-4333-8333-333333333333",
  "payment": {
    "status": "unpaid"
  },
  "lines": [
    {
      "description": "Pool cleaning",
      "amount": 12500,
      "accountId": "22222222-2222-4222-8222-222222222222",
      "listingId": "11111111-1111-4111-8111-111111111111",
      "party": "owners"
    }
  ]
}

Internal transfer

Select two different internal bank accounts, hide party and markup controls, enter one destination line, validate any bank-record magnitude, and preflight.

POST /transactions?dryRun=true
Content-Type: application/json

{
  "type": "transfer",
  "date": "2026-07-18",
  "currency": "usd",
  "accountId": "22222222-2222-4222-8222-222222222222",
  "lines": [
    {
      "description": "Move funds to operating account",
      "amount": 50000,
      "accountId": "66666666-6666-4666-8666-666666666666"
    }
  ]
}

Statement payout

Prefer the statement pay operation. The payouts it creates carry the statement reference on their lines, even when the payout journal date belongs to a later statement month.

POST /statements/pay?dryRun=true
Content-Type: application/json

{
  "date": "2026-08-08",
  "statements": [
    {
      "statementId": "55555555-5555-4555-8555-555555555555",
      "payouts": [
        {
          "contactId": "77777777-7777-4777-8777-777777777777",
          "provider": null,
          "bankAccountId": "66666666-6666-4666-8666-666666666666"
        }
      ]
    }
  ]
}

Webhook Reconciliation

transaction.changed is emitted only after the externally visible aggregate and required journal, payment, reconciliation, or attachment effects reach their final state. It covers create, material update, archive, and deletion; no-ops and journal-only changes that leave the public response unchanged do not emit. The body contains only resource ID, change type, version, and href—no type, description, amount, currency, contact, account, listing, bank, statement, or journal detail. Recurring templates are a separate resource and do not emit this event until they create or update a transaction.

API Reference

On this page