Home Property Setup Expense Types

8 Expense Types

Declare what expense types exist at the property and how they are calculated

What are expense types?

Expense types are a declaration of all costs associated with a property: what needs to be paid and how each payment is calculated. This is the configuration template, not actual charges or payments. Expense types belong to the property itself, regardless of whether it is currently rented or vacant.

The more accurately expenses are configured, the better your rental contracts will be, and the clearer the financial relationship between landlord and tenant.

Workflow: How It Works

Step 1: Create Expense Types (Templates)

First, you define recurring expense types (templates) such as "Electricity", "Water", "Heating". These are the categories that will be used month after month.

Example: Create an expense type "Electricity" with calculation period "Monthly", consumption type "By Counters", unit price 2.50 EUR/kWh.

Step 2: Create Charges (Actual Bills)

Once expense types are configured, you create charges for specific periods. A charge represents the actual consumption for a given month.

Example: For the expense type "Electricity", create a charge for January 2026: consumed 350 kWh × 2.50 EUR = 875 EUR.

Why This Separation?

Expense Types define HOW charges are calculated (price, period, formula). Charges are the RESULTS - actual bills sent to tenants. This separation allows you to reuse expense type configurations every month without re-entering all details.

Consumption types

Fixed

A known, fixed amount per billing period. The price does not depend on consumption or meter readings.

Example: Internet, condominium fee, security service

By counters

Calculated from meter readings: consumption (current minus previous reading) multiplied by unit price. The expense must be linked to one or more counters.

Example: Electricity, cold water, gas

By fact (as incurred)

The monthly amount is unknown in advance. You enter the real amount each period after receiving the invoice from the service provider.

Example: Heating, building maintenance, garbage removal

Flat fee

A fixed monthly amount independent of actual consumption. The tenant pays the same amount every month regardless of how much of the service was used.

Example: Internet 15 €/mo, regardless of traffic volume.

Connection to counters

Counter-based expenses are linked to specific meters installed at the property. When a charge is created, the system takes current and previous readings, calculates consumption, and multiplies by the configured unit price. Multi-zone tariffs (day/night or peak/half-peak/night) have separate prices per zone, and the system calculates each zone independently.

Calculation periods

Each expense has a calculation period that determines how often charges are created. The system can automatically generate charges on the configured schedule.

Manual Charges are created manually by the user
Monthly One charge every month on the configured charge day
Quarterly One charge every 3 months
Semi-annual One charge every 6 months
Annual One charge per year
Charge day Day of the month (1-28) when the charge is created. Default: 15.
Payment days Number of days given to make the payment after the charge is created. Default: 3.
Reminder days Number of days before the deadline when a reminder notification is sent. Default: 3.

Creating Charges Manually

For expenses with consumption type "As Incurred" or calculation period "Manual", you can create charges manually through the Charges page.

When to Use Manual Charges

  • For "As Incurred" expenses where the actual amount varies each period
  • For expenses with "Manual" calculation period that should not auto-generate

How to Create

  1. Go to Properties → [Property] → Charges
  2. Click the blue "Add Charge" button (appears only if you have expenses requiring manual charges)
  3. Select the expense from the modal window (shows expense type, provider, and calculation method)
  4. Fill in the charge form: date, period, amount, and notes

The form shows expense information including provider and any notes from the expense configuration.

Multi-currency

Each expense can use its own currency. For example, one property may have rent in USD, electricity in the local currency, and internet in EUR. The currency is set when configuring the expense and applies to all charges generated from it.

Service providers

Each expense can be linked to a service provider — the party who receives the payment. The system supports four types of providers:

Service provider

Companies from the global directory — utility companies, management companies, internet providers. Search by name or tax number.

Custom contragent

Your personal directory of contractors with full contact and banking details — name, tax number, IBAN, address.

System user

Existing platform users who can receive payments — maintenance workers, property managers. Link by email address.

Partner invitation

Partners invited by email who are not yet registered in the system. An automatic email invitation is sent.

Charge Documents

