VRPlatformVRPlatform

Changelog

Current public contract changes and migration links

Unreleased

  • Reservation, fee, and statement journalStatus includes optional reason: journalLocked when all outstanding changes were skipped because of accounting locks. A later calculation timestamp does not clear unapplied work.

  • Fees and statements with no known journal changes now report current instead of a false stale status. Actual pending work and locked-history failures remain visible.

  • Exhausted database socket connection attempts return 503 SERVICE_UNAVAILABLE instead of a generic 500. Writes still require outcome reconciliation before retrying.

  • Transaction reads explain why Cancel is unavailable for a failed or canceled ACH request. These terminal requests no longer show a waiting-for-reconciliation reason. Available payment and archive actions are unchanged.

  • MCP /directory supports reviewed accounting reads and confirmed record creation, updates, and deletion, including payment bookkeeping and owner statement preparation. It rejects provider payment execution, credential changes, unreviewed routes, and input switches. The custom / endpoint retains full API access. See MCP setup.

  • Path, query, and body validation failures use the documented public error envelope. Pagination rejects fractional and unsafe numeric values before SQL.

  • Journal refreshes detect changed sources and postings before applying their changes. Book closing coordinates with writes already in progress.

  • Bank-balance recalculation corrects balances before the first checkpoint and processes large update sets in bounded, atomic batches. Existing stored balances change when recalculated; this release does not perform a backfill.

  • The TypeScript client now attempts writes once, including when attempts is greater than one. Automatic retries remain available for GET, HEAD, and OPTIONS; there is no write-retry opt-in. Reconcile writes with an uncertain outcome before sending another request. Custom response assertions still run on writes unless explicitly disabled with assertOnLastAttempt: false.

  • Both throwIfError forms preserve public error codes and recovery fields. Malformed or missing error envelopes throw ApiClientResponseError with the HTTP status. Error context is typed as unknown and requires narrowing. See Errors & Retries.

2026-09-05

  • Offline POST /transactions/{id}/pay supports expenses whose terminal Ramp bills were permanently archived, including after expense unarchive. It retains payment history and accounting locks. Terminal Unpaid resets also accept the UI's explicit null payment date and bank account.
  • Deposit updates containing only lines_update preserve unrelated source lines and journal postings. Invoice matching no longer fails because an untouched legacy fee line would otherwise change party during a full deposit refresh. Accounting locks still protect the selected lines and their resulting postings.

2026-09-04

  • PUT /syncs/{id} accepts queued, started, completed, or failed as status. Cancellation requires POST /syncs/{id}/cancel.
  • Manual POST /transactions/{id}/pay now reads its selected GL bank account from fundingAccountId, matching the existing client payload. The temporary accountId field is removed.
  • Failed Ramp expense dispatches automatically return the expense to Unpaid and refresh its journal payment entries. The failed attempt remains in provider history and Pay becomes available for retry.
  • Ramp expense Cancel now holds the existing bill, deschedules its payment, and returns the expense to Unpaid. A later Pay releases and reschedules the same bill. Archive remains the separate permanent bill action.
  • Owner CSV imports now create or update standalone owners when a row has no listing. Company Type can determine individual or company kind, and a source Type column cannot change the contact type from owner.
  • CSV resource metadata now publishes the versioned detection aliases, value maps, mapping and row requirements, fixed values, and import effects used by generic processors. Expense and deposit detection infers date format and leaves ambiguous slash dates for explicit mapping.

2026-09-03

  • Public audit events now include event-time actor organization, attemptedEntityChanges, finite lock evidence, supported recovery actions, and the embedded source. Blocked reservation events show only reviewed attempted fields; committed changes remain separate.
  • Audit collections add inclusive occurredFrom and occurredTo filters. Contact create, update, archive, restore, delete, batch, and import activity now uses the reviewed contact field catalog. Owner-statement attachment evidence stays in the statement event that caused it.
  • Selected-team audit reads now use audit:read. Team Admin and the additive Audit Log bundle grant it; Standard Member does not. Signed-in partner members can use the regional partner collection within their stored all, assigned, and direct team reach.
  • Added subscribable audit.event.changed invalidations for each new public event revision. The payload identifies the event, revision, and current detail endpoint and follows existing self and managed-team subscription scope.
  • Embedded sessions can receive embed:banking:v1 for the three-step Plaid connect workflow. PMS reservation and contact imports use their Connection as sync actor, identical reservation imports no longer emit activity, and request IDs are no longer derived from session or correlation headers.
  • GET /api-tokens/permission-bundles now exposes every assignable API-token bundle with its stable name, display title and description, and expanded permissions. Token-management clients no longer need a hardcoded catalog.
  • Audit action, effect, mutation, and timeline reads now retain the stored actor name and email after the user is deleted. The actor ID was already retained.
  • Partner API keys can now queue deletion of a directly managed team through DELETE /team?confirm=true. Restricted keys require partner:team-deletion:v1; partner deletion cannot use force=true, and the returned operation remains readable from the partner team after deletion.
  • Failed POST /teams requests no longer leave discoverable global-only teams. Slugs are checked in the global registry and target region, incomplete teams remain hidden, and an interrupted create can be retried safely.
  • Failed CSV imports now return csvParsingFailed for file and header errors or csvImportRecordFailed for invalid data rows instead of the generic operationFailed result.
  • POST /teams now rejects a Hostaway-managed property-manager team unless dataRegion is hostaway. Operating country can still determine currency and tax defaults, but it cannot place a Hostaway client in another region.
  • Expense Pay requests now post accounts payable against the selected Ramp funding account before dispatch. Manual Mark as paid accepts the selected GL bank accountId and uses it for the payment journal.
  • Canceled Ramp expense payments keep their existing bill, return the expense to Unpaid after confirmation, and allow another payment attempt on that bill. Payout retry behavior is unchanged.
  • ACH setup rejects routing numbers with an invalid ABA checksum. Failed setup reads preserve Ramp's bank-detail rejection instead of replacing it with an unrelated Vendor-owner instruction.
  • Missing or stale Ramp funding selections return the actionable validation message Select an available Ramp funding account and are not reported as unexpected server failures.
  • Journal-relevant recurring fee updates now return one reservation-journal-refresh operation. Reservation, recurring fee, and owner statement reads expose journalStatus plus the same operation while recalculation is in progress. Operation detail accepts typed act_ and op_ identifiers, and existing UUID operation identifiers remain valid. GET /operations accepts initiatedBy=me, and operation responses identify a user or system initiator. Authenticated owners can poll embedded journal refresh operations that affect reservations in their owner access. Statement finalization, payout preview, and payout reject non-current journal scopes with JOURNAL_RECALCULATION_PENDING after the tenant guard is enabled.
  • VRI historical statement import now consolidates same-date source statements when re-listed properties map to one target listing. All derived balances and eligible lines are retained in the single target statement.
  • VRI historical listing previews now separate statements owned by a listing from statements that only reference one of its reservations. Use importedStatementCount for owned statements and referencedStatementCount for all mapping references.

