Historic Statements
Import pre-platform owner statement history from any source through the API
Historic Statements
When a team joins VRPlatform, its owners usually have months or years of statement history in the previous system — spreadsheets, PDFs, or a legacy platform. Importing that history preserves owner-facing balances and lets carry-forward amounts line up from the first live statement.
POST /statements/historic accepts already-resolved statement data from any
client. The API does not parse files — your client code extracts the data
(from CSV, PDF, an old database, anything) and resolves listings and accounts
via the existing endpoints; the API turns each payload into a real owner
statement with journal history.
This is the same mechanism the VRI migration uses for its historical statement import.
Concepts
The historical ledger
Imported statement lines become journal entries with ledger = historical and
status = inactive. They render on owner statement details and feed
owner-facing balances, but they never mix into the live general ledger —
trial balances and GL reports are unaffected.
Rendering by account
A historic statement has no stored row layout. The statement detail maps its
journal entries onto the team's statement layout by account, the same way live
statements render. So the import payload references real chart accounts
(accountId), and your client decides which account each source line belongs
to — typically with a mapping table the user confirms once, like the
VRI account mappings.
Reservation rows
A line can carry an optional reservationId. Reservation-linked lines render
as reservation rows on the statement detail — guest name, check-in/check-out,
and confirmation code come from the referenced reservation — instead of
anonymous one-off lines.
The reservation must already exist in the team: either synced from a PMS
connection or created through the reservation endpoints before posting the
statement. This endpoint never creates reservations; an unknown
reservationId is rejected. Lines without a reservationId are perfectly
fine — they render as plain statement lines.
Idempotency
Both the statement and each line carry a client-chosen uniqueRef. Re-posting
a statement uniqueRef replaces the previous import — the statement row is
updated and its journal entries are deleted and recreated — so imports can be
re-run safely while the user iterates on mappings.
Prerequisites
- The listing exists and has an ownership period covering the statement range (with members, when payout lines are imported).
- The chart of accounts contains the accounts your lines reference, and a
payout_distributionaccount exists when payout lines are imported. - A statement layout exists (the listing's assigned layout or the default is frozen onto the statement).
Import a statement
POST /statements/historic{
"uniqueRef": "csv-import:2025-03:ocean-view",
"listingId": "listing-uuid",
"startAt": "2025-03-01",
"endAt": "2025-04-01",
"currency": "usd",
"status": "published",
"centBalanceStart": 0,
"lines": [
{
"uniqueRef": "csv-import:2025-03:ocean-view:rent",
"type": "line",
"accountId": "rents-account-uuid",
"centTotal": 80000,
"description": "March rent",
"date": "2025-03-15"
},
{
"uniqueRef": "csv-import:2025-03:ocean-view:cleaning",
"type": "line",
"accountId": "housekeeping-account-uuid",
"centTotal": -20000,
"description": "Cleaning"
},
{
"uniqueRef": "csv-import:2025-03:ocean-view:payout",
"type": "payout",
"centTotal": 50000
}
]
}Amounts are owner-facing: revenue positive, expenses negative, payouts
positive (paid to the owner). The statement financials are derived from the
lines — here net income 600.00, payouts -500.00, balance end 100.00 —
and centBalanceEnd can be supplied explicitly when the source data carries
its own balances.
Payout lines without a contactId are split across the ownership period
members by their ownership share, one journal entry per member.
The response reports what was created:
{
"id": "statement-uuid",
"uniqueRef": "csv-import:2025-03:ocean-view",
"status": "published",
"replaced": false,
"journalEntryCount": 3,
"financials": {
"centBalanceStart": 0,
"centBalanceEnd": 10000,
"centTotal": 60000,
"centExpenses": -20000,
"centNetRevenue": 80000,
"centTransfer": -50000
}
}Guardrails
- The statement range must be covered by one listing ownership period; statements overlapping an existing statement on the same period are rejected.
- A
uniqueRefbelonging to a statement that was not created by this import is rejected — live statements can never be overwritten. - Deleting an imported statement (
DELETE /statements/{id}withonLocked=unlockAndDelete) also removes its historical journal entries.
Carry-forward balances
Statements chain balances through centBalanceStart / centBalanceEnd.
Import months oldest-first and carry each month's balanceEnd into the next
month's centBalanceStart (or pass explicit balances from the source data),
ending with the balance the first live statement should start from.
