Flow
ServicesLeases

Lease billing associations

How leases establish and retain a shared rental billing account.

Domain
Services
Kind
Go HTTP service
Lifecycle
Active
flowctl project
None
Stacks
Runs on its own

Leases owns occupancy agreements, lease parties, dates, and recurring charge definitions. Each lease has one billing_account_id; several leases may share that ID. Billing owns the corresponding association and validates it synchronously before the lease is saved.

Optional rent and charges

rent_amount_cents may be omitted or null when creating a lease. A supplied amount must be greater than zero. Responses and lease events preserve null when rent is unspecified. A lease can start with no charge definitions; add them later through the Charges tab or POST /v1/leases/{id}/charges. Billing schedules from charge definitions, not the rent field. Adding a charge does not change the lease's rent field.

The accounting demo's rent field is optional. Leaving it blank creates no rent charge; entering rent also adds a monthly rent charge. Other explicitly entered charges still apply.

Rent used by fees

Fees that are calculated from rent read it from the lease's rent charge lines, not from rent_amount_cents. A rent charge line is a recurring charge with charge type rent. The fee uses the total of the rent lines in effect on one date:

FeeDate used to find rent
Early terminationThe termination date (effective_end_date)
Month-to-month, percent of rentThe date month-to-month starts

A rent line is in effect on a date when its effective_from is on or before that date and its effective_to (the last day it applies) is empty or on or after that date. Concessions are separate charge lines and do not reduce this amount. If rent changes through the charge schedule, a fee uses the rent for its own date.

If no rent line is in effect on the date, a rent-based fee is rejected with a conflict error. Flat fees and month-to-month conversion with a fixed fee or no fee do not need rent. Leases the accounting app created before 2026-09-29 have a rent amount but no rent line, so Billing never scheduled their rent and rent-based fees are rejected for them.

The accounting app shows the rent in effect today, read from the rent lines in the same way, on the lease list, the lease drawer, and Take Payment. A lease that has not started shows its first rent.

Converting to month-to-month can also set the rent for the month-to-month term with rent_cents. A rent line cannot start after the contracted end until month-to-month is scheduled, so the conversion makes the change itself. Each open monthly rent line ends the day before month-to-month starts, a new rent line for rent_cents starts that day, and a percent fee is calculated from the new rent. The conversion is rejected when a rent line already starts on or after that day or already ends after it. The accounting app fills the month-to-month rent with the current rent and sends it only when it differs from the rent already in effect on that day.

Lease and party dates

Contractual start and end dates describe the expected lease term. Only create and a draft revision change them. The effective end is the day the agreement and its charges stop, and it can be set while the lease is still active. Move-in and move-out record occupancy. They are not charge boundaries. The notice date records when notice was given. The vacate day is stored as the effective end, and notice does not change the contractual end or end the lease. A legal entity stores fees and the notice period separately. A fee is a rent multiple, a flat amount, or a percent of rent, and each fee kind has its own versions. The notice period is the required number of days. The accounting app edits both on Policies. No fee is charged for short notice. The month-to-month fee is a percent of rent only (typically 40%) and is the legal entity's default; nothing reads it yet, and converting a lease to month-to-month still uses the fee entered on that form. Early termination reads the early_termination fee policy active on the effective end date and stores the result on the lease; a missing policy rejects the command.

Each party has a separate occupancy start and optional occupancy end. The end is inclusive. An absent party end means that person remains through the lease. When omitted during creation, the party start defaults to the contractual lease start. A party cannot start before the lease's contractual start or end before their own start. A party end can be after the contractual lease end for a holdover. Party dates can be edited on draft and active leases, and are fixed after a lease is ended or cancelled.

Revising a draft lease moves party starts and ends that still match the old lease boundaries. Null party ends remain null, and other party dates stay unchanged. The revision fails if the resulting party dates would be invalid.

Party dates do not select who is billed or split invoices. Updating them does not change the lease term, billing account, charge windows, or scheduled charges.