2026-09-02

  • Historical statement imports now classify line items that reference the team's payout_distribution account as signed transfers. Statement details, summaries, and stored financials therefore agree on payouts and expenses.
  • VRI-to-VRT preview preparation and activation now emit one durable API-owned lifecycle event per pair. The authoritative migration observer owns the verified event through the same shared tracking contract and transport.
  • GET /booking-channels now accepts either integrations:read or recurring-fees:read. Operations and recurring-fee API keys can load fee-referenced channel data without receiving broader integration access.
  • Breaking before permission launch: Operations now receives calendar:read without calendar:write or calendar:delete. Operations users can view calendar blocks, while create, edit, and delete controls and API mutations are denied.
  • Read Only team members can now resolve the selected team before opening connection details, Team General, Owner Portal settings, Owner Statements settings, and Opening Balances. The role gains no write permission.
  • Owner CSV resource metadata now orders identity and contact fields before reference and listing fields. First name, last name, company name, and company type are marked required; owner kind and listing are optional.

2026-09-01

  • App connect and reconnect requests that exhaust the 25-second provider wait now return 504 GATEWAY_TIMEOUT with the connection-specific static message. The provider run may continue after the request stops waiting. The export-renderer message remains limited to export tasks.
  • Reservation adjustment creates without an explicit posting date now move a locked natural date to the first open period by default. Pass onLocked=error to retain rejection. Reservation detail now marks inactive adjustment entries as statement-period locked when deletion would rewrite protected history.
  • Replacement payouts now receive their own PAY-YYYYMM-XXXXX reference when the create request omits uniqueRef. Ramp dispatch rejects payouts without a stored reference before provider work is queued, and transaction reads report Pay as unavailable.
  • Authenticated owners can now read an ACH verification operation when its primary contact target is in their server-derived owner access. Other contacts and operation types return an opaque not-found response.
  • Contact payout providerPayment now includes the masked destination account suffix captured for that attempt and its requested arrival date. Historical attempts remain unchanged after ACH account updates; legacy attempts without a reliable snapshot return destinationAccountLast4: null.

2026-08-31

  • Added POST /transactions/{id}/payment-returns for full and partial external owner-payout returns. It creates a linked compensation, supports dry run and exact BankRecord reconciliation, and leaves the original payout active.
  • Transaction reads now expose return links, effective time, cumulative return status, remaining amount, compensation IDs, and direct replacement ID.
  • Added atomic POST /bank-records/actions/batch for dry-run or apply of unmatch, exclude, and reactivate across at most 500 distinct records.
  • Exact-file CSV confirmation now blocks completed duplicate imports with csvExactFileDuplicate unless the caller sends the explicit intentional-reimport override.
  • Ramp provider-payment Archive now leaves Returned payout accounting active. Failed and Canceled payments keep the existing accounting archive behavior.
  • The banking guides now explain how published ledger templates can provide ordinary Bank Fees and Interest Income accounts. A team can use a BankRule as its repeatable interest mapping; there is no global beneficiary default.
  • The banking and transaction guides now document the existing channel-held security-deposit flow, open-period release and forfeiture adjustments, and the existing base-reservation refund and manual-adjustment flow. They also reserve exclusion for non-cash rows and describe the existing transfer behavior: one Transaction with one bank-record match on each bank-account side.
  • Breaking before release: bank-record reads no longer accept includeMatchingTransactions or return matchingTransactions[]. Request exact proposals with includeMatchingTransaction=true, set the optional matchingTransactionDaysOffset, and read every proposal from matchingTransactionSets[].
  • Projection-backed statement and owner-summary reads now return a freshly computed result when only the derived-cache write exhausts a database connection. Stale projections remain unusable, the persistence failure stays visible in monitoring, and other database failures retain their existing response behavior.

2026-08-30

  • POST /connections/{id}/connect now derives the app from the existing team-owned connection when appId and signed callback state are omitted. Unscoped POST /connections/connect requests still require one of them.
  • High-volume team deletion now commits bounded table and journal batches and checkpoints each regional cleanup group. Retries resume from the last committed group, while the internal job status exposes that progress. The deletion task and stale-job lease now support a 12-hour safety window.

2026-08-29

  • Account deletion now recognizes BankRule selectors and posting lines plus recurring-fee formula inputs as dependencies. BankRule and inactive-fee references can use onLocked=archive; every account used by an active fee remains protected from both deletion and archive.
  • Reservation journal refreshes now propagate database deadlocks and lock timeouts to their transaction owner so a failed transaction is not treated as a successful partial refresh.
  • Ramp ACH setup now returns RAMP_VENDOR_OWNER_REQUIRED with the connection setting destination when configuration is missing. Existing ownerless Ramp vendors return manual remediation in their failed ACH state. Failed payment requests that never created a Ramp bill archive accounting locally, while an existing payout is booked to the selected Ramp funding account before its first provider attempt.
  • Breaking before release: public HTTP and batch errors now use one finite uppercase code registry. Regional misroutes return an exact MISDIRECTED_REQUEST contract, migration freezes expose teamId, and unknown failures use static messages.
  • Breaking before release: connection prompts remain HTTP 200 with the finite lower-camel-case codes extensionRequired, formRequired, linkRequired, and oauthRequired. Provider failures now return 502 BAD_GATEWAY; the 25-second awaited limit returns 504 GATEWAY_TIMEOUT. The former HTTP 200 error response is removed.
  • Operation, CSV-import, and team issue schemas now publish finite code sets. Batch item repository aliases are mapped to their public HTTP equivalents.
  • Breaking before release: CSV preview and import failures now use typed camel-case recovery codes. The generic csvImportFailed and operation_failed values are replaced by phase-specific codes and operationFailed. Row issue codes are now a closed CSV-owned enum; resource validation failures retain their original issue in structured details. The API validates every reviewed plan chunk before resource writes and exposes import outcomes only after their manifest is finalized.
  • Shared full-date range filters now normalize the documented legacy comma separator before route execution. Account reconciliation summaries therefore accept both start...end and start,end without passing a combined value to database date serialization.
  • Re-inviting a team member now returns 409 CONFLICT if the membership changes during invitation preparation. Same-role re-invites continue to preserve the existing identity and explicit permission bundles.

2026-08-28

  • Breaking before release: the forwarding gateway now requires POST /teams General Ledger requests to provide an explicit dataRegion of us, eu, ap, or hostaway. A strict regional worker may omit the region to use its own cell and returns 421 MISDIRECTED_REQUEST for an explicit mismatch. Crunchy GL placement returns 400 BAD_REQUEST before provisioning. Because General Ledger is the default, these rules also apply when generalLedger is omitted. Non-General Ledger teams remain restricted to Crunchy, and an explicitly selected non-Crunchy region now returns 400 BAD_REQUEST before provisioning. Their omitted region defaults to Crunchy; direct regional workers return 421 MISDIRECTED_REQUEST with that placement.
  • GET /statements/pdf now renders one owner statement PDF directly and returns its signed URL without creating an operation. The queued POST /statements/pdf remains available.
  • All 32 generated artifact paths now expose the same delivery split. GET awaits admitted work and returns { url, expiresAt }; POST queues a durable export and returns the standard operation acknowledgement. Oversized GET requests return EXPORT_REQUIRES_POST.
  • CSV, range, batch, PDF, and ZIP renderers run in Trigger and upload directly to regional storage. The public request Worker materializes only one explicitly selected statement PDF, and durable exports no longer invoke a public GET or copy through a transient artifact.
  • GET /statements/by-period/csv now waits for its single-statement Trigger renderer to upload directly into regional transit storage, then returns only the signed URL metadata.
  • Trust Reconciliation PDF GETs now wait for rendering outside the regional Worker and return signed transit URLs. Matching POST routes create durable export operations, avoiding large report data and PDF buffers in Worker memory.
  • Temporary export links now expose an absolute expiresAt matching their seven-day lifetime. R2 CSV and download metadata is aligned across temporary and durable artifacts.
  • Queued export configuration failures now retry within a fixed limit and become terminal failed operations instead of remaining running.
  • Completed export operation resources now include a stable authenticated file download URL. Reservation and transaction journal refreshes both run inline for one explicit ID and queue for multiple IDs or an omitted selection.
  • Webhook test and replay responses now nest the standard operation acknowledgement instead of merging its fields into the delivery response.

