Home Property Setup Counters (Meters)

7 Counters (Meters)

Set up utility meters, track readings, manage tariff zones and measurement units. The system monitors timely submission of readings by tenants.

Why Counters Are Critical

Most of the key utility expenses — electricity, gas, water, heating — are calculated based on meter readings. Accurate and timely readings ensure correct billing, prevent disputes between landlords and tenants, and provide transparent consumption data.

What counters enable

  • Automatic expense charge calculation based on consumption
  • Multi-zone tariff support (day/night, peak/off-peak)
  • Consumption tracking per period with daily averages
  • Monitoring timely reading submissions by tenants
  • Adjustment mechanism for meter replacements or corrections

Expense Types with Counters

Five expense types support meter-based tracking. Each has a default measurement unit that can be overridden per counter.

ELECTRICITY kWh — Supports multi-zone tariffs
GAS m3 — Natural gas consumption
COLD_WATER m3 — Cold water supply
HOT_WATER m3 — Hot water supply
HEATING Gcal — Central or individual heating

Counter Fields

When creating a counter, you specify the expense type, serial number, tariff type, and the initial reading with its date.

expense_type_id Expense type (required)
serial_number Factory serial number, unique per property
tariff_type SINGLE_ZONE, TWO_ZONE, or THREE_ZONE
initial_value Starting reading (for single-zone)
initial_value_date Date of initial reading (required)
initial_value_t1/t2/t3 Zone values for multi-zone tariffs

Tariff Zones

Electricity counters support multi-zone tariffs. For two-zone and three-zone counters, you provide separate readings per zone — the total is calculated automatically.

Tariff Type Zones Fields Description
SINGLE_ZONE 1 value Standard single-reading meter
TWO_ZONE 2 value_t1, value_t2 T1 = Day, T2 = Night (coeff. 50%)
THREE_ZONE 3 value_t1, value_t2, value_t3 T1 = Peak (150%), T2 = Half-peak (100%), T3 = Night (40%)
Automatic sum calculation
For multi-zone counters, the total value is always calculated as the sum of zone values: value = value_t1 + value_t2 [+ value_t3]. You do not need to provide the total — it is computed automatically.

Types of Readings

The system supports four types of counter readings, each serving a specific purpose in the billing workflow.

Initial
Set when counter is created. One per counter. The only type that can be edited.
Regular
Normal meter reading. Must be >= previous value. Can be linked to expense charges.
Adjustment
Correction after meter replacement or error. Stored as negative delta. Cannot be linked to charges.
Calculated
Auto-interpolated by the system for intermediate billing periods. Can be linked to charges.
Validation rules
Only one reading per day per counter. New readings must be >= current value (otherwise auto-detected as adjustment). If a reading is lower than current, the system automatically creates an adjustment entry.

Timeliness Tracking

The system monitors whether tenants submit their readings on time. You can set a reading day for each counter, and the system tracks compliance.

Tracking features

  • Configurable reading day (1-31) per counter
  • Automatic interpolation for missed periods
  • Consumption statistics: total, per period, daily average
  • Counter photos and reading photos for verification

Documents

You can attach documents (photos, PDFs) to counters and their readings. Documents are stored securely in S3 and accessed via signed URLs.

Counter Documents
Attach photos of the meter, installation certificates, calibration documents. Upload on the counter detail page.
Reading Documents
Attach photos of the meter display when adding or viewing readings. Upload when creating a reading or on the counter detail page.
File limits
Maximum file size: 20 MB. Supported formats: JPEG, PNG, GIF, WebP, HEIC, PDF. Multiple files can be attached to each counter or reading.

Documents API

POST   /properties/{propertyId}/counters/{id}/documents/upload                           # Upload counter document
POST   /properties/{propertyId}/counters/{id}/documents/{documentId}/delete              # Delete counter document
POST   /properties/{propertyId}/counters/{id}/values/{valueId}/documents/upload          # Upload reading document
POST   /properties/{propertyId}/counters/{id}/values/{valueId}/documents/{documentId}/delete  # Delete reading document

All uploaded documents appear in the Documents section (/my/documents) and can be filtered by type: "Counter" or "Counter Reading".

How to Add a Counter

Navigate to any property, then click "Counters" in the property menu. From there you can add new counters and submit readings.

Property page > Property menu > Counters > Add counter

For detailed documentation, see the Counters section in the Library.

For Developers: REST API

All counter and reading operations are available via the REST API. Use the demo credentials to get a JWT token first.

GET List Counters

Get all counters for a property with their current values and status.

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

Response (200 OK):