Parties can be added to a draft or active lease with POST /v1/leases/{id}/parties, using the roles and date rules of create. The start defaults to the contractual start on a draft and to today on an active lease. A person who is already a party cannot be added again; edit that party's dates instead. A draft party is removed with POST /v1/leases/{id}/parties/{party_id}/remove, which deletes it. On an active lease the same command sets the party's end date and keeps the person in the history. A party who is currently billed, and the last leaseholder with no end date, cannot be removed. Change who pays first. The accounting app adds, removes, and sets payers from the Parties section of the lease drawer. A guarantor is a role and pays only when marked as a payer. Corporate tenants are not supported yet.

Billing account association

Supply an existing account ID when creating a renewal or transfer to continue billing through that account. The account must match the legal entity, currency, and responsible-party set. Marked payers are that set; otherwise the leaseholders are. Changing payers on a draft or active lease, or adding a party who changes who is billed, is rejected when another lease already uses the account. Adding an occupant is not. Changing a party's dates counts as changing who is billed when it makes a person responsible again, for example by clearing an end date that has passed. Every party change also compares the Billing account's responsible parties with the lease's and tries to repair the difference, for example after an end date has passed. The repair is not guaranteed. That repair never fails a change that does not itself change who is billed, and it is skipped on a shared account, where the difference is logged. Invoices already issued keep the account they were billed to. Omitting the ID creates a new account; the system does not infer a shared account from people or units. An invalid supplied ID fails rather than falling back to another account.

Creation retries reuse a persisted lease ID. Before Billing associates an account, Leases asks the catalog whether the unit can take the scheduled dates. The catalog answers and does not reserve the unit. An ineligible unit is refused, and Billing is not called. A completed request is replayed without asking again. Account association alone does not generate charges; activation events drive charge generation. A one-time charge, chosen as one time on the accounting app form, is a once charge line on the lease, both when the lease is created and later on a draft or active lease. Billing creates its charge when the lease is executed, billed on the line's date or the execution day if that is later, so a draft lease does not affect the ledger. Billing POST /v1/charges refuses a charge that names a draft lease. Revising a draft lease keeps the same billing account and does not schedule charges. Ending a lease cancels only its future unbilled charges and retains the account association. Early termination is POST /v1/leases/{id}/early-terminate, not the generic end command. It sets the effective end, leaves the lease active, and publishes lease.early_termination_recorded. The termination fee is calculated from the early_termination fee policy active on the effective end date; if no policy is configured for that date the command is rejected with a conflict. Billing cancels future charges from that event, reverses concessions already invoiced, and can net an operator concession against the fee. A later move-out publishes lease.ended and does not post the fee again. The charge rules are in Billing accounts and invoices. Cancelling a draft lease drops every still-scheduled charge for that lease. A one-time line on a draft was never sent to Billing. A lease that has moved in is ended, not cancelled. An executed lease the resident never moved into can be cancelled: Billing cancels the charges not yet invoiced and voids invoices that have no payment, deposit, or credit applied. Invoices with money applied are left as they are for operators, and nothing is refunded automatically. Neither ending nor cancelling a lease closes a shared account or cancels another lease's schedule. Notice to vacate records the notice date on an active lease and does not by itself set the move-out date. The accounting app records that notice from Manage on the lease drawer, and the lease list shows On notice while the lease stays active. A fixed term keeps its end when vacate is left blank, and the notice date must then fall on or before that end. A vacate date after the contracted end continues the lease month-to-month until that day, and Billing stops charges there. A move-out on or before the month-to-month start drops a conversion that has not taken effect, including one on a lease with no contracted end.