2026-08-27

  • Trust Reconciliation JSON, CSV, and PDF requests now validate listingId as a comma-separated list of listing UUIDs. Malformed values return 400 BAD_REQUEST before report work starts.
  • CSV upload, inspection, preview, confirmation, and import failures now expose stable recovery reasons in OpenAPI. Validation details are returned in issues; asynchronous source failures retain their specific failure code. Expired results use 410 GONE, and oversized requests use 413 PAYLOAD_TOO_LARGE. Preview and import status now use the shared operation lifecycle and failure object, and acknowledgements no longer expose the internal CSV Connection.
  • Internal operators can prepare one disconnected VRT preview with POST /internal/teams/{teamId}/vri-vrt-preview, selecting the source users who receive access. Activation uses POST /internal/teams/{teamId}/vri-vrt-preview/activate?confirm=true and preserves preview configuration while moving each PMS credential to its distinct target shell. Repeating activation repairs missing lifecycle effects without moving credentials twice.
  • GET /me now identifies prepared VRI-to-VRT preview relationships for the selected source or target team, including the snapshot timestamp and preview lifecycle. After activation, the selected source user receives the active target link while the target no longer presents itself as a preview.
  • When test or demo teams move between Hyperline Development and billing through VRPlatform or Ximplifi in Hyperline Production, the previous environment is now cleaned up before the target subscription is created. Moving to an external billing partner cancels the subscription but retains the customer for a later return; VRPlatform-to-Ximplifi changes also retain the Production customer.
  • Forced team deletion now removes statement-period-locked journal data and processes at most five team deletions concurrently. It also locks the target team and accounts before child cleanup, commits that cleanup, and performs a final connection pass in the locked transaction that removes the team. Tenant-owned connections now also cascade with team deletion, so a remaining connection cannot block the confirmed removal. Reservation mapping rows likewise cascade with an account that has already passed deletion safety checks. The active-subscription safeguard remains enforced.
  • Effective permissions and permission bundles returned by GET /me, GET /team/members, and GET /partner/members are now enum-backed in the OpenAPI contract. Generated API client consumers receive literal unions and the exported ApiScope and UiPermissionBundleName types instead of untyped strings.

2026-08-26

  • Completing team onboarding no longer initializes or changes subscription status, billing plan, or cancellation date. Missing billing state remains visible until the Hyperline workflow reconciles it.
  • Breaking before release: the Stripe payout reconciliation CSV preset now requires only the payout bank account. Reported categories and fees resolve through stripe_<name> transaction line mappings; configured application IDs, fee rates, fixed fees, and category account IDs were removed.
  • GET /reservations and GET /reservations/csv now accept dateField=cancelledAt. The existing single-date and inclusive start...end syntax filters on the reservation cancellation timestamp and composes with other reservation filters.
  • Listing-parent dry runs and mutations now return the structured lockedAccountingData and listingGroupHistoryMigration context when a zero-value owner revenue or expense row predates the parent statement's open date. The blocked operation still leaves listing, ownership, statement, and journal state unchanged.
  • Explicit POST /reservations/journal-entries ID selections now finish inline and atomically. total counts completed refreshes; a missing, locked, or failed reservation returns structured per-ID details and rolls back the selected set. Omitting ids retains queued portfolio refresh behavior.
  • Transaction create and batch-item requests now reject a missing description before execution for every non-transfer type. Transfers may still use the server-generated bank-context description.
  • POST /statements now returns 409 CONFLICT with structured identity details when another statement already occupies the requested listing and effective start date. Reload current statements instead of retrying the create as a transient server failure.
  • Breaking before permission launch: the dashboard role catalog now contains Team Admin, Standard Member, Operations, and Read Only. Accountant, Owner Statements, and Reports Viewer were removed; stored shadow assignments are migrated to their replacements.
  • Replaced broad transaction writes with independent expense, deposit, transfer, and payout write scopes. Operations receives only transactions:expenses:write; broad roles receive all four. Type changes require both scopes, mixed batches are checked before mutation, and payment state still needs the separate payment permission.
  • Operations can now read calendar blocks, recurring fees, and statement layouts and can create or edit expenses. It still has no calendar mutation, report, bank, reconciliation, non-expense write, transaction delete, or payout access.
  • Read Only now includes matching exports for listings, contacts, reservations, transactions, accounts, statements, reports, and bank records.
  • Standard Member no longer includes owner or vendor ACH setup. Team Admin includes ACH setup, while ACH execution remains an additive ACH Payer grant.

2026-08-25

  • Ramp payout Cancel and Archive now return the standard asynchronous operationId contract. Poll GET /operations/{id} for the provider-payment-recovery lifecycle, then refresh the transaction and provider-payment history for the canonical outcome. The status read requires operations:read; it no longer requires access to integration sync details.
  • Ramp payout recovery cancellation normalizes to the public terminal failed state. Provider-specific cancellation details remain internal.
  • Banking documentation now starts with a short workflow index. Use Reconcile Bank Records for transaction and BankRule suggestions, explanations, ambiguity, automatic execution, and multi-booking payouts. Use Bank Accounts and Connections for Plaid, feed mapping, imports, ACH readiness, and recovery.

2026-08-24

  • Audit Events now cover major team and access settings, accounting rules and mappings, recurring configuration, integration mappings, API tokens, webhooks, and templates. Protected values appear only as redacted field names. Coverage is forward-only and does not backfill earlier changes.
  • Breaking: Ramp payouts can be submitted only once. Provider payment history cannot be unlinked, and Failed, Returned, or Canceled payouts must be archived before creating a replacement payout. Vendor-expense retry behavior is unchanged.
  • Transaction reads now include providerPaymentActions with server-owned availability and disabled reasons. Added provider-first Ramp payout Cancel and Archive operations plus explicit recreatedFromTransactionId links.
  • Ramp payout fields and hard delete stay locked after every ACH attempt. Same-bill retries made directly in Ramp remain in attempt history, and Returned now requires Transfer API RETURNED_BY_RDFI evidence.
  • Bank-record transaction suggestions now include typed match reasons, matchingDate, ambiguity, and related listing and reservation context. Bank-rule suggestions include direction, account-scope, and per-condition outcomes so clients can explain why each suggestion exists.
  • Bank-record reads now accept includeMatchingTransaction and a separate matchingTransactionDaysOffset that defaults to seven days. The ordered matchingTransactionSets[] items contain one to five eligible transactions. Bank-rule suggestions remain separate in matchingRules[]; multi-member discovery uses an internal seven-day maximum, considers at most 20 eligible nearby candidates, and returns at most 10 sets. The overloaded includeMatchingTransactions query remains supported but is deprecated.
  • Rules remain ordered deterministically, but same-mode rules with the same priority are marked ambiguous. If multiple automatic rules share the highest matching priority during bank-record ingest, the record remains unmatched and the batch returns a CONFLICT issue with context.bankRuleIds.
  • Reconciliation suggestions still require an exact total. This change does not add amount tolerances, adjustments, or multi-bank-record matching.
  • The CSV workflow guide is now named Import Data from CSV.
  • Breaking before release: normalized the CSV import contract. Preview requests now use selection, operation responses are bare objects, lifecycle status uses started and canceled, leaf paths use {id}, and import status and records use /csv-imports/{id}.
  • CSV upload references now retain their resource. Clients send resource only to POST /csv-imports/uploads; upload, inspection, and preview requests do not resend it.
  • CSV record pages now accept up to 250 records and use the shared opaque cursor contract. Preview confirmation no longer accepts a JSON body.
  • GET /teams?excludeTestTeams=true now excludes teams explicitly marked for testing in addition to the demo tenant and its accounting or billing teams.
  • Partial PUT /recurring-fees/{id} requests now preserve an existing percentage rate type when rateType is omitted. New fee definitions still default an omitted rate type to Flat Rate.
  • Bank-rule create and preview requests now return validation errors when an expense omits its vendor or a deposit supplies expense-only fields.
  • Ownership-period create and update requests now reject a repeated owner contact during request validation.
  • POST /team/members/invite now returns the documented 400 BAD_REQUEST conflict when the selected team already contains an owner membership for the email. The request creates no invite token and sends no email.