{ "counters": [ { "id": 4, "expense_type": {"id": 3, "code": "ELECTRICITY"}, "serial_number": "NIK-2281-073915", "tariff_type": "THREE_ZONE", "zone_count": 3, "current_value": "5729.000", "documents_count": 2, "is_active": true, "created_at": "2026-02-20T15:45:43+00:00" }, { "id": 5, "expense_type": {"id": 10, "code": "GAS"}, "serial_number": "G4-BK-2023-041822", "tariff_type": "SINGLE_ZONE", "zone_count": 1, "current_value": "1612.400", "documents_count": 0, "is_active": true, "created_at": "2026-02-20T15:45:44+00:00" } ], "total": 5 }

POST Create Counter

Create a new counter with initial reading. Specify zone values for multi-zone tariffs — the total is calculated automatically.

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

Headers:

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

Single-zone example:

{ "expense_type_id": 10, "serial_number": "G4-BK-2023-041822", "tariff_type": "SINGLE_ZONE", "initial_value": "1245.500", "initial_value_date": "2025-09-01" }

Three-zone example (electricity):

{ "expense_type_id": 3, "serial_number": "EA-2024-073915", "tariff_type": "THREE_ZONE", "initial_value_t1": "2100.000", "initial_value_t2": "1500.000", "initial_value_t3": "850.000", "initial_value_date": "2025-09-01" }
For multi-zone tariffs, provide initial_value_t1, initial_value_t2 (and t3 for three-zone). The total initial_value is computed as their sum. Serial numbers must be unique within the property.

Response (201 Created):

{ "id": 1, "expense_type": {"id": 3, "code": "ELECTRICITY"}, "serial_number": "EA-2024-073915", "tariff_type": "THREE_ZONE", "zone_count": 3, "initial_value": "4450.000", "initial_value_t1": "2100.000", "initial_value_t2": "1500.000", "initial_value_t3": "850.000", "initial_value_date": "2025-09-01", "current_value": "4450.000", "documents_count": 0, "is_active": true, "can_delete": true, "created_at": "2025-09-01T00:00:00+00:00" }

GET Counter Details

Get full counter details including all readings sorted by date (newest first).

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

Response (200 OK):

{ "id": 4, "expense_type": {"id": 3, "code": "ELECTRICITY"}, "serial_number": "NIK-2281-073915", "tariff_type": "THREE_ZONE", "zone_count": 3, "initial_value": "4450.000", "initial_value_t1": "2100.000", "initial_value_t2": "1500.000", "initial_value_t3": "850.000", "initial_value_date": "2025-08-01", "current_value": "5729.000", "documents_count": 2, "is_active": true, "has_linked_charges": false, "can_delete": true, "values": [ { "id": 8, "value": "5729.000", "value_t1": "2752.100", "value_t2": "1851.300", "value_t3": "1125.600", "date": "2025-12-01", "is_initial": false, "is_adjustment": false, "documents_count": 0, "has_linked_charge": false, "can_edit": false, "can_delete": true }, { "id": 5, "value": "4450.000", "value_t1": "2100.000", "value_t2": "1500.000", "value_t3": "850.000", "date": "2025-08-01", "is_initial": true, "is_adjustment": false, "documents_count": 0, "has_linked_charge": false, "can_edit": true, "can_delete": true } ] }

PUT Update Counter

Update counter metadata (serial number, expense type, tariff type). Only provided fields are updated.

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

Updatable fields:

expense_type_id integer
serial_number string (max 100)
tariff_type SINGLE_ZONE | TWO_ZONE | THREE_ZONE

Request Body:

{ "serial_number": "EA-2024-073915-NEW" }

Response (200 OK):

{ "id": 4, "expense_type": {"id": 3, "code": "ELECTRICITY"}, "serial_number": "EA-2024-073915-NEW", "tariff_type": "THREE_ZONE", "zone_count": 3, "current_value": "5729.000", "documents_count": 2, "is_active": true, "has_linked_charges": false, "can_delete": true, "created_at": "2026-02-20T15:45:43+00:00" }

DELETE Delete Counter

Delete a counter and all its readings. Returns 204 on success.

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

A counter cannot be deleted if it has readings linked to expense charges. In this case, deactivate it instead using the toggle-active endpoint.

POST Toggle Active/Inactive

Toggle counter active status. Inactive counters are excluded from automatic expense charge calculations.

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

Response (200 OK):

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

POST Add Reading

Submit a new meter reading. For single-zone counters, provide "value". For multi-zone, provide zone values (value_t1, value_t2, value_t3) — the total is computed automatically.

