Legacy (VRI) Migration
Move a non-GL team into GL and import historical owner statements
VRI is the legacy VRPlatform product generation that ran without a general ledger; VRT is the current generation with full general-ledger accounting. Migrating a VRI team to a VRT team is a staged flow: bootstrap the new team, map legacy statement accounts and listings onto the target team, then import the historical owner statements.
POST /partner/vri-to-vrt— create the GL team from the legacy source and provision its chart of accounts, statement layouts, and recurring fees.GET /partner/vri-to-vrt/statements/account-mappings— see which legacy accounts need mapping.GET /partner/vri-to-vrt/statements/listing-mappings— see which legacy listings need mapping.POST /partner/vri-to-vrt/statements— import historical statements with explicit mappings.
For a team migrated before booking-channel classification migration was added,
run POST /partner/vri-to-vrt/booking-channels
without rerunning bootstrap or importing statements.
Use a partner API key issued to the partner that manages the source and target
teams. Bootstrap requires the partner:provisioning:v1 bundle. Mapping
previews and statement import require partner:general-ledger:v1. A partner
cannot migrate or inspect teams managed by another partner.
If the GL target already exists, select it with x-team-id and link it before
requesting mapping previews:
PUT /team
Content-Type: application/json
{
"migratedFromTenantId": "legacy-team-uuid"
}A restricted key may make this link with partner:provisioning:v1. The body
must contain only a non-null migratedFromTenantId; clearing the link, renaming
the team, or combining the link with another team edit requires teams:write.
The selected target and source must still belong to the calling partner, and an
existing link to another source is rejected.
Concepts
Effective cutover
glStartAt on the bootstrap request is the minimum requested cutover. The
migration pushes the effective cutover forward to the first day of the month
after the source team's last posted/published owner statement, so live GL
accounting never overlaps months the legacy team already settled.
PMS accounting source
Bootstrap rebuilds PMS accounting windows for the GL target instead of copying
the source dates. The source's single current PMS receives
accountingStartAt = effectiveGlStartAt with no end date. Other copied PMS
shells have no accounting window, so they remain outside live GL accounting.
When multiple PMS connections are active, an existing single open accounting window identifies the current source. If no single current source can be identified, bootstrap rejects the migration before writing the target team.
Booking channels
Bootstrap copies the booking-channel classifications used by the source team's
reservations into the shared control plane, then queues their projections to
every data region. This preserves labels such as Airbnb, Booking.com,
VRBO, and Direct when the target PMS sends only provider aliases.
Historical-statement import repeats this step so an existing migrated team can be repaired safely. An existing global classification must match the source; a conflict rejects the migration instead of overwriting shared configuration. Unclassified aliases remain unavailable to booking-channel selectors.
Historical ledger
Imported statements and their journal entries land on ledger = historical.
They preserve the owner-facing history (balances, statement PDFs/views) without
mixing into the live general ledger that starts at the cutover.
Account mappings
Legacy statements reference VRI accounts that do not exist on the new chart. Each legacy account found on importable statement lines must resolve to one target account. A hardcoded mapper covers the common cases; everything else needs an explicit mapping — the same fetch-then-post pattern as PMS migration listing mappings.
Listing mappings
Historical statements must attach to target VRT listings and ownership periods. Teams created by the bootstrap flow already have deterministic target listing ids. Manually created VRT teams often have different real listing ids, so the statement import supports explicit legacy-to-target listing mappings.
When an explicit mapping targets an existing VRT listing, the import reuses the native ownership period active at the effective cutover and extends its start to inception. Historical statements attach to that period instead of creating a duplicate period ending at the cutover. A rerun removes the replaced imported period after its statements and journal entries have moved. Later real owner changes remain separate periods.
Use targetListingId to attach legacy statement history to an existing VRT
listing. Use targetListingId: null only when the legacy listing has no VRT
match and should be imported as an inactive historical-only listing.
1. Bootstrap the GL team
POST /partner/vri-to-vrt{
"sourceTeamId": "legacy-team-uuid",
"targetName": "Mountain Papa",
"glStartAt": "2025-01-01",
"moveConnectionCredentials": false
}Copies only the narrow bootstrap state:
- tenant shell, listings, and owner identities (as contacts);
- connection shells — credentials stripped and
disabled = trueunlessmoveConnectionCredentialsistrue, which moves credentials over and clears them on the source so OAuth/token integrations are never live twice; - derived ownership periods, preferring historical statement-owner snapshots
from
glStartAtforward and falling back to current listing owners. The first ownership starts at inception, and the GL cutover does not split unchanged ownership.
Bootstrap finishes by initializing the target team's chart of accounts,
statement layouts, and recurring fees from the default template, so the
mapping previews below work immediately. Tax rates and line mappings are not
part of this initialization; run POST /team/init with the target team in
x-team-id if the team needs them. No statements are imported yet — that is
the follow-up flow below.
2. Review account mappings
GET /partner/vri-to-vrt/statements/account-mappings
?sourceTeamId=...&targetTeamId=...Returns the legacy accounts that the current migration plan would import, with a suggestion per account and the target account pool:
{
"effectiveGlStartAt": "2025-02-01",
"oldAccounts": [
{
"legacyAccount": {
"key": "legacy-ref:341",
"ref": "341",
"name": "Rental Income",
"classification": "Revenue"
},
"sources": ["line", "template"],
"suggestedTargetAccount": { "id": "rents-uuid", "title": "Rents" },
"currentTargetAccount": null
}
],
"newAccounts": [
{
"id": "rents-uuid",
"title": "Rents",
"category": {
"id": "category-uuid",
"name": "Revenue",
"classification": "revenue"
},
"type": "ledger",
"status": "active"
}
]
}suggestedTargetAccountis the hardcoded mapper's pick;nullmeans the account needs an explicit user choice.currentTargetAccountreflects mappings stored by a previous import run, so reopening the mapping screen shows what is already resolved.- Only legacy accounts on imported, non-zero, non-payout statement lines are listed; template-only accounts are skipped.
3. Review listing mappings
GET /partner/vri-to-vrt/statements/listing-mappings
?sourceTeamId=...&targetTeamId=...Returns legacy listings used by importable historical statements, exact
uniqueRef target suggestions, and the target listing pool:
{
"effectiveGlStartAt": "2025-02-01",
"oldListings": [
{
"legacyListing": {
"id": "legacy-listing-uuid",
"name": "Ravensong",
"uniqueRef": "461898",
"status": "active",
"pmsStatus": "active"
},
"importedStatementCount": 12,
"suggestedTargetListing": {
"id": "target-listing-uuid",
"name": "Ravensong",
"uniqueRef": "461898",
"status": "active",
"pmsStatus": "active"
},
"currentTargetListing": null,
"currentMapping": null
}
],
"newListings": [
{
"id": "target-listing-uuid",
"name": "Ravensong",
"uniqueRef": "461898",
"status": "active",
"pmsStatus": "active"
}
]
}suggestedTargetListingis set only when exactly one target listing has the sameuniqueRefas the legacy listing.currentTargetListingandcurrentMappingshow what a previous import run stored.currentMapping = "historicalListing"means the source listing was imported as an inactive historical-only listing.
4. Import historical statements
POST /partner/vri-to-vrt/statements{
"sourceTeamId": "legacy-team-uuid",
"targetTeamId": "migrated-team-uuid",
"accountMappings": [
{
"legacyAccountKey": "legacy-ref:341",
"targetAccountId": "rents-uuid"
}
],
"listingMappings": [
{
"sourceListingId": "legacy-listing-uuid",
"targetListingId": "target-listing-uuid"
},
{
"sourceListingId": "legacy-listing-without-vrt-match-uuid",
"targetListingId": null
}
]
}- Each
accountMappings[]entry binds oneoldAccounts[].legacyAccount.keyfrom the preview to one target account id. Explicit mappings win over the hardcoded mapper. - Resolved mappings are stored on the target team, so the import can be rerun
and the preview returns them as
currentTargetAccount. listingMappings[]works the same way for listings: oneoldListings[].legacyListing.idfrom the listing preview per entry, bound to one target listing id or tonullfor an inactive historical-only listing.- Exact
uniqueReflisting suggestions are applied when no explicit listing mapping is sent for that source listing. - The import brings over pre-cutover owner statements, their provenance rows, attached historical journal entries, and statement templates rewritten onto the target chart. Only statements that attach to derived ownership periods are imported.
- Explicit mappings onto existing VRT listings reuse the native ownership period active at cutover. This keeps migrated history and native statements on one ownership timeline when the owners did not change.
- On success, the target team's
statementStartAtmoves to the effective cutover andhistoricalStatementsImportedAtrecords the run.
Repair an existing migration
POST /partner/vri-to-vrt/booking-channels{
"sourceTeamId": "legacy-team-uuid"
}This idempotent backfill reads only classified booking channels used by the
source team's reservations, publishes them to the control plane, and queues
every-region projections. It does not rerun team bootstrap or import historical
statements. The response returns bookingChannelCount. The route requires the
partner:provisioning:v1 bundle.
Exact contracts
The migration routes are part of the generated Partner API reference under the
Partner / VRI Migration tag. The request and response schemas there are the
exact contracts; this guide explains how to sequence them.
Related
- PMS Migration
- Historical Statements — the generic import that creates the same historical-ledger statements from any source.
- Statements and Reporting Periods
- Ownership and Attribution
- Banking and Accounts