2026-08-22

  • Management Commission tax now posts separately for included or excluded tax behavior when the tax rate has a payable account. If its optional debit tax override is omitted, the fee's debit account is used for that side.
  • PUT /listings/{id}/opening-balance can clear one existing listing opening balance to zero across a statement-period boundary. Non-zero changes, books closing, and journal entries directly attached to an owner statement remain locked.
  • Added public GET /countries and GET /countries/{countryCode} endpoints for pre-provisioning country selection. Responses include the English name, two-letter country code, applicable currencies, and assigned us, eu, or ap data region.

2026-08-21

  • Added GET /partner/teams, a control-plane team catalog that lists the authenticated Partner's managed teams across all data regions.
  • vrt_team_lookup now searches globally through GET /me/teams for OAuth or GET /partner/teams for Partner API tokens. Callers no longer select a regional partition before finding a team.
  • Deprecated GET /teams/lookup. Use GET /teams?search= for rich regional inventory, /me/teams for OAuth discovery, or /partner/teams for Partner API token discovery.

2026-08-20

  • Breaking: the Connection-scoped CSV import API now supports fixed templates only. Its reviewed upload, preview, confirmation, and asynchronous status routes were removed; reviewed imports use the top-level /csv-imports workflow.

2026-08-19

  • Ramp owner ACH setup now treats contact address and phone as optional. New vendors use the connected Ramp business location when needed and no longer inherit another vendor's owner or location. Structured Ramp vendor validation failures are returned as the setup failure reason when available.
  • Contact ACH payment methods now include nullable failureReason detail for failed setup attempts. The value comes from safe sync/change status and never exposes raw provider bank-response bodies.
  • POST /transactions/{id}/provider-payments/{providerPaymentId}/cancel requests Ramp cancellation for a materialized payment that has not initiated. Ramp remains authoritative, while the VRT payout accounting and payment link remain intact.
  • Email team-member invitations now reuse their prepared identity and link after authorization commits instead of repeating identity and membership preparation. Delivery failures and copy-link behavior are unchanged.
  • Ramp providerPayment responses now include provider status detail, payment method and reference, the submitted arrival target, and Ramp-returned scheduled-initiation, initiated, and completed timestamps. Ramp payment events are refreshed from the current bill and cannot regress a processing attempt back to requested.
  • New and existing teams now deny partner-derived ach-payments:execute by default. Existing denial policies are preserved, direct memberships remain unaffected, and an authorized direct manager can explicitly remove the ACH denial for a team.
  • Guest Balances now retains the positive accounts-receivable reversal from a zero-dollar co-host clearing transaction. The reversal can offset the reservation's earlier payment, so fully cleared reservations no longer appear as payment-only credits.
  • Authenticated team-scoped writes return HTTP 423 with code TEAM_MIGRATION_FROZEN while a controlled regional migration is copying and verifying that team's data. Reads remain available; clients should refresh team discovery after the freeze to obtain the new regional API base URL.
  • Schema-less DELETE endpoints accept an empty body even when a client sends Content-Type: application/json. DELETE endpoints that declare a body keep validating their JSON schema.
  • Targeted reservation fee, adjustment, and A/R journal refreshes now return the same structured journalEntries lock as full refreshes when applying only unlocked changes would split an otherwise balanced journal group.
  • PMS line-mapping initialization now proves multi-team line-type usage with bounded indexed lookups instead of scanning every matching reservation line.

2026-08-18

  • POST /statements/pay/preview now returns a warnings array. An explicit positive amount above the statement's remaining available balance adds the non-blocking amountExceedsAvailableBalance warning with structured availableBalance and excessAmount; overpayment remains allowed.
  • POST /teams accepts migratedFromTenantId to atomically link a newly created GL team to the non-GL VRI team it replaces. The source must be managed by the same partner; invalid links are rejected before team creation.
  • Historical statement lines can now link an existing reservation with matchReservationConfirmationCode. Matching is scoped to the statement listing; unknown, ambiguous, or conflicting references are rejected before import.
  • Guest Balances date filters now accept a single date or shared date-range syntax. A range uses its inclusive end as the report's as-of date; malformed ranges return HTTP 400 before report queries run.

2026-08-17

  • Long-running routes now return { operationId, type, status: "queued", resource }. GET /operations adds requester-scoped history, filters, retained status counts, and cursor pagination.
  • Supported resource reads include retained operation counts plus actionable queued, running, and latest unresolved failed work. Team lists retain this state across refresh without per-row operation requests.
  • Calendar updates, connection sync and extract, CSV preview/import, payment dispatch, migration, journal batches, recurring runs, and other long work now use the same four-state operation lifecycle.
  • Generated CSV, PDF, and ZIP routes now have POST initiation and a durable file result. Existing generating GET routes are deprecated compatibility endpoints. Download routes remain synchronous.
  • CSV preview and import status now read persisted VRPlatform results instead of Trigger.dev. Public operation responses never expose runtime IDs, effects, attempts, child progress, or provider details.
  • Team deletion now retains its audit action through destructive cleanup, so authorized deletion runs can persist their final mutation and follow-up work after the selected team row is gone.
  • Bank-record list and CSV-export filters now validate every UUID in ids, accountId, and accountConnectionId. Malformed values return HTTP 400 instead of a database error.
  • Calendar block and unblock failures no longer expose or classify PMS-specific messages in the API. Integrations map recognized denials to the stable calendarRequestRejected reason; unknown failures remain server errors and are reported by the integration runtime.

