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.
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.
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.
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.
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.
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
- Go to Properties → [Property] → Charges
- Click the blue "Add Charge" button (appears only if you have expenses requiring manual charges)
- Select the expense from the modal window (shows expense type, provider, and calculation method)
- 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.
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
}
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):
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)
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"
}
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
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)
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"
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)