VRPlatformVRPlatform

Changelog

Current public contract changes and migration links

Read Versioning and deprecation for breaking-change notice periods and migration rules.

Unreleased

  • GET /bank-records accepts appId to filter by the source app independently of matchedByBankRule. BankRecord reconciliation statuses are paid or unpaid; underpaid, overpaid, and null are removed from the BankRecord contract. See Bank reconciliation.

  • Bank-rule dry-runs validate proposed Transactions before reporting wouldApply. Matching previews return isAutoApplicable=false when the team's GL start date is missing. Asynchronous runs are failed when every attempted record fails; their result endpoint still returns the per-record outcomes. See Bank reconciliation.

  • Ramp ACH setup, expense payments, and statement payouts now return Ramp is not connected when the team has no active Ramp connection. Ramp payment flow is not active now means only that the flow was set to inactive. The status code, reason, and issue code are unchanged. See Bank connections.

  • GET /contacts and GET /contacts/{id} return paymentMethods.ach.setup = { available, reason }, so clients can hide ACH setup until the team's Ramp setup accepts bank details. See Bank connections.

  • Owner statement issues[].message texts now name the condition and the next step. balanceMismatch_start, balanceMismatch_end, netRevenueMismatch, expensesMismatch, and payoutsMismatch add context.statementAmount, context.journalAmount, and context.delta in cents. balanceMismatch_end also adds context.figure (balanceEnd or netIncome). Journal refresh failure messages in journalStatus.failures[].message and operation failure.message now say what to check. Codes, severities, and reasons are unchanged. See Statements.

  • GET /me accepts preferActiveTeam=true for remembered portal preferences. An accessible inactive requested team is replaced with the active default team when available. Explicit selections omit this option. See Team Context.

  • The TypeScript client sends control-plane-only requests, including team discovery and embedded session bootstrap exchange, to the VRPlatform US API. These requests no longer initialize an unused regional database. See Team Context.

  • New GET /partner/vri-to-vrt/reservation-line-mappings returns sanitized VRI reservation setup for an authorized linked source and target, including cross-region pairs. The custom Claude MCP connector adds review guidance for comparing these facts with native VRT mappings. See VRI migration.

  • Listing fee drawers, listing-detail fee tabs and listing tables now show archived recurring-fee definitions as Archived, even when their subscription dates include today. See Recurring fees.

  • Internal user upsert lock timeouts now include retryable: true in their existing 409 CONFLICT response. Control-plane writes may already have committed before a regional failure. See Requests, Locks & Issues.

  • Authentication cache invalidation now retries local deletion failures. Persistent failures return a retryable 503 SERVICE_UNAVAILABLE instead of a generic server error. A preceding mutation may already have committed. See Requests, Locks & Issues.

  • Split-owned statement views, owner previews, and individual-owner PDFs show the active owner's share below the payable summary amount. Full ownership hides the split row. Statement financials and payout behavior are unchanged. See Owner statements.

  • GET /metrics/owner-performance accepts an optional asOf date. It then counts only stays booked by the end of that day (UTC) and not cancelled by then, like lastYearAtDateRevenue of GET /metrics/booked-revenue. Without asOf, nothing changes. See Owner Dashboard Metrics.

  • Team settings has a new showOwnerDashboard flag on GET /team, PUT /team, and GET /me. It defaults to false. When true, the owner portal opens on the owner dashboard for teams that have the dashboard feature. See Owner Dashboard Metrics.

  • New GET /metrics/average-daily-rate-over-time returns revenue, nights, and averageDailyRate (cents, null without booked nights) per check-in bucket of booked stays. It accepts the same interval and date range as the other interval metrics. See Manager Dashboard Metrics.

  • GET /metrics/occupancy-rate-over-time now counts availableNights from ownership periods: only the days a listing is owned and active count. Before, every listing counted for every day of the bucket, so months before a listing joined showed too-low occupancy. A listing without ownership periods no longer adds available nights. See Manager Dashboard Metrics.

  • VRI migration now creates flat fee drafts with a zero formula and separate inactive posting accounts for review. Previously, flat fees copied the management commission formula and accounts. See VRI migration.

  • New scope apps:read. Every dashboard bundle now includes it. GET /apps and GET /apps/{id} accept either apps:read or integrations:read. Before, they required integrations:read, so roles without integration access, such as Operations, got 403 when a page showed an app icon. The hasConnection filter still requires integrations:read. See Access and permissions.

  • POST /connections/{id}/csv-import now accepts a fileUri only if it is a signed upload URL issued by VRP. Any other URL returns 400 "fileUri must be a signed upload URL" and no import is queued. Before, VRP fetched any URL. Send the file as fileData instead.

  • GET /statements/historical/{operationId}, GET /general-ledger/historical/{operationId}, and GET /bank-rules/{id}/runs/{operationId} now return the saved error of a failed operation (message, reason, context, issues) with status 409. Before, a failed operation returned the same 409 "not completed" as a running one, so a client polling it never saw the failure. A running operation still returns 409 "not completed".

  • GET /teams/issues now includes Plaid connections in brokenConnections. Before, Plaid connections were left out, so a Plaid connection with health.status actionRequired or warning raised no portfolio issue. See Portfolio Health.

  • New Mews reservations that share a channel booking number and a room import with the confirmation code <channel number>-<Mews number>. Before, VRP rejected the second one as a duplicate, so it was never imported. Reservations already imported keep their code. See Connections.

  • A Mews date-range fetch now also returns reservations created in the range. Before, it returned only reservations last updated in the range, so a range picked by booking date missed older bookings. See Connections.

  • Listing periods of a recurring fee accept matchBy (stayDate or bookedDate, default stayDate) on POST /recurring-fees/listing-periods, PUT /recurring-fees/listing-periods/{id}, and inline listingSubscriptions of POST /recurring-fees. Responses and activeRecurringFeePeriods return it. A bookedDate period matches reservations by booked date (created date when never booked); posting dates do not change. Periods with different values do not conflict, and the booked-date period wins when both match. PUT returns 400 when matchBy changes on a period that a statement includes. The unique start date per fee and listing now includes matchBy. Audit events for recurringFeeListingSubscription include matchBy. See Match by Stay Date or Booked Date.

  • GET /teams/resolve caches the control-plane fields (name, slug, partner, billing partner, members, enabledFeatures, isOpex) for 30 seconds. A change to a member, a feature, the partner, or the billing partner can take up to 30 seconds to appear; an update to the team itself appears immediately. Regional fields and operations are read live. Access checks are not cached by this change: a removed member is denied on the next request. See Team context.

  • Deposit CSV previews report issues[].code: "coHostWithoutAppliedAmount" for a row with a non-zero Co-Host amount and a zero Applied Amount. The record stays valid and imports. The row records no payment for the reservation. See CSV imports.

  • New GET /reports/owner-balances returns the owner balance at the end of any date, per ownership and currency, including activity not yet on a published statement. It changes no payout, statement, or paid status. See Owner Balance Rows.

  • A Plaid connection whose Item has no valid accounts (NO_ACCOUNTS, NO_AUTH_ACCOUNTS) now reports health.status actionRequired with a replacementRequired issue and the replace action. Before, GET /connections reported healthy while every scheduled sync failed. See Stored Bank Feed Health.

  • Date filters and date fields accept years 1000 to 9998 only. dateRange, paidAtRange, the reservation date filter, audit timeline from and to, and date boundary fields return 400 with issue message Invalid date range, Invalid date filter, Invalid time boundary, or Invalid date for other years. Before, GET /bank-records with dateRange=9999 returned 500 after Postgres rejected year 10000. Date fields read the year from its four-digit part, so month-first dates such as 09/06/2026 stay accepted.

  • GET /me without a requested team resolves the oldest active membership in an active team. A membership in an inactive team is the default only when the user has no membership in an active team. See Team context.

  • PUT /team and POST /teams resolve the migratedFromTenantId source through the control plane, so a non-GL source in crunchy can be linked to a GL target in any other region. The non-GL source, GL target, and same-partner rules are unchanged. A missing source returns 404 with message Source tenant not found. The link is recorded as migratedFromTenantId on the team audit event. See VRI migration.

  • POST /connections/{id}/sync with type: reservation now refetches the named Mews reservations. uniqueRef takes Mews reservation ids or Commander reservation URLs, separated by commas. Before, Mews had no single-reservation sync and the request fetched nothing. A sync fails when Mews does not return a requested reservation. See Connections.

  • POST /connections/connect returns 400 with message Connection provider returned an error and context.providerError (the provider's error code, or unrecognized) when a callback carries an error parameter and its state is not accepted. Other unverifiable callbacks keep Invalid connection state. See Connections.

  • GET /teams/issues adds missingAccountAssignments. It lists the account assignment types that no account on the team holds, in context.assignments. Query one team with teamIds={teamId}&codes=missingAccountAssignments. See Missing Account Assignments.

  • Operations (ui:operations:v1) now includes team-issues:read, so its members can read GET /teams/issues for their team. See Access and permissions.

  • New scope transactions:mark-paid:reimbursement, granted to Operations. It records an expense as paid only without a funding account: POST /transactions/{id}/pay with provider: null, POST /transactions, and PUT /transactions/{id} accept it when the stored and resulting accountId are null and no bank records are involved. Any other payment returns 403 with reason paymentAccountRequiresMarkPaid. A PUT that changes the funding account of a paid expense now requires a payment scope. Recurring template create and update with paymentStatus: "paid" now require a payment scope, so keys holding only transactions:expenses:write can no longer create paid templates. See Transactions.

  • DELETE /listings/{id}?onLocked=archive now archives a listing that belongs to a group instead of returning 400. The listing stays in the group and gets its own deactivation period, so its status is inactive. Owner-side postings of that listing dated from the archive date move to the deactivation period and drop out of the group's owner statements, including bookings the PMS sends later. Entries attached to a statement stay where they are. Archive is no longer refused for booked reservations or recurring fees after today, and context.reason: groupedListingScheduledActivity no longer exists. DELETE /listings/ownership-periods/{id} on that period reactivates the listing and returns its entries to the group's ownership. Ungroup refusals caused by published statements or statement-attached journals add context.nextAction: archiveListing. See Listings & Ownership.

  • PMS cutover listing mappings add the suggested status: the PMS listing name or shared reservations point at one source listing. Apply rejects unconfirmed suggestions with reason: "listingMappingRequired".

  • PMS cutover impactedReservations.unmatchedReservations adds suggestedReservation, the only new-PMS reservation on the same listing and dates. Preview and apply accept reservationMappings to resolve each unmatched reservation. See PMS Migration.

  • POST /plaid/connect/complete returns 422 PLAID_CONNECT_REQUIRES_NEW_LINK instead of 503 PLAID_PROVIDER_UNAVAILABLE when Plaid reports NO_ACCOUNTS or NO_AUTH_ACCOUNTS for the Item. See Bank Accounts and Connections.

  • POST /flows/{id}/connections/{connectionId}/settings/{settingKey} with value: null on a setting that is not set returns 200 {} instead of 400. See Connections.

  • POST /account-connections/batch returns 400 instead of 500 when sourceId or accountId is not a UUID. PUT /account-connections/{id} returns 400 instead of 500 for a non-UUID accountId and now rejects a non-UUID sourceId, which it previously ignored. See Integration writes.

  • GET /me now includes Hostaway and other populated regions in partnerContext.managedTeamRegions for global admins with the VRPlatform team selected. Counts cover all active and inactive property-manager teams. See Team Context.

  • New POST /contacts/{id}/preview issues a one-hour, read-only bearer token that shows the owner portal as one owner contact sees it, including owners who have not been invited. It requires contacts:read, and the token keeps the caller's permissions. Under the token, GET /me returns the owner portal context and every non-GET request except POST exports returns 403. See Listings & Ownership.

  • The locked accounting history banner on owner statements no longer persists for reservations dated before the accounting start. A queued journal refresh that is blocked only from writing inactive pre-start entries into closed books or a locked statement period now completes. A refresh that would change an active entry stays locked and still shows the banner. See Reservations.

  • Guest Balances and reservation payment.received now count a deposit that posts a payment plus an offsetting merchant-fee leg (a payment grossed up for credit card fees). They previously dropped it as a net-zero rebalance. See Reports.

  • GET /reports/owner-statement-summaries-per-layout and its details/{contactId} route no longer fail with 500 when an owner contact has a blank stored email; the email is omitted. Contact create and update, including batch imports, store a blank email as null. See Portfolio.

  • DELETE /listings/ownership-periods/{id} with onLocked=archive on a locked last period that has a previous period now requires onLast=none. The default extendPrevious returns 400 with context.suggestedOnLast: "none" instead of creating overlapping periods. See Portfolio.

  • PMS cutover no longer moves stays cancelled before the cutover date. They post on their cancellation date and stay with the old PMS, so they no longer block a cutover after books close. Apply now rejects old-PMS reservations with an amount and no match on the new PMS with reason: "reservationUnmatched"; preview lists them in impactedReservations.unmatchedReservations. See PMS Migration.

  • New GET /connections/pms-cutover/apply/{operationId} returns the PMS cutover result. A failed preview or cutover now returns its reason and context, such as reservationLocked with up to 100 locked reservation IDs. Ambiguous listing matches return reason: "listingMappingAmbiguous". Preview impactedReservations.lockedReservations lists up to 100 locked reservations with booksClosed or statementAttachment. Operation results that are not ready now return Operation result is not ready. See PMS Migration.

  • Reservation batch items rejected because the PMS connection has no accounting start date now return reason: "pmsAccountingStartMissing".

  • New GET /metrics/take-rate-trend and GET /metrics/booked-revenue return take rate and revenue excluding taxes by check-in month for manager dashboards. Take rate counts only stays with a PM/owner split and returns null with a status when the split does not cover at least 90% of a month's revenue. Booked revenue compares with last year's final and same-date totals. GET /metrics/take-rate and GET /metrics/future-booked-revenue are deprecated. See Reports.

  • YYYY-MM month parameters, such as month on statements and metrics, now reject months outside 01 to 12 with a 400 instead of failing later.

  • Billing-partner changes now activate a newly created first subscription for teams that have completed onboarding. Pricing rollout and cancellation checks still apply. See Team setup.

  • Team status now stays active during onboarding with a pending subscription, provided the team and its billing partner are not suspended. Team reads, resolution, team-list status filters, billing rows, and active-listing counts use this rule. Subscription activation remains tied to onboarding completion. See Team setup.

  • Business rule errors now return a top-level reason, such as transactionReconciled or booksClosed, next to the unchanged code, message, and context. Batch item failures carry the same field. Errors without reason are access or integration failures. See Reasons.

  • GET /statements/by-period accepts ownerId and optional comma-separated listingIds for one month's owner-view details. It omits empty unsaved drafts and applies owner draft visibility. Draft and In Review details are visible to owners when draft previews are enabled. See Statements.

  • Owner financials accepts status=published|all for previews; owners cannot bypass hidden drafts. Owner metrics now default to an exclusive range end. Owner performance counts only owned days for available nights, occupancy, and RevPAR. See Reports.

  • New GET /metrics/owner-performance and GET /metrics/owner-financials return an owner's monthly booking performance (nights, rent, owner revenue, check-ins, ADR, occupancy, RevPAR) and statement totals with payment status, filtered by ownerId, listingIds, and currency. Owner sessions can read them for their own contacts and listings. Both require metrics:read. See Reports.

  • Owner statement details now show attachment changes at once. Uploading, removing, or changing the owner visibility of an expense attachment previously left attachments stale in cached owner views, including drafts. Previously cached details also refresh on the first read after deployment.

  • A duplicate bank last 4 digits error now returns one issues[] entry per affected field, with code: duplicateLast4, a static message, and the field path. POST /accounts and PUT /accounts/{id} point at banking.last4, PUT /account-connections/{id} at lastDigits, POST /account-connections/batch at data[index].lastDigits, and POST /plaid/connect/configure at bankAccounts[index]. Error envelope issues can now carry an optional code. The top-level code, message, and context are unchanged. See Error Contract.

  • GET /reports/journal-entries accepts includeHistoricalLedger. When true, imported historical-ledger entries are returned alongside the entries matched by ledger and status. Owner statement and owner statement summary drilldowns set it, so their journal-entry detail includes imported historical activity. See Reports.

  • Owner statement summaries count payouts on imported historical statements. A historical payout without a contact is split by ownership share. Pre-go-live postings that an imported statement's balance already carries, such as the listing opening balance, are no longer counted again as activity.

  • First-party tracking preserves authenticated owner and partner attribution, including partner users working in managed PM teams. Service events retain users with managed partner access. See Integration Writes.

  • New POST /listings/{id}/children/merge merges standalone listings and their published owner statement history into the group parent {id}. Run it with dryRun=true, then apply with the returned planDigest. It requires the new listings:merge-children scope, granted by the Team Admin role and the new partner:listing-merges:v1 bundle. New GET /listings/{id}/children lists a group parent's child listings (listings:read). See Listings & Ownership.

  • PUT /listings/{id}/parent refusals caused by published statements, statement-attached journals, or locked accounting history now return context.nextAction: mergeListingChildren instead of supportAction: listingGroupHistoryMigration, and name POST /listings/{id}/children/merge. Ungroup refusals are unchanged.

  • GET /statements and GET /statements/totals aggregates include paymentReceived: the sum of payment.received for every matching statement in that currency, before pagination. See Statements.

  • Team isTest now includes the direct classification of the accounting and billing partners, including partners in another region. Inherited classifications do not recurse. See Team Setup.

  • PUT /reservations/{id} statement-period lock errors now include context.ownerStatementId, identifying the Review or Published statement that established the lock boundary. The error message and lock decision are unchanged. See Reservations.

  • GET /partner/billing and GET /partner/listings now return uniqueRef with the partner-provided stable identifier for the team.

  • A statement journalStatus in a failed state carries an optional failures array naming the reservations behind it: reservationId, confirmationCode, invalidationId, month (YYYY-MM), its own reason, and a fixed display message. It appears on every failed reason, including journalLocked, where it names the unresolved build failure behind the lock. Completed accounting-lock skips are never listed. It is scoped to the statement's ownership period, month and currency. Returned by GET /statements, GET /statements/by-period and GET /statements/{id}. See Statements.

  • Reservations return the unmappedFinancialLines error issue with context.lineIds when a non-excluded, non-zero line has no account mapping. A single unmapped line discards the whole reservation's journal build, and the reservation's journalStatus now reports journalConfigurationInvalid / fixJournalConfiguration for it instead of asking for a retry. A bulk journal refresh operation reports the same cause instead of the generic "Journal refresh failed". See Reservations.

  • Reservation financial lines return inactiveReason=priorToStartDate when a line is inactive only because it falls before the accounting start, not the system catch-all; a line also excluded for a higher-precedence reason keeps that reason. Totals are unchanged. The field is on the reservation detail response, so every route returning it carries it: GET /reservations/{id}, POST /reservations, PUT /reservations/{id}, POST /reservations/{reservationId}/adjustments, PUT /reservations/{reservationId}/adjustments/{id}, DELETE /reservations/{reservationId}/adjustments/{id}. See Reservations.

  • GET /plaid/health now checks live provider health even when the last sync failed. Item errors retain their recovery action and include available institution warnings in context.institutionHealth. A failed institution lookup is recorded separately in context.institutionHealthError. See Bank Connections.

  • GET /teams accepts billingCustomerId for an exact regional lookup of current customer bindings within the caller's access scope. See Team Setup.

  • Listing create, update, and batch import now include stateCode in generated address.full, with fallback to the deprecated state field. Listing reads also include the saved state for existing addresses that match the old generated format without a state. Custom address strings and stored data remain unchanged by reads.

  • Internal billing updates accept subscription assignment and billing state in one request. Customer/environment validation and conflicting-assignment rejection still apply. See Team Setup.

  • GET /contacts search now matches a case-insensitive partial full name across firstName and name in display order.

  • Interactive signed-in callers on all 17 /partner routes now receive 403 FORBIDDEN with context (selectedTeamId, selectedTeamType, requiredTeamType, partnerTeamId) when the selected team is not the partner team. Partner API keys keep 404 Not a partner API key. See Partner Context.

  • Transaction updates that change a date and its lines together check the previous lines at the old date and the resulting lines at the new date. Manager-side lines leaving a statement period no longer fail with a statement-period lock; owner-side lines leaving a published period still do. See Locking and Historical Integrity.

  • First-party tracking now checks test/demo teams and internal users in the queue worker, before clearing missing or team-mismatched user references. Deletion events and replays use the same queue. Sec services use the VRPlatform team header and can submit events for deleted teams through the body. See First-party Tracking.

  • GET /team and billing team rows add nullable billingSubscriptionId. The field identifies a team's regional billing subscription. See Team Setup.

  • GET /teams/issues adds journalLocked, journalConfigurationInvalid, and journalUnbalanced for outstanding failed reservation refreshes. Detail context includes affected reservation IDs and a supported action; fetch each reservation's journal preview for current blocking details. See Reservation Journal Refresh Issues.

  • GET /reservations/{id}/journal-preview reports proposed journal mutation counts and typed accounting blockers without changing stored data. The preview contract replaces blocker with shared lock and issues fields, includes safe configuration and imbalance details, and pairs each reason with its supported status and action. Regenerate the client before integrating this response; clients built against the initial preview contract must stop reading blocker. Refresh still checks current locks before writing. See Reservations.

  • Flow responses accept command event listeners such as payment.command.

  • Transaction creation accepts unpaid payment input with no date or bank records without transactions:mark-paid. Update and batch permissions are unchanged.

  • GET /reservations accepts journalChangesFromFeeId to find a fee's pending journal changes. Applied changes and completed accounting-lock skips are excluded.

  • Ownership-period linking includes unassigned journals from current child listings. Existing date boundaries and accounting locks still apply.

  • Reservation adjustments reject missing, deleted, and other-team recurring fees with 404 NOT_FOUND before saving the adjustment.

  • GET /teams/issues includes shared bank-feed health in brokenConnections detail context. Supported connections use the same assessment as account badges, including unknown evidence and account-scoped consent actions. The issue can now have warning severity. See Bank Connection Health.

  • Connections from supported services expose stored health in Account, AccountConnection, and Connection lists and details. The summary includes typed recovery actions and observation times; account-specific consent issues retain their feed scope. Missing, stale, or unreadable evidence returns unknown. See Stored Bank Feed Health.

  • Failed reservation journals now offer accounting-lock review when current locks prevent a full refresh, including older failures without a typed cause. This changes recovery guidance without clearing unapplied changes.

  • Reservation refresh includes deferred-revenue entries for owner adjustments recognized on a different date from the shared reservation receivable.

  • Non-current reservation, recurring-fee, and statement journalStatus values now include a typed reason and supported action. Statement guard errors return the same values as journalReason and journalAction.

  • Recurring fees no longer report completed accounting-lock skips as failed calculations. The protected reservation history remains unchanged.

  • Earlier owner statements can be created, reopened, and republished while all later statements for the ownership period remain Draft. A later Review or Published statement returns 409 CONFLICT with the blocking statement ID, start date, status, and nextAction: returnStatementsToDraft.

  • The support-managed owner portal pilot adds registered custom domains and optional platform promotion in owner emails and PDFs. Support manages registrations at runtime and the portal edge delivers branding to each page; no public lookup endpoint is exposed. See Custom Owner Portal for setup requirements.

  • Reservation detail now returns financials.totals.openingGuestReceivable. This amount identifies guest receivables carried into accounting on the day before the team's start date while remaining included in financials.totals.guest and payment.expected.

  • GET /reservations accepts journalReviewOperationId to list distinct reservations still awaiting review after accounting locks blocked an operation's journal refreshes. Applied changes and active retries are excluded. The existing pagination, reservation detail fields, and owner access apply. See fee recovery.

  • Expense CSV previews identify each missing mapping field. Ambiguous mapped dates return separate mappingRequired issues for issueDate and paymentDate, including their original CSV headers in preview-record details.

  • CSV preview confirmation returns an expected 409 CONFLICT with context.reason: duplicateOverrideNotApplicable when a duplicate override is sent without an exact prior import. Confirm the preview without the override.

  • Operations now includes tax-rates:read, allowing expense forms and the read-only Tax Rates settings page to load. Tax-rate changes and deletion remain excluded.

  • Statement payment preview and recording accept up to 1,000 statements. Omitted or null payout dates resolve when the submission is accepted and remain fixed during queued execution and retries. Completed responses use 200; pending responses use 202 with Location. Await through GET /operations/{id}?waitSeconds=20, then retrieve the same final response through GET /operations/{id}/result. Payment results now separate providerOperation from recording and include nullable failure. Use the UUID Idempotency-Key header for retries, including manual payments; it is required for payments above 100 statements. CSV contracts are unchanged. See bulk statement payments.

  • MCP OAuth calls to /partner routes now require the partner team through teamId or the connection's team_id and forward it as x-team-id. Partner API-key calls remain bound to the credential tenant and omit the header. Source and target customer team IDs stay in each route's documented query or body fields.

  • Operations can create and edit vendors through the new contacts:vendors:write permission. POST /contacts, PUT /contacts/{id}, and POST /contacts/batch accept either that permission for vendor-only changes or the existing contacts:write permission. Vendor-only access does not permit owner changes, invitations, viewer management, deletion, CSV import, or ACH setup.

  • PUT /reservations/{id} no longer advertises unsupported adjustments_update or adjustments_delete fields. Use the dedicated adjustment resource routes to update or delete an adjustment.

  • Team responses expose a required, read-only isTest boolean based on system identities and direct Demo and Test Console partner references. See team setup.

  • Audit events identify PMS imports with source.type=integration and source.connectionId when the action records a sync actor. Captured diffs omit unchanged fields. Identical reservation imports no longer create changes when adults and children match. Fully captured settled updates with no public changes do not create new events; existing event identities remain stable.

  • PM statement JSON, CSV, and PDF routes use the dedicated pm-statements:read and pm-statements:export scopes. Operations (ui:operations:v1) no longer reads or exports PM statements. Team Admin, Standard Member, Read Only, and the partner and embedded statement bundles keep access.

  • Owner-statement CSV and PDF exports reject explicit ownership-period selections containing a period without an owner. 400 BAD_REQUEST includes context.reason: ownershipPeriodsWithoutOwners and the affected ownershipPeriodIds; no export operation is queued.

  • SISS OAuth uses a short, single-use state reference valid for 15 minutes. Complete the callback as the initiating user and team. Restart the flow after cancellation, expiry, or failed completion; legacy SISS states are rejected. Apps requiring server-reference state must start at /connections/connect; /connections/oauth rejects them and unknown app IDs before issuing state. See OAuth connections.

  • Team locks now distinguish migration, deletion, and unknown freeze reasons. Confirmed deleted teams return 410 TEAM_DELETED; missing teams without deletion evidence retain 404 NOT_FOUND. Consumers must handle these codes before rollout. See Errors & Retries.

  • GET /recurring-fees/listing-periods rejects malformed recurringFeeId filters with 400 BAD_REQUEST before querying listing periods.

  • Transaction updates reject duplicate empty-string uniqueRef values before writing. Use null or omit the field for transactions without a reference.

  • Recurring-template deletion waits for an instance creation that already locked the template, then keeps the expense and clears its template link.

  • Reservation financial rows include locks from every journal leg sharing the same payment-line ID. Fee adjustments attached to owner statements therefore appear locked on both their owner and manager rows.

  • CSV imports infer date formats from valid calendar dates across the full file and reject conflicting evidence and impossible dates. Field errors identify their mapping field and original source columns. Bank previews return projected ending balances. Bank mappings accept an optional stable transaction-reference column; same-date-and-amount matches remain valid with possible-duplicate warnings. Preview and import record endpoints accept an issueCode filter. See CSV imports.

  • Owner-statement batch PDF ZIPs require POST /statements/pdf/batch. Valid GET selections return 422 EXPORT_REQUIRES_POST before rendering. Submit the same selector with POST and download the completed operation's result file. See Statement exports.

  • Draft statement list and detail reads reflect current journal totals after financial changes, without mismatch warnings against previously saved draft amounts. Non-draft statements retain their saved totals and validation.

  • Statement freshness excludes completed journal refreshes skipped by accounting locks. These skips no longer produce a statement warning or block review, publication, or payout; other outstanding refreshes still do.

  • Independently balanced reservation adjustments can post on an open date after the reservation is archived. Existing journal history and accounting locks remain unchanged.

  • Audit event app references accept string app IDs such as airbnb. Other entity reference IDs remain UUIDs.

  • Range statement exports skip unattached journal months before ownership begins, so older ledger entries do not fail the whole export.

  • Partner team deletion returns a field issue for the prohibited force=true flag.

  • Connection deletion, archival, and settings changes return 400 BAD_REQUEST before removing a PMS accounting source still required by active connections without accounting dates. Use the PMS cutover flow or deactivate those connections first.

  • Team deletion confirmation errors include a field issue for confirm.

  • 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.

  • Connection reads and nested bank-feed connections expose an optional consent snapshot with status, expiry date, and last check timestamp when the integration declares bank-data consent support. This replaces sissConsent. GET /me and GET /team expose the persisted team addressCountry. See Bank Accounts and Connections.

  • Team aggregates include totalListings, counting active and inactive listings. It is available in GET /team and GET /teams?includeAggregates=true. Existing activeListings counts retain their meaning. See Team setup.

  • 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.

  • Bank CSV previews add result.details.balanceProjection.from, the earliest date among rows the file checks accept and do not exclude. Together with asOf it is the statement period the upload covers, read before rows are validated against the bank account. See CSV imports.

2026-10-05

  • Published Versioning and deprecation. The API keeps one live version. Deprecated operations return Deprecation, Sunset, and a notice Link, including error responses.
  • Deprecated: GET /teams/lookup. Use GET /teams?search=, GET /me/teams, or GET /partner/teams for your authentication type.
  • Deprecated: generating GET CSV, PDF, and ZIP operations that have a POST counterpart. Submit the same selection to POST, poll GET /operations/{id}, then download the completed file. Synchronous downloads remain supported.
  • Deprecated: deleted in the DELETE /bank-rules/{id} response. Read status: "deleted" instead. Field-only deprecations are marked in OpenAPI and do not add operation headers.
  • The historical includeMatchingTransactions bank-record query was already removed before this policy. Use includeMatchingTransaction and read matchingTransactionSets[]; this publication does not restore the old query.
  • Existing deprecated legacy metric operations and the provider-payment unlink operation also carry lifecycle headers. Use the current operations in the Reports guide; choose the metric for the value your client needs. Provider payment history remains permanent; use its documented cancel or archive action when available.

Initial notice date: 2026-10-05.

Initial Sunset: 2027-01-03, after the approved notice window of 90 days.

Removal remains subject to the policy’s contract-partner notice requirements. Publishing these dates does not bypass direct notice.

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