Recurring Fees
Build fee definition, listing-period, calculation, and preview flows
Mental Model
Recurring fees calculate financial lines from reservation data. A reusable fee definition describes the formula and postings; a listing period activates that definition for one listing and date range.
Resources and Lifecycle
Definitions cover management, additional, booking-channel, cleaning, and merchant fees. They can be active or archived. Listing periods preserve time-based rate changes without rewriting historical configuration.
Changing either resource can refresh fee journals for multiple reservations. Historical books and statement locks still apply to affected postings.
A definition with no active listing period matches no new reservations. A definition change can still refresh reservations whose existing journal entries already reference that fee so obsolete postings are removed safely.
Definition create and update queue one durable planning effect before expanding
high-cardinality reservation refreshes. A successful mutation means that work
is durably queued, not that every reservation journal has already settled.
Journal-relevant updates return a reservation-journal-refresh operation.
Poll that operation, then re-read the fee and affected resources. Their
journalStatus remains authoritative: current means every known scoped
change is applied; recalculating, failed, and stale require visible UI
state instead of assuming the stored journals are current.
Read Model
Render returned includedFinancials, linkedAccounts, resolved rate,
conditions, taxes, recognition, fee lines, locks, and issues. Do not parse a
formula to reconstruct its calculated display.
Use journalStatus for journal-derived freshness and operations to discover
the work that can change it. A fee can be stale without an active operation,
so do not derive freshness from operation presence.
A fee with no known journal invalidations is current, with null freshness
timestamps. It does not need a refresh merely to populate a timestamp. This
status describes tracked changes; it does not independently validate every
historical fee amount.
Calculation Order
When one fee formula reads an account produced by another fee, the producing
fee is calculated first. That dependency applies only when both fees have
overlapping listing periods for the same listing. Listing periods are
half-open: startAt is included and endAt is excluded, so one period ending
on a date does not overlap another period starting on that date.
This ordering is shared by journal refresh and full-journal calculation. A refresh must therefore produce the same amount as Preview after all dependent fees settle. Existing stored journals are not rewritten by a code deployment; refresh affected, unlocked reservations after a calculation-order correction.
Creating, changing, or removing a recurring-fee adjustment also recalculates fees whose formulas depend on the affected posting accounts. The targeted refresh follows those dependencies transitively, so a cleaning-fee adjustment can update Management Commission without rebuilding unrelated fee history. That recalculation applies only while the affected fee group is fully editable. If any existing row for a dependent fee is statement-attached, the adjustment still posts its own balanced correction, but every historical row for that locked fee remains unchanged. Books-close and statement locks still apply to every changed posting.
An ordinary reservation adjustment with a custom posting date is narrower. It creates its own fee effect on that date without normalizing a recurring fee's historical base rows. Statement-attached fee history therefore remains unchanged even when a current rebuild would round that historical amount differently. Its posting status is independent of an inactive base reservation; ordinary reservation and fee postings inherit the reservation's GL status.
Account-total conditions are evaluated against each calculation group. A posting-dated adjustment must satisfy the fee's conditions using that adjustment's grouped entries before its formula runs. For example, a negative adjustment cannot create a fee whose source-account condition is greater than zero, even when the reservation's historical account total was positive.
Fee formulas use active journal rows. Inactive reference-account rows remain
available only when their inactive reason is nonPostingAccount, because those
accounts intentionally carry formula inputs. Every other inactive reason is
excluded, including GL start boundaries, inactive transactions, inactive
listings, inactive accounts, currency mismatches, and cancelled reservation
lines. Historical-ledger rows and reservation rows without an effective
ownership period are also excluded under their explicit reasons. If a row used
by the formula or an account-total condition is inactive without a reason,
preview fails and journal refresh leaves the existing fee postings unchanged
with an integrity issue. Preview and persisted fee calculation apply the same
rule. Formula variables are parsed exactly, so an account ID cannot match a
different account merely because one ID contains the other.
When a percentage fee uses pro-rata recognition, its generated schedule covers the complete window from the first through the last included revenue-recognition source date. This keeps the fee postings on the same effective date range as the formula after a GL start boundary excludes source rows. Flat pro-rata fees keep the full reservation schedule.
Each generated pro-rata or deferred posting uses the ownership period effective on its own posting date. A date with no ownership period remains unassigned so ownership validation can report the configuration gap; it is never assigned to a period from another date. If one inactive leg would otherwise leave a posting group unbalanced, companion legs use the explicit group-propagation reason while the source leg retains its own reason.
Formula results use banker rounding at the cent boundary. Exact half-cent results round to the nearest even cent, including when floating-point evaluation lands immediately beside the mathematical half-cent value. Preview and persisted fee postings use the same rounding boundary.
Decision Table
Fee Definitions
| Field or context | Rule |
|---|---|
| Type | managementFee, additionalFee, bookingChannelFee, cleaningFee, or merchantFee |
| Flat rate | defaultRate is integer cents |
| Percentage rate | defaultRate is basis points; 100000 is 100% |
| Partial definition update | Omitted fields preserve their stored values |
| Posting | Active debit/credit accounts plus `owners |
| Tax | Tax rate plus included/excluded behavior |
| Management-fee tax | Payable account splits tax; missing debit override uses fee debit |
| Recognition | Optional fee-specific override |
| Conditions | Booking channel, reservation status, and account totals |
| Period rate | Optional override; a value equal to the definition default remains inherited |
| Period overlap | Choose one documented conflict strategy |
Account, category, party, fee type, formula, and tax validity must be evaluated
as one configuration. An active fee requires both direct posting accounts, and
all direct, tax, and formula-input accounts must be active and owned by the same
team. Every account-total condition must also reference an account owned by the
same team. A missing account reference returns 400 BAD_REQUEST before the fee
is written. An Account used anywhere by an active fee cannot be deleted or
archived. An archived fee can retain historical account references, but those
accounts must be active before the definition is reactivated. Every
posting-affecting change to a tax rate used by an active fee revalidates its
effective accounts, even when the account IDs do not change.
For Management Commission, the payable tax account controls whether tax is a separate posting. This applies to both included and excluded tax behavior. If the tax rate has no debit-side override, the tax debit uses the fee definition's debit account. Other recurring-fee types continue to require both tax account overrides for a separate tax posting; otherwise their tax remains embedded.
Listing Periods
Listing-period strategies are error, updateExisting,
adjustInsertingItem, and closeExistingPeriods. Explain that a strategy can
change stored neighboring periods, not just the visible row.
Listing-period reads return the effective rate. When a create or update sends that same value as the current definition default, the API stores inheritance instead of a redundant override. A later definition-default change carries those inherited periods forward. A period rate different from the definition default remains an explicit override and does not change with the default.
startAt and endAt must be parseable date strings. The API rejects invalid
values with a 400 validation response before reading or writing fee periods.
Two overlapping open-ended periods cannot both omit boundary dates because no
strategy can determine where one period ends and the other begins. The API
returns a 400 user error instead of inventing a boundary.
Editability
Locked periods cannot be deleted normally. onLocked=archive closes the open
portion while preserving locked journal history. Definition changes can also
be rejected when affected reservation fee entries are in closed or
statement-attached history.
A fee definition can be hard-deleted only before it has generated journal or
payment history. Once related records exist, the default delete returns a
controlled response with suggestedOnLocked=archive. Retry with
onLocked=archive to deactivate the definition and persist one planning effect
that expands into lock-aware reservation refreshes. The API response does not
wait for every affected reservation journal to settle.
Books closing protects every journal entry a listing-period delete would remove, including inactive history. Making an entry inactive does not make a closed posting removable.
Preview and Preflight
Use fee preview to answer what one proposed definition would calculate for one reservation. It returns projected lines with account, party, amount, and fee metadata; projection IDs are not persisted.
Use mutation dry run to validate whether a definition or listing period can be created, changed, or removed. A successful calculation preview does not prove that persistence is currently writable.
Re-run preview after rate, formula, account, party, tax, recognition, filter, listing-period, or reservation changes.
Mutation Recipe
- Load eligible accounts, tax rates, channels, and existing periods.
- Select fee type and rate model.
- Resolve formula inputs and both posting sides.
- Add conditions and recognition only when needed.
- Preview against a representative reservation.
- Configure the listing period and explain its conflict strategy.
- Dry-run the complete definition or period mutation.
- Confirm calculated and persistence effects separately.
- Apply and re-read affected fee and period resources.
Failure and Recovery
stale means known work remains unapplied without an active recalculation.
recalculating means work is queued or running. Re-read the fee after its
operation finishes. failed includes exhausted recalculations and changes
that could not apply because journals were locked; it does not by itself mean
the fee formula is wrong.
Inspect the linked operation and affected reservations before retrying. Fix reported mapping or fee-configuration errors, then request a full journal refresh for the affected editable reservations. Books-close and statement locks still apply. Do not unlock published history merely to clear a fee badge; review whether the intended change needs an adjustment in an open period. A successful full refresh covers earlier changes for that reservation. A partial fee refresh does not establish that every older change has been applied.
Keep formula and account errors beside the posting configuration. When periods overlap, preserve the proposed range while the user chooses a strategy; a lock should surface the affected history and the supported archive outcome. If a posting account is archived, repoint or deactivate the fee before archiving the account. Deactivating a fee cannot inactivate one of its generated accounts while another active fee still uses that account. Repoint or deactivate the dependent fee first, then retry and re-preview.
Common Recipes
Percentage management fee
Choose managementFee, percentage basis points, the included financials,
owner/manager posting sides, tax and recognition behavior, then preview it
against a reservation before saving.
This example calculates a 15% fee from one linked revenue account:
POST /recurring-fees/preview
Content-Type: application/json
{
"name": "Management fee",
"type": "managementFee",
"rateType": "percentage",
"defaultRate": 15000,
"formula": "\"22222222-2222-4222-8222-222222222222\" * \"rate\"",
"creditAccountId": "33333333-3333-4333-8333-333333333333",
"creditParty": "manager",
"debitAccountId": "44444444-4444-4444-8444-444444444444",
"debitParty": "owners",
"reservationId": "88888888-8888-4888-8888-888888888888"
}Change a fee rate next month
Keep the definition stable and create a new listing period with the new rate and non-overlapping boundary. Dry-run the selected conflict strategy.
POST /recurring-fees/listing-periods?dryRun=true
Content-Type: application/json
{
"recurringFeeId": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"listingId": "11111111-1111-4111-8111-111111111111",
"startAt": "2026-08-01",
"endAt": null,
"rate": 18000,
"onConflict": "closeExistingPeriods"
}API Reference
- Create a recurring fee — POST /recurring-fees
PUT /recurring-fees/{id}— Update a recurring fee- Preview a fee — POST /recurring-fees/preview
DELETE /recurring-fees/{id}— Delete or archive a recurring feePOST /recurring-fees/listing-periods— ContractPUT /recurring-fees/listing-periods/{id}— Update oneDELETE /recurring-fees/listing-periods/{id}— Delete one