2026-08-16

  • Asynchronous operation status is now read from the top-level GET /operations/{id} endpoint. The short-lived /partner/operations/{id} path was removed before wider adoption.

  • Operation responses now include a stable public type. Reads remain scoped to the tenant that initiated the operation, not a target entity named in the request, and internal payloads, errors, effects, and runtime ids remain private.

  • Team deletion acknowledgments now return operationId and status=queued. The same operation resource reports the provider-neutral team-delete type; Trigger run IDs and destructive-job diagnostics remain internal.

  • Concurrent removal, demotion, or permission replacement can no longer remove every direct access manager from a team after UI Permission Roles is enabled.

  • Re-inviting an existing member with the same legacy role now preserves all assigned dashboard roles. Requesting a different role replaces the complete assignment with that role's compatibility defaults before retryable email delivery begins. A delivery error does not roll back that committed change.

  • UI Permission Roles and Operations Accounting feature changes now invalidate every affected authorization cache, including feature deletion.

  • Authorization mutations now clear affected cached access before returning, and delayed membership projection events cannot restore an older role or dashboard-role assignment after a newer revision was applied.

  • Mutations that use the asynchronous operation contract return { operationId, type, status: "queued", resource }. Poll GET /operations/{id} for the provider-neutral type, queued/running/completed/failed status, and lifecycle timestamps. Reads are scoped to the tenant that initiated the operation; payloads, errors, effects, and runtime ids remain private. See Asynchronous operations.

  • Concurrent removal, demotion, or permission replacement can no longer remove every direct access manager from a team after UI Permission Roles is enabled.

  • Re-inviting an existing member with the same legacy role now preserves all assigned dashboard roles. Requesting a different role replaces the complete assignment with that role's compatibility defaults before retryable email delivery begins. A delivery error does not roll back that committed change.

  • UI Permission Roles and Operations Accounting feature changes now invalidate every affected authorization cache, including feature deletion.

  • Authorization mutations now clear affected cached access before returning, and delayed membership projection events cannot restore an older role or dashboard-role assignment after a newer revision was applied.

2026-08-15

  • VRI-to-VRT historical statements now select ownership periods only from the statement's mapped target listing. Reservations referenced by a statement can remain on another mapped listing without redirecting statement ownership.
  • Historical account previews now collapse lines onto their preserved structured account paths. Optional description rules are restricted to broad paths, and reservation reuse checks PostgreSQL's real deduplication identity before inserting rows.

2026-08-14

  • Current early-access Partner bundles now grant their complete domain workflows, including deletes, exports, journal posting, statement lifecycle, account and tax-rate maintenance, bank cleanup, team configuration, and partner-member management. Existing managed keys derive the expanded scopes from their bundles without replacement. Payment recording, ACH execution, credential administration, and team partner-deny policy remain separate.

  • partner:general-ledger:v1 now grants books:close for PUT /team/books-closing. Managed Partner-token authorization derives effective scopes from its bundles, so existing keys with this bundle gain the capability without replacement.

  • VRI-to-VRT historical import reruns now preserve imported reservations that are referenced by live journal entries and update their corrected listing mapping in place. Existing payout and deposit links remain attached.

  • UI Permission Roles can now be enabled by team and canaried by user. Until enabled, interactive users keep legacy authorization while migrations and every membership writer maintain the new role-bundle data in parallel.

  • Existing and newly created legacy user memberships receive Accountant. Team Admin, Partner Admin, and ACH Payer remain separate additive roles; Standard Member is available only as an explicit new assignment.

  • Bundle and partner-policy mutations return 403 for legacy-mode actors. Last-access-manager protection is enabled with bundle authorization and a flag rollback restores the earlier member-management behavior.

  • Team partner-deny policy affects partner API keys and embedded sessions only after full-team enablement. Partial user canaries do not change integration access.

  • Ownership-period journal attribution retries now finish without changing journals when their target period was deleted after the original mutation. Current ownership and all journal locks remain authoritative.

  • VRI-to-VRT historical listing previews now include listings used by reservations referenced on importable statements. Imports resolve those targets before writing and return a user error for any missing target instead of a database foreign-key failure.

  • Recurring-fee reservation refreshes now update list-level manager, owner, tax, and payment totals in the same transaction as the fee journal. List and detail totals remain aligned when a fee formula includes deductions such as channel commissions.

2026-08-13

  • Reservation detail, list, filters, and owner-statement payment projections now keep the full guest charge when co-host A/R activity meets or exceeds it. Excess payment remains an overpaid credit instead of being compared with only manager revenue; partial co-host payouts retain manager-only collectibility.
  • POST /transactions/batch now reuses an existing transaction when its sourceId matches, even if the submitted uniqueRef changed. Duplicate or concurrent sourceId submissions return an item-level issue instead of a raw database constraint error.
  • Posting-dated recurring-fee adjustments now preserve a dependent fee's complete historical group when any row is attached to an owner statement. The new adjustment still posts its balanced open-period correction; fully editable dependent fees continue to recalculate transitively.
  • POST /account-connections/batch now returns item-level issues for conflicting create or update groups without leaving the request transaction unusable. The failed group is fully rolled back and later batch processing can continue; no partial group is persisted.
  • POST and DELETE /calendar-blocks now return a 400 user rejection when the provider says that a property is muted. Superseded on August 17 by the provider-neutral integration-owned classification above.
  • Guest Balances now preserves the A/R relief leg of a zero-dollar co-host reclassification when the reservation charge is on accounts receivable. Paid channel-collect reservations no longer appear with their full charge and zero payments; zero-charge phantom co-host credits remain excluded.

2026-08-12

  • Default General Ledger initialization now selects a published template by managing partner rather than data region. Teams inherit the VRPlatform global template only when their partner has no template. Existing but unpublished partner templates fail setup instead of silently inheriting global settings.
  • Centrally managed reservation-line defaults use the same partner or global scope as the selected General Ledger template. Existing team-owned mappings and exclusions remain unchanged.
  • An explicit mutation of an independently balanced reservation adjustment can now refresh its targeted journal group while the parent reservation remains GL-inactive. Reservation status changes continue to inactivate the complete reservation journal.
  • Owner statement layout otherSections now keep reservation adjustments with different source line IDs as separate rows. Each row retains its own description, account category, and amount; net-revenue reservation aggregation is unchanged.
  • Guest Balances now excludes reservation-scoped zero-dollar co-host reclassifications when the reservation charge is outside accounts receivable. An A/R leg paired with an equal Co-Host Payouts offset no longer appears as a guest payment or creates a phantom credit balance.
  • Posting-dated reservation adjustments now preserve statement-attached historical recurring-fee rows. The adjustment and its line-linked fee effect post on the selected open date without normalizing older fee rounding; recurring-fee adjustments still refresh dependent fee definitions.
  • PUT /listings/{id}/parent can now group a child with no published statement history when its current in-review statement matches a published parent statement by dates and currency. Published child history and non-zero pre-close activity remain blocked.
  • Plaid configuration and POST /accounts/{id}/banking-init can now create an explicit opening balance before the team selects its statement start date. Regular transaction writes still require the accounting start date.
  • Bank-record CSV previews now accept null unused mapping columns from completed task output. Signed mappings can leave debit and credit null, debit/credit mappings can leave their signed source null, and clients can submit the reviewed mapping unchanged when confirming the import.
  • GET /reports/journal-entries and its CSV export now return an empty result when a non-empty entityTypes selection maps to no supported journal relation. UI empty-state display values can no longer produce invalid SQL.
  • Ramp connection now automatically activates the unconfigured pushRampAccounts listener and starts a full Category synchronization when autoExtract is enabled or omitted. Reconnect preserves an explicitly inactive listener. Payment preview can report rampCategoryAccountNotReady only while that asynchronous push is pending or after it fails.
  • Concurrent POST /statements requests for the same ownership period are now serialized before financial detail is calculated. A retry reuses the committed statement and journal attachments instead of applying stale empty financials. Use PUT /statements/{id} when changing status.
  • Reservation journal refresh now returns a structured journalEntries lock when changing only unlocked rows would split a balanced posting group. The refresh writes nothing; genuine journal calculation imbalances remain internal errors.
  • Scheduled recurring expenses no longer retry owner-side occurrences that fall inside an inactive listing ownership period. No transaction is created for that date; interactive creates and template runs remain rejected.
  • Background connection, line-mapping, recurring-fee, recognition, and tax-rate changes no longer schedule journal refreshes for legacy reservations missing their canonical listing link. Explicit reservation refresh still reports the invalid data so it can be repaired rather than posted to an inferred listing.