Each expense charge can have documents attached — invoices, receipts, payment confirmations, or any other files related to the charge. Documents are uploaded to secure S3 storage and served via HMAC-signed URLs. Upload uses multipart/form-data format.

file Binary file to upload (PDF, JPG, PNG, etc.). Required.
document_type_id Document type ID (e.g. 1 = official document, 2 = receipt). Required.
comment Optional text comment describing the document.

Documents are attached to specific charges (not to the expense itself). First create or refresh a charge, then upload documents to it. File size and type restrictions are configured per document type.

Why accurate configuration matters

Expense configuration is the foundation for all financial operations. Accurately declared expenses lead to correct rental contracts, precise charge calculations, timely payment reminders, and transparent financial reports. When a tenant signs a contract, they see exactly what costs are associated with the property.

What this step does NOT cover
This step covers expense configuration only — declaring what costs exist. Actual charge creation, payment processing, and receipt generation are separate operations that happen after expenses are configured and a rental contract is active.

How to add expenses

Expenses can be added through the web interface or via the API. In the web interface, navigate to any property and open the "Expenses" tab.

Property → Expenses tab → Add expense button

For detailed documentation including API endpoints and validation rules, see Property Expenses in the Library.

Expense fields

When creating an expense, the following fields are required:

expense_type_id Type of expense (electricity, gas, internet, etc.). Get IDs from GET /api/expense-types
calculation_period MANUAL, MONTHLY, QUARTERLY, SEMI_ANNUAL, or ANNUAL
unit_price Price per unit as string (e.g. "4.3200"). For fixed expenses this is the total amount
measurement_unit_id Measurement unit ID (kWh, m3, unit, etc.)
currency_id Currency ID for this expense
start_date Start date in YYYY-MM-DD format

Optional Fields

fixed_consumption Fixed consumption quantity for fixed-type expenses (e.g. "1.0000" for a flat-rate service)
end_date End date (nullable). When set, no charges are generated after this date
counter_ids Array of counter IDs to link (for counter-based expenses). Counters must belong to the same property
zone_prices Zone prices object for multi-zone tariffs: {"PEAK": "4.32", "HALF_PEAK": "3.24", "NIGHT": "2.16"}
service_provider_id Service provider ID (from expense-providers endpoint)
account_number Account number at the service provider
charge_day Day of month (1-28) when charge is created. Default: 15
payment_days Days allowed for payment after charge. Default: 3
reminder_days Days before deadline to send reminder. Default: 3
notes Free-form notes (text only, HTML is sanitized)

For Developers: REST API

All expense operations are available through the REST API. Authentication via Bearer token is required.

GET List expenses

Returns all expenses for a property. Use active_only=true to filter only active expenses.

GET /api/v1/properties/{propertyId}/expenses

Filter active only:

GET /api/v1/properties/{propertyId}/expenses?active_only=true

Response (200 OK):

{ "expenses": [ { "id": 1, "expense_type": {"id": 3, "code": "ELECTRICITY", "name_key": "expense_type.electricity"}, "calculation_period": "MONTHLY", "unit_price": "4.3200", "measurement_unit": {"id": 1, "symbol": "kWh", "name_key": "unit.kwh"}, "fixed_consumption": null, "currency": {"id": 1, "code": "UAH", "symbol": "\u20b4"}, "start_date": "2026-01-01", "end_date": null, "service_provider": {"id": 5, "name": "Energozbut"}, "account_number": "0123456789", "is_active": true, "has_charges": true, "can_delete": false, "notes": null, "charge_day": 15, "payment_days": 3, "reminder_days": 3, "counters": [ {"id": 4, "serial_number": "NIK-2281-073915", "tariff_type": "THREE_ZONE"} ], "zone_prices": {"PEAK": "4.32", "HALF_PEAK": "3.24", "NIGHT": "2.16"}, "created_at": "2026-01-01T00:00:00+00:00", "updated_at": "2026-02-15T10:30:00+00:00" }, { "id": 2, "expense_type": {"id": 15, "code": "INTERNET", "name_key": "expense_type.internet"}, "calculation_period": "MONTHLY", "unit_price": "500.0000", "measurement_unit": {"id": 5, "symbol": "unit", "name_key": "unit.unit"}, "fixed_consumption": "1.0000", "currency": {"id": 1, "code": "UAH", "symbol": "\u20b4"}, "start_date": "2026-01-01", "end_date": null, "service_provider": null, "account_number": null, "is_active": true, "has_charges": false, "can_delete": true, "notes": null, "charge_day": 1, "payment_days": 5, "reminder_days": 2, "created_at": "2026-01-01T00:00:00+00:00", "updated_at": "2026-01-01T00:00:00+00:00" } ], "total": 2 }