POST /api/v1/properties/{propertyId}/counters/{id}/values

Single-zone reading:

{ "value": "1587.340", "date": "2025-11-01" }

Multi-zone reading:

{ "value_t1": "2280.000", "value_t2": "1620.000", "value_t3": "925.700", "date": "2025-11-01" }
If the submitted value is lower than the current reading, the system automatically creates an adjustment entry (values stored as negative delta). Only one reading per day per counter is allowed.

Response (201 Created):

{ "id": 5, "value": "4825.700", "value_t1": "2280.000", "value_t2": "1620.000", "value_t3": "925.700", "date": "2025-11-01", "is_initial": false, "is_adjustment": false, "documents_count": 0, "has_linked_charge": false }

PUT Update Reading

Update an existing meter reading. Only initial readings can be edited via the web interface; the API allows updating any reading.

PUT /api/v1/properties/{propertyId}/counters/{id}/values/{valueId}

Request Body:

{ "value": "1590.000", "date": "2025-11-02" }
Only initial readings (is_initial=true) can be edited via the web interface. Regular readings can only be corrected using adjustments. The API does not enforce minimum value validation on updates.

Response (200 OK):

{ "id": 5, "value": "1590.000", "value_t1": null, "value_t2": null, "value_t3": null, "date": "2025-11-02", "is_initial": false, "is_adjustment": false, "documents_count": 0, "has_linked_charge": false, "can_edit": true, "can_delete": true }

DELETE Delete Reading

Delete a counter reading. Returns 204 on success.

DELETE /api/v1/properties/{propertyId}/counters/{id}/values/{valueId}

Readings linked to expense charges cannot be deleted. The linked charge must be removed first.

POST Counter Documents

Attach photos of the meter, installation certificates, calibration documents. Upload on the counter detail page.

POST /properties/{propertyId}/counters/{id}/documents/upload

Headers:

Content-Type: multipart/form-data

Request Body (form-data):

documents[] — file (jpg, jpeg, png, gif, webp, heic, pdf), max 20 MB comment — string (optional)
Maximum file size: 20 MB. Supported formats: JPEG, PNG, GIF, WebP, HEIC, PDF. Multiple files can be attached to each counter or reading.

Response (200 OK):

{ "success": true, "uploaded": 1 }

DELETE /properties/{propertyId}/counters/{id}/documents/{documentId}/delete

{ "success": true }

POST Reading Documents

Attach photos of the meter display when adding or viewing readings. Upload when creating a reading or on the counter detail page.

POST /properties/{propertyId}/counters/{id}/values/{valueId}/documents/upload

Request Body (form-data):

documents[] — file (jpg, jpeg, png, gif, webp, heic, pdf), max 20 MB comment — string (optional)

Response (200 OK):

{ "success": true, "uploaded": 2 }

DELETE /properties/{propertyId}/counters/{id}/values/{valueId}/documents/{documentId}/delete

{ "success": true }

cURL Examples

1. Create Counter (Single-zone example)

curl -X POST https://landlordkeeper.com/api/v1/properties/5/counters \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN_HERE" \ -d '{ "expense_type_id": 10, "serial_number": "G4-BK-2023-041822", "tariff_type": "SINGLE_ZONE", "initial_value": "1245.500", "initial_value_date": "2025-09-01" }'

2. Create Counter (Three-zone example (electricity))

curl -X POST https://landlordkeeper.com/api/v1/properties/5/counters \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN_HERE" \ -d '{ "expense_type_id": 3, "serial_number": "EA-2024-073915", "tariff_type": "THREE_ZONE", "initial_value_t1": "2100.000", "initial_value_t2": "1500.000", "initial_value_t3": "850.000", "initial_value_date": "2025-09-01" }'

3. Add Reading

curl -X POST https://landlordkeeper.com/api/v1/properties/5/counters/1/values \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN_HERE" \ -d '{ "value_t1": "2280.000", "value_t2": "1620.000", "value_t3": "925.700", "date": "2025-11-01" }'

4. List Counters

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

5. Counter Documents

curl -X POST https://landlordkeeper.com/properties/5/counters/4/documents/upload \ -H "Cookie: PHPSESSID=your_session" \ -F "documents[][email protected]" \ -F "comment=Meter photo"

6. Reading Documents

curl -X POST https://landlordkeeper.com/properties/5/counters/4/values/8/documents/upload \ -H "Cookie: PHPSESSID=your_session" \ -F "documents[]=@reading_jan.jpg" \ -F "documents[]=@reading_jan_2.pdf"
Replace YOUR_TOKEN_HERE with the JWT token from the authentication step.