Executing a lease and completing move-in are separate. POST /v1/leases/{id}/execute turns a draft into an active lease with executed_on set and no move-in, and emits lease.activated, which is the event that makes Billing build the schedule. POST /v1/leases/{id}/complete-move-in then records the move-in day on that lease and emits lease.moved_in, which makes Billing bill any one-time line with bill_trigger move_in and changes nothing else. POST /v1/leases/{id}/activate still does both at once, and the accounting app calls it Execute and complete move-in. An active lease with no move-in cannot be given notice, early-terminated, scheduled to move out, or ended. It can be converted to month-to-month, change its charge lines, and have eviction recorded.

Scheduling a move-in stores only a planned arrival, on a draft lease or on an executed lease that has not moved in. The day must be today or later, and on or after the contractual start. It does not execute the lease, set the move-in date, or create charges. Clearing it removes that planned day. Completing move-in does not require a scheduled day and leaves the scheduled day as it was. A schedule or clear is rejected once move-in is recorded. The accounting app records these from Manage on the lease drawer. The lease list shows Move-in scheduled while the lease has no move-in, and Awaiting move-in while it is active with no move-in.

Scheduling a move-out stores only a planned departure on an active lease. It does not set the move-out date, the effective end, or notice, and it does not stop charges. Clearing it removes that planned day. Completing move-out is still the end command, and it does not require a scheduled day.

A month-to-month lease that has no contractual end requires a last day on or after the notice date, which becomes the effective end, and the lease stays active. Billing schedules any missing months through that end. Converting to month-to-month stores the day it begins. That day is the day after the contracted end, or later. The contractual end stays, including after the conversion takes effect. The conversion can add a monthly month-to-month fee (mtmfee) starting on that day. The accounting app starts the percent at 20% of the lease rent and also offers a fixed amount or no fee, on both the convert form and a new month-to-month lease. Billing then inserts about three months of charges from that date, or through the notice move-out when one is already set. Months before that date stay empty. Starting a conversion after notice is rejected. Overlapping leases are permitted on an account; proration and overlap corrections are separate concerns. Billing stores each open monthly rule and refills the scheduling window once a day. Notice or move-out closes the rule, cancels later unbilled periods, and shortens the unbilled period that contains the cutoff. An already invoiced period is left unchanged. Create and add-charge accept a monthly or a once cycle, and lease templates accept only monthly. The accounting app does not offer weekly or biweekly. A once line is one charge billed on effective_from, with no other dates, unless it has a bill_trigger of signing or move_in, in which case it needs no date and Billing bills it at signing or at move-in (a monthly line may carry signing, which bills its first period at signing; see Billing), and Leases checks its charge type with Billing when it is added. On a draft it can be deleted. Revising a draft, and removing a once line from an active lease, follow docs/leases/leases.md. An add-on (a pet, parking space, storage unit, or coworking membership) is a recurring charge line with its own charge type, not a separate record. Adding or removing a line is allowed on draft and active leases only. A line may start no earlier than the contractual start and no later than the effective end. Its last day is bounded by the effective end when set; otherwise by the contractual end, unless the lease is month-to-month or has month-to-month scheduled, in which case there is no bound. lease.charge_added and lease.charge_removed carry charge_id, the line that changed. The accounting app lists a lease's recurring and one-time lines in the Charges tab, previews the first month's amount when adding a recurring one, and removes a recurring line with a last day. On a draft, the Charges tab also previews the whole lease and shows any Billing charges that were entered for the draft before one-time lines existed under "Already in Billing". A recurring rent concession (conr) can be a fixed amount, a percent of rent, or a free month. A free month names the calendar month it starts and a length in half-month steps, such as 0.5 or 1.5. Half a month credits half of that month's rent. A length of 1.5 credits the starting month in full and half of the next month. The lease's own first month begins on the lease start date. Other concession types stay a fixed amount.

The lease drawer shows outstanding invoices computed by Billing after payment applications. It also links to the account, which shows all associated leases and any unapplied funds. See Billing accounts and invoices for the financial rules and coordinated deployment procedure.

services/leases/migrations/0007_multiple_leases.sql removes the unique account index. services/leases/openapi.yaml describes lease operations.

On this page