POST Create expense

Creates a new expense for the property. Only one expense per expense type per property is allowed.

POST /api/v1/properties/{propertyId}/expenses

Headers:

Authorization: Bearer {token} Content-Type: application/json

Fixed expense (e.g. Internet):

{ "expense_type_id": 15, "calculation_period": "MONTHLY", "unit_price": "500.0000", "measurement_unit_id": 5, "fixed_consumption": "1.0000", "currency_id": 1, "start_date": "2026-01-01", "charge_day": 1, "payment_days": 5, "reminder_days": 2, "notes": "Fiber 100 Mbps" }

Counter-based expense with zone prices (e.g. Electricity):

{ "expense_type_id": 3, "calculation_period": "MONTHLY", "unit_price": "4.3200", "measurement_unit_id": 1, "currency_id": 1, "start_date": "2026-01-01", "counter_ids": [4], "zone_prices": { "PEAK": "4.32", "HALF_PEAK": "3.24", "NIGHT": "2.16" }, "service_provider_id": 5, "account_number": "0123456789" }

As-incurred expense (e.g. Heating):

{ "expense_type_id": 1, "calculation_period": "MONTHLY", "unit_price": "1.0000", "measurement_unit_id": 6, "currency_id": 1, "start_date": "2026-01-01", "charge_day": 20 }
Only one expense per expense_type per property is allowed. Attempting to create a duplicate will return a 400 error.

Response (201 Created):

{ "id": 3, "expense_type": {"id": 15, "code": "INTERNET", "name_key": "expense_type.internet"}, "calculation_period": "MONTHLY", "unit_price": "500.0000", "measurement_unit": {"id": 5, "symbol": "unit", "name_key": "unit.unit"}, "fixed_consumption": "1.0000", "currency": {"id": 1, "code": "UAH", "symbol": "\u20b4"}, "start_date": "2026-01-01", "end_date": null, "service_provider": null, "account_number": null, "is_active": true, "has_charges": false, "can_delete": true, "notes": "Fiber 100 Mbps", "charge_day": 1, "payment_days": 5, "reminder_days": 2, "created_at": "2026-02-22T10:00:00+00:00", "updated_at": "2026-02-22T10:00:00+00:00" }

GET Get expense details

Returns full expense details including linked counters, zone prices, and charge history.

GET /api/v1/properties/{propertyId}/expenses/{id}

Response (200 OK):

{ "id": 1, "expense_type": {"id": 3, "code": "ELECTRICITY", "name_key": "expense_type.electricity"}, "calculation_period": "MONTHLY", "unit_price": "4.3200", "measurement_unit": {"id": 1, "symbol": "kWh", "name_key": "unit.kwh"}, "fixed_consumption": null, "currency": {"id": 1, "code": "UAH", "symbol": "\u20b4"}, "start_date": "2026-01-01", "end_date": null, "service_provider": {"id": 5, "name": "Energozbut"}, "account_number": "0123456789", "is_active": true, "has_charges": true, "can_delete": false, "notes": null, "charge_day": 15, "payment_days": 3, "reminder_days": 3, "counters": [ {"id": 4, "serial_number": "NIK-2281-073915", "tariff_type": "THREE_ZONE"} ], "zone_prices": {"PEAK": "4.32", "HALF_PEAK": "3.24", "NIGHT": "2.16"}, "charges": [ { "id": 12, "charge_date": "2026-02-15", "period_start": "2026-02-01", "period_end": "2026-02-28", "quantity": "279.000", "amount": "1205.28", "source": "auto", "has_items": true } ], "total_charges": 1, "required_zones": ["PEAK", "HALF_PEAK", "NIGHT"], "created_at": "2026-01-01T00:00:00+00:00", "updated_at": "2026-02-15T10:30:00+00:00" }

