Property reports contract
Reports are at /w/{workspace}/p/{property}/reports. Administrators, revenue managers and
accountants have the built-in reports.view grant; custom roles need that grant for the
selected property. Other roles receive 403; a property outside the selected workspace is
404. All reads use a repeatable-read, read-only database snapshot and export no guest
identifiers, names, contact details, posting descriptions or notes.
Choose Performance, Pickup, Cash, Folio revenue or Cancellation trends, then apply a date range. Every report has a chart, an equivalent accessible table and an authenticated CSV download. Filters survive reload in the URL. Invalid dates identify the field and explain recovery; loading, empty results, denied access, failed reads and export retries are explicit.
All ranges are half-open [from,to) ISO civil dates in the property’s IANA time zone,
with 1–366 days. Money is a decimal string in the property’s explicit currency and ISO
minor-unit precision; no exchange conversion occurs. CSV uses UTF-8, CRLF, quoted RFC
4180 cells, ISO dates, decimal-point numbers and empty nulls. Text formula prefixes are
neutralized; signed numeric values retain their sign. Each call has its own snapshot,
so a later export may differ if another operator writes in between. Responses are
no-store. Reports themselves write no audit records or domain events.
History coverage
Section titled “History coverage”GET /api/v1/properties/{property_id}/reports/history (reports.history) discovers the
available observation history as history_from and available_through property-local
dates. Migration 0030 captures a baseline for existing properties;
new properties begin at creation. Previously overwritten reservation prices/dates,
room/type activation/archive states and removed block states cannot be reconstructed.
Pickup comparisons before coverage are unavailable. Performance keeps all date ranges:
before history_from, capacity uses the rooms and dated OOO blocks recorded in the
baseline, including first observations made by the end of history_from in the property’s
time zone, rather than claiming to reconstruct unknown inventory. Each period, source and
total reports pre_history_nights, the number of stay dates before coverage (not a count
of room-nights). The screen explains this basis and marks affected periods. Rooms created
on later local dates still start at their creation date; subsequent changes keep their
observation times. Cash and folio revenue use the existing append-only ledger and have no reservation-history restriction.
The new CSV resources use _csv to follow the API’s snake-case path convention;
the existing performance.csv URL is retained.
Snapshots are PII-free reporting facts captured in the same transaction as the existing audited reservation or inventory write. A failed write leaves neither state nor history. A pickup observation records when GuestCraft received a change; an OTA’s earlier knowledge is not implied. See ADR 0031 for the boundary and capture decisions.
Performance
Section titled “Performance”GET …/reports/performance(reports.performance).GET …/reports/performance.csv(reports.performanceCsv).
Query: from, to, group_by (day, Monday-based week, or month; default day).
Edge buckets are clipped. PerformanceReport definition version 2 returns historical
capacity rather than the former current_active_units denominator, alongside history
coverage, property/currency/time zone, totals, periods and sources. This changes the closed
definition_version and capacity_basis values and requires the revision 5.0.0 SDK; see
the dated exception in ADR 0016.
Available room-nights use each date’s observed unit and room-type activation/archive state, less out-of-order blocks. Later changes do not restate earlier days. Out-of-service blocks and housekeeping status do not reduce saleable capacity; future dates use latest known inventory and dated blocks. Capacity is aggregated independently of booking rows. Sold nights and booked room revenue use frozen non-cancelled nights before release; tentative, unassigned and departed sold nights count. Booked room revenue is a night’s price less the package services it includes (packages), so ADR and RevPAR measure the room alone; pickup snapshots taken from migration 0054 on do the same, while earlier snapshots keep what they recorded. This is booked revenue, independent of payment and folio postings. Ratios with no denominator are null; ADR and RevPAR round half to even. Summary ratios use total numerators/denominators, never averages of ratios. Sources use the whole property’s capacity. Movements and stay/lead averages are per room; cancellations describe impact by stay date, not cancellation events by timestamp.
CSV includes a header, total row, chronological periods and source rows, with the same
metric fields as JSON plus property/currency/time-zone/grouping context. The existing
columns retain their order; definition_version, capacity_basis, history_from and
pre_history_nights are appended at the end.
Pickup
Section titled “Pickup”GET …/reports/pickup(reports.pickup).GET …/reports/pickup_csv(reports.pickupCsv).
Query: stay-date from, to, and observation dates baseline, comparison.
The baseline cannot follow the comparison, precede history_from, or exceed the known
available_through date. Future as-of dates are refused. The current as-of day includes
observations visible at the report snapshot; later changes today are not predicted.
A date means the end of that civil day in the
property’s time zone, including all observations before the next local midnight.
Each stay-day row shows booked rooms and frozen revenue at both snapshots and the signed
difference (comparison − baseline). Cancellations and reductions can produce negative
pickup. Reservations not yet observed contribute zero; subsequent edits, cancellation,
early release and re-pricing do not rewrite earlier snapshots. The chart and table use
these same daily rows. CSV includes the selected comparison context.
GET …/reports/cash(reports.cash).GET …/reports/cash_csv(reports.cashCsv).
Query: from, to. Rows group payment/refund postings by each movement’s own date
(service_date) and original payment method, matching the folio and statement. A payment
recorded after midnight for the still-open business date belongs to that recorded date.
Payments, refunds and signed net receipts are separate. Reversals correct the relevant payment/refund subtotal on the reversal’s
own service date. Cash includes every recorded payment method, not only physical cash.
This is a ledger receipts report, not a provider reconciliation or cash-shift closing.
Corrections do not change earlier daily totals; each reversal uses its own date.
Folio revenue
Section titled “Folio revenue”GET …/reports/revenue(reports.revenue).GET …/reports/revenue_csv(reports.revenueCsv).
Query: from, to. Rows group revenue postings by service date (service_date), source
category (room, package, extra, manual, policy, adapter) and canonical tax
category. package holds the services a rate plan’s price includes, split from the room
charge with their own tax categories, so room revenue and package components stay apart.
Room charges are allocated to the night they are for, even when check-in records the
whole stay at once. policy includes cancellation and no-show fees; adapter includes
module levy lines, such as tourist taxes collected for an authority, in these gross
ledger totals. The report does not classify those levies as earned revenue. The source
category describes the posting, not its reservation’s folio. Every movement joins through its own workspace and folio; multiple folios on one reservation
do not multiply or lose postings.
Only charge postings and reversal postings whose original kind is charge count as
revenue. The explicit allow-list excludes payments, refunds, transfers and any future
posting kind until deliberately added. Reversals reduce revenue on their own service
date, retaining their original category. The report sums gross frozen ledger amounts;
it is not a tax calculation, fiscal document or recognized-revenue statement. A tax
category does not imply that a country tax adapter is installed.
Cancellation trends
Section titled “Cancellation trends”GET …/reports/cancellations(reports.cancellations).GET …/reports/cancellations_csv(reports.cancellationsCsv).
Query: from, to, group_by (day, Monday-based week, or month; default day).
Rows group captured room cancellations and reinstatements by the property-local date they
were saved. total, periods, channels (each source, OTA channel name and cause over the
range) and details (the same within each period, empty combinations omitted) share one row
shape. Each row has cancelled_rooms, cancelled_nights, cancelled_value (the frozen
booked value of the cancelled unserved nights, not recognized revenue), retained_fees,
reinstated_rooms, reinstated_value, net_value_lost (cancelled value − fees − reinstated
value, signed), average_lead_time and lead_time buckets. The buckets are
same_day_or_after (0 or fewer days), days_1_to_7, days_8_to_30, days_31_to_90 and
over_90_days, counted from the cancellation’s property-local date to the room’s arrival.
cause is member (a member or integration), channel (a Channex revision) or
option_expired (night audit released an unconfirmed option). No-shows are not
cancellations. A channel cancellation counts when GuestCraft applied the revision, which can
be later than the guest’s action; Channex’s receive time is stored with the event, not used
for grouping. A booking first received already cancelled is not a new cancellation.
Capture starts at migration 0058 for existing properties, or at a property’s creation.
captured_from is that property-local date and pre_capture_days counts each row’s earlier
dates. unknown_date_cancelled_rooms counts the property’s currently cancelled rooms without
a captured cancellation (cancelled before capture, or first received from a channel already
cancelled), whatever the range; they are never assigned to a period. Earlier
cancellation dates are not reconstructed. The CSV has total, period, channel and detail
rows with the coverage columns and one lead_* column per bucket. See
ADR 0031.
Consolidated reports
Section titled “Consolidated reports”GET /api/v1/reports/consolidated(reports.consolidated) and…/reports/consolidated_csv(reports.consolidatedCsv): the workspace of the header.GET /api/v1/organization_reports/consolidated(reports.organizationConsolidated) and…/organization_reports/consolidated_csv(reports.organizationConsolidatedCsv): the organization of the header’s workspace.
The screens are /w/{workspace}/reports (Consolidated report) and /w/{workspace}/organization-report
(Organization report). Query: from, to (1–366 nights, exclusive to), optional
reporting_currency (ISO 4217) and, for the organization, include_sandbox=true.
The workspace report covers exactly the properties where the caller has reports.view, decided
at each property; a member limited to some properties gets those, and integrations’ credentials
with the grant may read it. The organization report is for owners and administrators of the
organization with a browser session; credentials, device tokens and ordinary members get 403. It
covers every active workspace, sandboxes only with include_sandbox=true, grouped by workspace
(name and environment) then property; archived workspaces never appear. Each report is one
read-only snapshot and writes no audit.
Every report states period_basis: property_local_dates: each property’s own [from, to) dates,
so the same range covers different instants in different time zones. Rows:
properties(grouped underworkspacesfor the organization): each property’s performance total in its currency and time zone, with the metrics of the performance report.currencies: per currency, the number of properties and the metrics of the summed statistics. Occupancy, ADR and RevPAR come from summed room-nights and revenue, never averages of ratios; a ratio without a denominator is null. Revenue is never summed across currencies.all_properties: counts and occupancy of every property; revenue only when converted.
With reporting_currency, each currency’s subtotal is converted once, rounding half to even to
the reporting currency’s minor units, with the organization’s stored rate: the latest one dated on
or before the range’s last night and at most seven days older (conversion.rate_max_age_days,
conversion.rates_dated_through), direct, inverse, or crossed through a currency both are quoted
against, of one source and day. Each currency’s converted holds the converted revenue, ADR and RevPAR (over that
currency’s summed room-nights) and exchange_rates, the rates used (none for the reporting
currency itself); all_properties.converted is the sum of the converted subtotals with ADR and
RevPAR over every property’s room-nights. Original amounts are always returned. A currency
without a usable rate answers 422 exchange_rate.missing with currencies (such as
"EUR, USD") and reporting_currency; it is never converted at 1 or 0. Add a rate dated within
the range’s last week, or omit the reporting currency.
The CSV has the columns kind (property, currency, all_properties), workspace identifier,
name and environment (organization report), property identifier and name, currency, time zone,
property count, the performance metric columns, the reporting currency, the converted revenue,
ADR and RevPAR, the rates used (EUR/USD 1.1 2027-01-02 manual, separated by ; ), the period
basis, from and to. Values equal the JSON’s; quoting and formula neutralization follow the
other report CSVs. See ADR 0031.
Exchange rates
Section titled “Exchange rates”/w/{workspace}/settings/exchange-rates lists, adds and deletes the organization’s stored rates.
GET /api/v1/exchange_rates(exchangeRates.list):reports.viewat a property, orexchange_rates.manage. Rates by day, latest first, 100 per page; optionalbase,quote(a pair as stored) anddate. The cursor is bound to the workspace and the filters.POST /api/v1/exchange_rates(exchangeRates.create):base,quote,rate(a positive plain decimal, at most 20 significant digits, kept exactly as typed),dateandreason(1–500 characters). Sourcemanual. A second rate of the same pair, in either direction, for the same day answers 409exchange_rate.duplicate; an unusable rate 422exchange_rate.invalidlinked to/rate(or/quotefor the same currency twice); an unknown currency or empty reason 400request.invalidlinked to its field.DELETE /api/v1/exchange_rates/{id}(exchangeRates.delete): deletes a mistyped rate, with the rate’setagin If-Match (a rate never changes, so its ETag is always"1"; another value answers 412exchange_rate.version_mismatch). Reports read rates when they run and nothing booked depends on them: only later reports change.
Writes need exchange_rates.manage (built in for administrators and accountants) with every
property, from a browser session: API keys, organization tokens and device tokens cannot hold or
use it. Rates belong to the organization and every active workspace of it shares them; an archived
workspace sees none. Adding and deleting are audited as organization entries (exchange_rate.created,
exchange_rate.deleted) with the actor and the rate’s facts, never the reason, and emit no event.
SDK and recovery
Section titled “SDK and recovery”Use a session, device token or workspace API key with the selected workspace header.
The SDK’s reports group exposes history, performance, pickup, cash, revenue,
cancellations, consolidated, organizationConsolidated and each matching Csv operation;
exchangeRates exposes list, create and delete. Generated low-level operations and React
query factories are available under the reports, consolidated_reports and exchange_rates
tags. CSV methods accept parseAs: "text"; the web screen
uses the authenticated transport and creates a local download. No Channex calls or
customer credentials are needed.
Malformed dates/ranges and pickup observation dates outside coverage return
request.invalid with query field errors, including /baseline or /comparison for
unsupported snapshots; choose a supported range and apply again. Performance accepts
pre-history dates and labels their capacity basis. Permission checks precede report data
reads. Every operation documents its exact problem list in the public contract,
including infrastructure failures, authentication and GET body/media validation.
Country reports, owner statements, online payment providers, booking engine, groups/allotments and online check-in remain planned.