2026-08-11

  • Added additive dashboard roles backed by versioned ui:* permission bundles. Team Admin handles access, settings, API keys, integrations, and ACH setup but does not execute ACH; ACH Payer is a separate role.

  • GET /team/permission-bundles and GET /team/permissions expose stable string names for role selectors. PUT /team/members/{userId}/permission-bundles replaces a member's roles, and GET /me returns assigned bundles plus effective permissions.

  • Added GET and PUT /team/partner-permission-policy. A selected team can deny primitive permissions to partner-derived dashboard sessions, API keys, and embedded sessions without restricting direct team members.

  • Existing admins receive Team Admin; partner-team admins also receive Partner Admin; existing users receive Accountant. Operations covers expense editing and owner-statement publishing without bank management, reconciliation, or general reports. Accountant retains broad day-to-day accounting access with read-only settings. V1 roles and partner policy are team-wide, not bank-account-specific.

  • Recurring-fee adjustment mutations now recalculate chained formula fees in the same targeted journal refresh. For example, changing a cleaning-fee adjustment updates Management Commission when its formula reads the cleaning posting account, while unrelated recurring fees remain unchanged.

  • Recurring-fee listing-period writes now preserve inheritance when their rate equals the definition default. Changing a definition default carries those periods to the new amount and refreshes unlocked reservation journals, while distinct listing-specific overrides remain fixed.

  • DELETE /statements/{id} now rejects statements referenced by payout transaction lines, including requests with onLocked=unlockAndDelete. Use PUT /statements/{id} for lifecycle transitions so the statement ID and payout association remain stable.

  • Grouped owner statement list and detail now use only the parent listing's authoritative opening balance. Legacy child balances no longer inflate the first statement after grouping.

  • PUT /listings/{id}/parent now rejects a child with a non-zero opening balance, including during dry-run. Clear the child balance before grouping, then set the opening balance on the group parent.

  • Reservation journal refresh now orders formula-dependent recurring fees only across overlapping listing periods for the same listing. Adjacent historical and future fee definitions no longer create false cycles that can calculate a management commission before its channel-fee deduction. Deployments do not rewrite existing journals; refresh affected, unlocked reservations to apply the corrected calculation.

2026-08-10

  • PUT /statements/{id} now treats the current status as an idempotent read, and transitions between inReview and published preserve their existing journal attachments. A current projection avoids accounting writes; a stale projection refreshes without detaching entries. A repeated published request can still request email delivery.
  • Owner statement payout preview and execution now validate the requested date against the listing's current statement-period lock. Preview returns statementPeriodLocked with structured openFrom, and execution creates no payout transaction while locked. Select openFrom or a later date and preview again.
  • Reservation detail, list, paid-status filters, and owner-statement rows now recognize an owner-direct co-host reclass that completes the remaining guest balance after an earlier deposit. payment.expected and payment.received remain the full guest charge when their active A/R total settles it; genuine co-host payouts retain manager-only collectibility.
  • Reservations spanning the accounting start now apply inactive pre-start owner adjustments to opening A/R and its matching opening-balance assignment. Reversing imported charges before the start date and replacing them after it no longer stacks both amounts into the guest total.
  • POST /teams now allocates generated slugs from the global tenant registry and validates explicit slugs there before regional creation. A name already used in another region receives the next available suffix instead of failing provisioning after email verification.
  • Plaid Link sessions for Hostaway-region teams now select the hostaway Dashboard customization with its required United States country scope and the Hostaway client name. This applies to create, update, and replace flows; all other regions use the VRPlatform client name and default United States and Canada customization.
  • POST /connections/{id}/csv-import now validates importers against canonical integration capabilities. A stale regional app projection no longer rejects a supported Booking.com payout CSV, and invalid importer requests are rejected before import state or transit files are created.
  • Breaking: payment authorization now separates accounting records from external money movement. transactions:pay is replaced by transactions:mark-paid, and statements:pay is replaced by statements:record-payout.
  • provider: "ramp" on POST /transactions/{id}/pay and POST /statements/pay additionally requires ach-payments:execute. Unlinking a Ramp provider attempt requires that scope plus transactions:mark-paid. provider: null never requires the ACH scope.
  • POST /contacts/{id}/payment-methods/ach now requires the isolated ach-payment-methods:write scope instead of contacts:write.
  • Added partner:payments:v1 for read and record-only payment workflows and additive partner:ach-payments:v1 for ACH setup and execution. Restricted keys must combine both bundles to trigger Ramp payments.
  • GET /apps, GET /apps/{id}, GET /connections, and GET /team now use canonical integration capability metadata. Stale regional metadata no longer hides supported Hostaway reservation or Stripe payout Fetch actions; stored sync: "none" capabilities remain excluded.
  • Ramp statement payout preview and execution now require current chart-of-accounts synchronization evidence for the payout_distribution Category. Preview returns rampCategoryAccountNotReady, and execution creates no payout until a newer full Ramp account push or exact account-event push completes.
  • POST /transactions/{id}/pay applies the same check to every outbound Ramp bill Category and returns 422 before creating a provider-payment request. Existing Ramp connections without identifiable sync evidence must run the Ramp chart-of-accounts flow once before their next Ramp payment.

2026-08-06

  • Reservation list and detail responses now share one materialized issue snapshot and expose issuesComputedAt plus issuesStale. A stale response keeps the prior issue set while a bounded background evaluation runs; clients should re-read until freshness advances and must not recalculate issues.
  • Corrected Airbnb extractor metadata so Payout is the visible ranged capability, Reservation remains hidden but available to supported connections, and disabled Listing, Metric, and Review capabilities remain hidden. GET /apps and GET /apps/{id} return only validated full and cursor capabilities; stored none values are not client actions.
  • Owner statement reads now invalidate when the team's payout_distribution account assignment changes. Selected manual or Ramp bank accounts remain funding accounts only and do not need that assignment; their owner distribution entries continue to use the separately assigned payout account.

2026-08-04

  • GET /reports/trial-balance and /reports/trial-balance/csv now validate every comma-separated classification value at the request boundary. The case-insensitive supported values are asset, liability, revenue, and expense; unsupported values return 400 BAD_REQUEST with a query issue.
  • POST /reservations/{reservationId}/adjustments documentation now states that active bookings require either type or recurringFeeId. A selected accountId alone does not classify the line; canceled bookings remain exempt from this existing rule.

2026-08-01

  • PUT /transactions/{transactionId}/lines/{id} now permits a reconciled deposit line to move between reservation and non-reservation items when the bank match is its only lock. The bank-record link is preserved. Source and destination saved statement periods, books closing, and an attached owner statement still reject the move; locks on unrelated lines do not.
  • GET /teams/lookup can now authenticate an interactive bearer user before a team is selected, without x-team-id. The response remains limited to teams the user can access in the selected regional partition. Use GET /teams?search= for general team-list search.