PUT Update expense

Partially updates an expense. Only include fields you want to change. When counter_ids or zone_prices are provided, they replace all existing values.

PUT /api/v1/properties/{propertyId}/expenses/{id}

Updatable fields (all optional):

unit_price string
calculation_period string
counter_ids array
zone_prices object
is_active boolean
charge_day integer (1-28)

Request Body:

{ "unit_price": "4.5600", "zone_prices": { "PEAK": "4.56", "HALF_PEAK": "3.42", "NIGHT": "2.28" }, "charge_day": 10 }

Response (200 OK):

{ "id": 1, "expense_type": {"id": 3, "code": "ELECTRICITY", "name_key": "expense_type.electricity"}, "calculation_period": "MONTHLY", "unit_price": "4.5600", "charge_day": 10, "zone_prices": {"PEAK": "4.56", "HALF_PEAK": "3.42", "NIGHT": "2.28"}, ... }

DELETE Delete expense

Deletes an expense. Returns 204 No Content on success.

DELETE /api/v1/properties/{propertyId}/expenses/{id}

Response (204 No Content)

Cannot delete an expense that has existing charges. Deactivate it instead using toggle-active or by setting is_active to false in the update endpoint.

POST Toggle active status

Activates or deactivates an expense. Deactivated expenses are not used for automatic charge generation.

POST /api/v1/properties/{propertyId}/expenses/{id}/toggle-active

Response (200 OK):

{"id": 1, "is_active": false}

POST Refresh charges for expense

Generates a charge for a single expense if its calculation period has ended and no charge exists for the current period.

POST /api/v1/properties/{propertyId}/expenses/{id}/refresh

Response (200 OK):

{ "success": true, "charge_id": 15, "message": "Charge created for period 2026-02-01 - 2026-02-28" }
Only works for active, non-MANUAL expenses. Manual expenses do not support automatic refresh.

POST Refresh all automatic charges

Generates charges for all active automatic (non-MANUAL) expenses of the property. Returns counts of created, skipped, and errored charges.

POST /api/v1/properties/{propertyId}/expenses/refresh-all

Response (200 OK):

{ "created": 3, "skipped": 2, "errors": 0 }

Documents in the Expenses Module

In the expenses module there are two levels where documents can be attached:

Why attach documents to expenses?

Expense documents allow you to store important reference materials that apply to the recurring expense configuration itself, not individual charges. This includes:

  • Service contracts with utility providers
  • Tariff schedules and rate cards
  • Service level agreements (SLAs)
  • Equipment manuals and maintenance schedules

Expense Type defines the recurring template (e.g., "Water Supply"). Charge is the actual billing instance created from that template (e.g., "Water for January 2026").

Charge A specific billing entry for a period (e.g. electricity for January). Invoices, receipts, and payment confirmations are attached here via the API. Path: /api/properties/{id}/charges/{chargeId}/documents
Expense Type (Template) The recurring expense type definition (e.g. "Electricity, monthly"). Documents such as contracts with contractors or tariff schedules can be attached to the expense type. API path: /api/properties/{id}/expenses/{expenseId}/documents
Not to be confused with Counter Documents
Counter readings have their own document attachments managed via the Counters module. The endpoints below are exclusively for expense charge documents.

Charge Documents API

Base path: /api/properties/{propertyId}/charges/{chargeId}/documents. A charge is created from an expense and represents a specific billing period. Documents attached here serve as proof of payment or invoices for that charge.

