Skip to content

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.

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.

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

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

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

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

  • 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 under workspaces for 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.

/w/{workspace}/settings/exchange-rates lists, adds and deletes the organization’s stored rates.

  • GET /api/v1/exchange_rates (exchangeRates.list): reports.view at a property, or exchange_rates.manage. Rates by day, latest first, 100 per page; optional base, quote (a pair as stored) and date. 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), date and reason (1–500 characters). Source manual. A second rate of the same pair, in either direction, for the same day answers 409 exchange_rate.duplicate; an unusable rate 422 exchange_rate.invalid linked to /rate (or /quote for the same currency twice); an unknown currency or empty reason 400 request.invalid linked to its field.
  • DELETE /api/v1/exchange_rates/{id} (exchangeRates.delete): deletes a mistyped rate, with the rate’s etag in If-Match (a rate never changes, so its ETag is always "1"; another value answers 412 exchange_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.

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.