2026-07-31

  • Breaking: team responses now expose the derived lifecycle state as status; the separate effectiveStatus field was removed. GET /teams and GET /teams/lookup apply the same derived semantics to the status filter, and /partner/billing uses the same contract for its team rows. capabilities.activate reports whether activation can be requested, and PUT /team returns 400 BAD_REQUEST with structured disabled reasons when the team cannot be activated. Deactivation remains available.
  • Flow contact mapping creates and updates now reject unavailable contacts with 400 BAD_REQUEST and context.contactId before persistence. Refresh stale contact selections before retrying.
  • POST /teams now rejects a defaultCurrency absent from the target region's supported currency catalog with 400 BAD_REQUEST and context.defaultCurrency, before creating any team data.
  • The audit events API is marked work in progress: its contract is not yet frozen and can change in ways that are not backward compatible. Read audit events on demand rather than mirroring them into your own store, and contact us if you need a local copy. See Audit Events.
  • Added the generated Audit Catalog: every public audit subject with its field paths, value types, and entity-reference targets, plus the action-code format. The Audit Events guide now leads with reading history directly and documents the 429 rate-limit response.
  • Breaking: statement payment is now per owner. POST /statements/pay requires one selection (contactId, provider, bankAccountId) per ownership member; provider is the sole ACH trigger and provider: null never dispatches, even on a Ramp-backed bank account. For provider: "ramp" the selected bank account's Ramp account connection is the funding account; the former statement-level provider, bankAccountId, fundingAccountId, and selections fields were removed. Results now associate every payout transaction and dispatch syncId with its owner contact and add a recorded status for book-only payments.
  • Breaking: POST /statements/{id}/pay was removed. Pay a single statement through POST /statements/pay with one statement entry; per-statement amount covers additional partial payments and owner receipts.
  • Breaking: POST /statements/pay/preview now accepts the per-owner statements shape and returns authoritative owner allocations after rounding, default bank accounts, ACH readiness, and typed blockers. Omit payouts to receive server-computed defaults.
  • GET /contacts search now matches secondary emails on linked owner users. Contact access and GET /contacts/{id}/viewers responses now include secondaryEmails.
  • Audit-event collection cursors now preserve PostgreSQL's full timestamp precision, preventing duplicate ascending pages and skipped descending rows when a projection batch shares one timestamp. Cursors issued before this repair return 400; restart the walk without the old cursor.
  • Audit-event requests with updatedFrom before the retained-history watermark now return the same 410 and earliestAvailableAt recovery context as an expired cursor.

2026-07-30

  • Breaking pre-consumer change: public audit-event collections now expose typed entity values and accountingImpact.netEffect. The former affected, changedDimensions, postingChanges, and include=postingChanges contracts were removed.
  • GET /audit/events/{id} now returns revision-bound, paginated entryChanges with observed journal-entry identity, typed snapshots, and safe cause attribution. Continue with entryCursor; restart from page one after 409 AUDIT_EVENT_REVISED.
  • Audit-event collections add hasChanges, changedEntityType, and changedPath filters. accountId now matches observed before and after entry sides even when the account/currency net effect is zero.
  • Active team administrators can read their team's public audit events. Ordinary members, owners, and embedded sessions remain denied. Recurring fees, bank records, and account connections are now reviewed root entities.
  • GET /teams/resolve no longer returns billingCustomerId. Integrations that need the regional Hyperline customer ID must call GET /team at the returned apiBaseUrl.

2026-07-29

  • POST /accounts/{id}/banking-init now commits account state, the opening transaction, and the feed checkpoint atomically and returns the complete opening transaction from that operation. Concurrent account cleanup can no longer turn an otherwise successful initialization into an invalid response.
  • POST /teams requests sent to the generic API gateway now route an explicit body dataRegion to the matching regional cell. The creator remains authorized through x-team-id; omitted placement continues to use the receiving cell.
  • Concurrent duplicate DELETE /transactions/{id} requests now return the standard NOT_FOUND response after the winning request removes the row, instead of exposing an internal database no result error.
  • GET /team and GET /teams/resolve now return effective billing-partner metadata when the selected team and partner are homed in different regions. Team application data remains regional; partner identity and lifecycle status come from the control plane.
  • Error message values are now static per condition across the production API. UUIDs, refs, emails, indexes, raw values, provider diagnostics, causes, and lock data are returned in structured context instead. Integrations should continue branching on code and reading context, not parsing display messages.
  • GET /teams/issues now includes a stable feature on every issue for partner-native queue grouping. Issue state remains source-controlled: fixed conditions disappear and later recurrence reuses the issue ID with a higher version. Snooze and application navigation remain partner UI concerns.
  • Audit events (GET /audit/events, GET /audit/events/{id}, GET /partner/audit-events) are now the only audit surface available to external callers, and the partner:audit:v1 bundle grants exactly that family. VRPlatform's internal forensic reads — /audit/actions, /audit/actions/{id}, /audit/effects, /audit/journal-deltas, /audit/mutations, and /audit/timeline — are no longer reachable with partner API keys or ordinary first-party users. They return raw change records, effect retry mechanics, and journal-row identity that the public event contract deliberately aggregates and redacts.
  • Audit change records now record that a sensitive field changed without storing its value. Credential, secret, token, and password fields are reduced to the changed key names.
  • GET /apps and GET /apps/{id} now return category: null for legacy apps that do not have a category. This aligns the response with the existing nullable app data and prevents one uncategorized app from failing the full app collection.
  • Reconnect-created provider syncs now change to failed when their asynchronous dispatch fails instead of remaining indefinitely queued without a Trigger run. Effect retries preserve the sync and its idempotency key.
  • VRI-to-VRT migration is now a supported Partner API workflow under /partner/vri-to-vrt. Bootstrap requires partner:provisioning:v1; account and listing previews plus historical statement import require partner:general-ledger:v1. Requests use sourceTeamId and targetTeamId, and both teams must be managed by the calling partner. The former /internal/migrate-vri-to-vrt routes have been removed.
  • Added partner-only POST /team/duplicate for asynchronous test-team copies. The source comes from x-team-id; uniqueRef provides deterministic exact retries; and optional anonymization preserves accounting amounts, dates, and relationships while replacing visible PII. Cloned connections are disabled and contain no credentials or sync state. Poll the returned audit action for completion; restricted keys need partner:audit:v1 for that read.

2026-07-27

  • Partner API keys can use every documented public partner workflow for a selected managed team. New restricted-key bundles cover audit, calendar, files, general ledger, integrations, recurring fees, statement layouts, and team members. Destructive operations remain outside standard bundles. Credential management, self-service, internal, demo, and legacy routes stay unavailable to partner API keys.

2026-07-25

  • Team responses now return billingPartner metadata for the same effective partner represented by billingPartnerId: the explicit billing partner, otherwise the accounting partner, otherwise VRPlatform.
  • GET /teams now accepts excludeTestTeams=true to exclude the demo tenant and teams assigned to the demo accounting or billing partner while retaining VRPlatform and its direct customers.
  • Team responses began exposing the now-retired effectiveStatus alongside the administrative status. See the 2026-07-31 breaking replacement.

2026-07-24

  • GET /team and GET /teams/resolve now return the effective non-nullable billingPartnerId, falling back to the accounting partner and then VRPlatform when no explicit billing partner is stored. Their partner metadata uses the same VRPlatform fallback when no accounting partner is stored.
  • Embedded members in GET /team and GET /teams/resolve now include lastName alongside the existing firstName, matching the member name fields returned by GET /team/members.
  • Added bearer-only PUT /me so an authenticated user can synchronously update their own first and/or last name. Supplied values are trimmed, blank names and target identifiers are rejected, and the response uses the same user shape as GET /me. API keys and current read-only embedded bundles cannot call the mutation.
  • Plaid sandbox completion no longer treats shared test account masks as GL account duplicates when returning configuration suggestions, issues, and action capabilities. Production sessions continue to reserve last four digits across active and inactive GL accounts.

