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.
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.
Decision Table
Fee Definitions
| Field or context | Rule |
|---|---|
| Fee type | managementFee, additionalFee, bookingChannelFee, cleaningFee, or merchantFee |
| Flat rate | defaultRate is integer cents |
| Percentage rate | defaultRate is basis points; 100000 is 100% |
| Posting | Debit and credit each combine account and `owners |
| Tax | Tax rate plus included/excluded behavior |
| Recognition | Optional fee-specific override |
| Conditions | Booking channel, reservation status, and account totals |
| Period rate | Optional override of the definition default |
| Period overlap | Choose one documented conflict strategy |
Account, category, party, fee type, formula, and tax validity must be evaluated as one configuration.
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.
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.
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, 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
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. Re-preview after correcting any dependent input.
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
POST /recurring-fees/listing-periods— ContractPUT /recurring-fees/listing-periods/{id}— Update oneDELETE /recurring-fees/listing-periods/{id}— Delete one