GET List charge documents

Returns all documents attached to a specific charge. Each document includes a secure download URL, file metadata, and optional comment.

GET /api/v1/properties/{propertyId}/charges/{chargeId}/documents

Response (200 OK):

{ "documents": [ { "id": 1, "document": { "id": 10, "original_name": "invoice_february.pdf", "file_url": "https://landlordkeeper.com/file/abc123hash", "mime_type": "application/pdf", "file_size": 245760, "document_type": { "id": 1, "code": "OFFICIAL_DOCUMENT", "name_key": "document_type.official_document" }, "created_at": "2026-02-15T10:30:00+00:00" }, "comment": "Electricity invoice for February 2026", "created_at": "2026-02-15T10:30:00+00:00" }, { "id": 2, "document": { "id": 11, "original_name": "receipt_scan.jpg", "file_url": "https://landlordkeeper.com/file/def456hash", "mime_type": "image/jpeg", "file_size": 128000, "document_type": { "id": 2, "code": "RECEIPT", "name_key": "document_type.receipt" }, "created_at": "2026-02-16T14:00:00+00:00" }, "comment": null, "created_at": "2026-02-16T14:00:00+00:00" } ], "total": 2 }

POST Upload charge document

Uploads a file to the charge. Uses multipart/form-data (not JSON). The file is validated against the document type restrictions (allowed extensions, max size) and stored in S3.

POST /api/v1/properties/{propertyId}/charges/{chargeId}/documents

Headers:

Authorization: Bearer {token} Content-Type: multipart/form-data

Upload fields (multipart/form-data):

file Binary file (PDF, JPG, PNG, etc.). Required. Validated by document type restrictions.
document_type_id Document type ID (integer). Required. Defines allowed file extensions and max size.
comment Optional text comment (string, nullable). Plain text, HTML tags are stripped.

Response (201 Created):

{ "id": 3, "document": { "id": 12, "original_name": "water_bill_march.pdf", "file_url": "https://landlordkeeper.com/file/ghi789hash", "mime_type": "application/pdf", "file_size": 184320, "document_type": { "id": 1, "code": "OFFICIAL_DOCUMENT", "name_key": "document_type.official_document" }, "created_at": "2026-02-22T12:00:00+00:00" }, "comment": "Water bill March 2026", "created_at": "2026-02-22T12:00:00+00:00" }

GET Get document details

Returns full details of a specific charge document including the secure file URL, file metadata, and comment.

GET /api/v1/properties/{propertyId}/charges/{chargeId}/documents/{id}

Response (200 OK):

{ "id": 1, "document": { "id": 10, "original_name": "invoice_february.pdf", "file_url": "https://landlordkeeper.com/file/abc123hash", "mime_type": "application/pdf", "file_size": 245760, "document_type": { "id": 1, "code": "OFFICIAL_DOCUMENT", "name_key": "document_type.official_document" }, "created_at": "2026-02-15T10:30:00+00:00" }, "comment": "Electricity invoice for February 2026", "created_at": "2026-02-15T10:30:00+00:00" }

PUT Update document comment

Updates the comment for a charge document. Only the comment field can be changed. Send JSON body with the new comment value. Set to null to remove the comment.

PUT /api/v1/properties/{propertyId}/charges/{chargeId}/documents/{id}

Request Body:

{ "comment": "Updated: electricity invoice Feb 2026 (paid)" }

Response (200 OK):

{ "id": 1, "document": { "id": 10, "original_name": "invoice_february.pdf", "file_url": "https://landlordkeeper.com/file/abc123hash", ... }, "comment": "Updated: electricity invoice Feb 2026 (paid)", "created_at": "2026-02-15T10:30:00+00:00" }

DELETE Delete charge document

Permanently deletes a document from the charge. The file is removed from S3 storage first, then the database record is deleted. This action cannot be undone.

DELETE /api/v1/properties/{propertyId}/charges/{chargeId}/documents/{id}

Response (204 No Content)