2026-07-24

  • Added public audit events for synchronizing who or what changed a business entity, which public fields changed, and what the action did to accounting. GET /audit/events lists upsertable events for the selected team and GET /audit/events/{id} returns one event with signed posting-bucket before and after totals. Regional GET /partner/audit-events reads all managed teams in one data region. First-party access is administrator-only; partner API keys need the audit:read scope from the new partner:audit:v1 bundle; owner users and embedded sessions are denied.
  • Audit events cover account, listing, listingOwnershipPeriod, ownerStatement, reservation, and transaction roots with event-time actor, team, and account snapshots and allowlisted field changes. For synchronization, request order=asc, persist nextCursor, and upsert by id when revision increases; accounting revisions reappear on the same event ID. Ascending reads trail real time by a short visibility lag so concurrently committed revisions are never skipped. Online history is guaranteed for at least 180 days; a cursor pointing before the earliest retained event returns 410 with earliestAvailableAt.

2026-07-23

  • DELETE /accounts/{id}/connections/{accountConnectionId} now returns the detached connection ID with status: "deleted".
  • Global VRPlatform admins can now list, create, reveal, and revoke API tokens for any selected team without joining that team. Team admins retain their existing inherited access; API-key and ordinary-user callers remain denied.
  • Expanded the pre-release embed:reports:v1 bundle with read access to GET /accounts, GET /listings, and GET /contacts for embedded report filters. Their detail, CSV, and mutation routes remain unavailable to embedded sessions, and every request remains bound to the session team.
  • Added regional GET /partner/listings for the partner dashboard Listings CSV. It returns active property-manager listings with owning-team identity, calculated display names, structured/full addresses, the existing dashboard team filters, deterministic team/listing ordering, and standard pagination. Explicit team IDs outside the authorized regional slice return 403.
  • Added partner-only GET /partner/connections for regional connection inventory. It returns managed VRI and VRT teams, including empty teams, with active connection app metadata, error state, and current sync state. Team, company, lifecycle, app, and app-scoped error-state filters run before team totals and pagination.
  • Standardized sorting on GET /bank-records, /bank-records/csv, /files, /internal/feature-flags, /partner/billing, /partner/connections, /partner/owner-statements, /partner/users, /syncs, and /teams on one signed sort parameter. An unprefixed field sorts ascending and -field sorts descending. Generated schemas explicitly list both variants and each endpoint's default.
  • This is a breaking change: sortBy, sortDirection, and direction-suffix values are no longer accepted. Migrate sortBy=date&sortDirection=desc, sortBy=-date, or sort=created_desc to sort=-date. Text ordering is case-insensitive, nullable fields place nulls last, and deterministic ID tie-breakers keep pagination stable.

2026-07-21

  • Added VRP-admin message-overwrite administration under /internal/message-overwrites. The collection supports pagination, total counts, deterministic ordering, and case-insensitive partial search across patterns and replacement messages; create, replace, and delete operations reject blank fields and return not found for missing resources.
  • Owner statement summary list, detail, and exports now allocate manual listing payouts tagged to a non-owner payee across the ownership-period percentages. Payouts tagged directly to an owner still belong fully to that owner.

2026-07-19

  • POST /plaid/connect now accepts optional environment=sandbox|production. New Items default to production, and the returned connectId owns the environment for completion and configuration.
  • PRO-16794: added team-name search, pagination, and status-count ordering to GET /partner/owner-statements. Partner dashboards can traverse regional VRT teams with page and limit, or offset and limit, use pagination.total for the complete matching count, and order by draft, inReview, or published counts. Search is a case-insensitive partial match.
  • Added GET /partner/owner-statements for regional partner dashboards. It returns VRT child teams with persisted owner-statement counts for draft, inReview, and published; VRI and manager statements are excluded.
  • GET /me now includes the resolved team's dataRegion and apiBaseUrl. When the resolved team is a partner, it also includes partnerContext.managedTeamRegions with regional API URLs and active/inactive managed-team counts.
  • Regional partner portfolio reads now accept x-data-region. The header selects one storage partition without changing the authenticated partner or team context. Initial support covers team lists/lookups, team issues, and partner-user reads.
  • Portfolio pages and cursors remain regional. Clients that combine a multi-region portfolio call each advertised region independently.

2026-07-18

  • Renamed the external banking identity resource from BankAccount to AccountConnection. Use /account-connections, POST /accounts/{id}/connections, and DELETE /accounts/{id}/connections/{accountConnectionId}.
  • BankRecord contracts now use accountConnectionId, accountConnectionRef, and accountConnection for the optional external identity. accountId remains the canonical posting Account.
  • CSV bank-record imports remain valid with only accountId; they do not need an AccountConnection. CSV exports now label the external identity as Account Connection.
  • Statement payout bankAccountId continues to mean a canonical bank Account and is validated accordingly.

2026-07-17

  • Added compact, source-versioned connection.changed, sync.status.changed, reservation.changed, listing.changed, statement.status.changed, and transaction.changed webhook contracts. Current authenticated API reads remain the source of truth.
  • Webhook creation now requires an explicit verification test after the receiver stores its secret. URL/event-set changes and reactivation require a test of the current subscription revision.
  • Secret rotation now emits signatures from the new and previous secrets for a 24-hour overlap. Added webhook administration quotas, 90-day terminal delivery retention, and one-minute delivery recovery.
  • Outbound delivery now uses private resolve-and-pin egress. Every attempt rejects mixed or non-public DNS answers, dials one validated numeric address, preserves the original TLS hostname, and never follows redirects. Public enablement remains gated on pre-release failure drills.
  • Embedded-session bearer tokens now authenticate on GET /me and preserve their issued team binding when x-team-id is omitted.
  • GET /me now returns user.email: null for embedded identities, which use the partner-provided sub instead of an email address. Email remains required for ordinary authenticated users.
  • Team-member reads no longer fail for users created with an empty secondary email list; stored values are constrained to JSON arrays.
  • Added onLocked=nextOpen to reservation adjustment creation. It resolves books-closed and statement-period conflicts to the earliest valid concrete posting date; onLocked=error remains the default.
  • Reservation adjustment dry runs now return the resolved generalLedgerPostingAt without saving the adjustment.
  • Clarified that reservation line exclusion is set through a mapping with accountId: null; status=excluded and line isExcluded are derived.

2026-07-16

  • Retired partner edge hosts; the public API is served only at https://api.vrplatform.app.
  • Removed machine-to-machine credential references from public documentation; public authentication is x-api-key and Authorization: Bearer as listed in the Authentication summary.
  • Documented the webhook signature headers and HMAC-SHA256 signed-string format in Webhooks.
  • Documented the rate-limit policy in the Error contract: no enforced limits currently, and the API never emits 429.

2026-07

  • Added materialized, versioned GET /teams/issues snapshots.
  • Added team-owned outbound webhook subscriptions, delivery history, retries, secret rotation, testing, and replay.
  • Added team.issues.changed as the first subscribable public event.
  • Published generated issue and webhook catalogs from typed runtime registries.

Exact endpoint schemas are generated from current OpenAPI. Historical design context remains in repository RFCs and version logs rather than in current behavior pages.

Documentation

  • 2026-07: Consolidated UI business rules, concepts, partner setup, imports, and operations into task-oriented journeys while preserving old documentation URLs with permanent redirects.

On this page