Deleting a document permanently removes the file from S3 storage and cannot be reversed. Make sure you no longer need the document before deleting.

cURL Examples

1. Fixed expense (e.g. Internet)

curl -X POST https://landlordkeeper.com/api/v1/properties/5/expenses \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN_HERE" \ -d '{ "expense_type_id": 15, "calculation_period": "MONTHLY", "unit_price": "500.0000", "measurement_unit_id": 5, "fixed_consumption": "1.0000", "currency_id": 1, "start_date": "2026-01-01", "charge_day": 1 }'

2. Counter-based expense with zone prices (e.g. Electricity)

curl -X POST https://landlordkeeper.com/api/v1/properties/5/expenses \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN_HERE" \ -d '{ "expense_type_id": 3, "calculation_period": "MONTHLY", "unit_price": "4.3200", "measurement_unit_id": 1, "currency_id": 1, "start_date": "2026-01-01", "counter_ids": [4], "zone_prices": {"PEAK": "4.32", "HALF_PEAK": "3.24", "NIGHT": "2.16"}, "service_provider_id": 5, "account_number": "0123456789" }'

3. List expenses

curl https://landlordkeeper.com/api/v1/properties/5/expenses \ -H "Authorization: Bearer YOUR_TOKEN_HERE"

4. Update expense

curl -X PUT https://landlordkeeper.com/api/v1/properties/5/expenses/1 \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN_HERE" \ -d '{"unit_price": "4.5600", "charge_day": 10}'

5. Toggle active status

curl -X POST https://landlordkeeper.com/api/v1/properties/5/expenses/1/toggle-active \ -H "Authorization: Bearer YOUR_TOKEN_HERE"

6. Refresh all automatic charges

curl -X POST https://landlordkeeper.com/api/v1/properties/5/expenses/refresh-all \ -H "Authorization: Bearer YOUR_TOKEN_HERE"

7. Upload charge document

curl -X POST https://landlordkeeper.com/api/v1/properties/5/charges/12/documents \ -H "Authorization: Bearer YOUR_TOKEN_HERE" \ -F "file=@/path/to/invoice.pdf" \ -F "document_type_id=1" \ -F "comment=Electricity invoice February 2026"

8. List charge documents

curl https://landlordkeeper.com/api/v1/properties/5/charges/12/documents \ -H "Authorization: Bearer YOUR_TOKEN_HERE"

9. Delete charge document

curl -X DELETE https://landlordkeeper.com/api/v1/properties/5/charges/12/documents/1 \ -H "Authorization: Bearer YOUR_TOKEN_HERE"
Replace YOUR_TOKEN_HERE with the JWT token from the authentication step.

Expense Type Documents API

Base path: /api/properties/{propertyId}/expenses/{expenseId}/documents. Documents attached to an expense (such as contractor agreements or tariff schedules) are managed through these endpoints.

GET List Expense Documents

GET /api/v1/properties/{propertyId}/expenses/{expenseId}/documents

Retrieve all documents attached to an expense.

Response (200 OK):

{ "documents": [...], "total": 2 }

POST Upload Expense Document

POST /api/v1/properties/{propertyId}/expenses/{expenseId}/documents

Upload a new document to an expense (multipart/form-data).

Request Body (multipart/form-data):

file: (binary file) document_type_id: integer (required) comment: string (optional)

Response (201 Created):

{ "id": 3, "document": {...}, "comment": "..." }

GET Get Expense Document

GET /api/v1/properties/{propertyId}/expenses/{expenseId}/documents/{id}

Retrieve details of a specific expense document.

PUT Update Expense Document

PUT /api/v1/properties/{propertyId}/expenses/{expenseId}/documents/{id}

Update the comment of an expense document.

Request Body (application/json):

{ "comment": "Updated comment" }

DELETE Delete Expense Document

DELETE /api/v1/properties/{propertyId}/expenses/{expenseId}/documents/{id}

Delete an expense document and its associated file from S3.

Response (204 No Content):

(empty)
Commercial Use of the Property → Go to Commerce