Help topics

API reference

Tenants

Add one tenant at a time: create the property and unit, then move the tenant in.

For many tenants at once, use bulk import instead.

Create a property

POST/propertiesscope tenure:write
FieldTypeRequiredDescription
namestring ≤120yesBuilding name.
kindstringnoapartment_block, maisonette, bungalow, single_unit, gated_community, hostel, commercial, mixed_use, godown
estatestring ≤120noEstate or neighbourhood.
townstring ≤80noTown.
owner_namestring ≤120noLandlord, when you manage for someone else.
POST /properties
{ "name": "Riverside Court", "estate": "Kilimani", "town": "Nairobi" }

201 Created
{ "id": "5b1c…", "public_id": "prp_01J9…", "name": "Riverside Court" }

List existing properties with GET /properties.

Create a unit

POST/properties/{property_id}/unitsscope tenure:write
FieldTypeRequiredDescription
labelstring ≤16yesDoor label, e.g. B12.
codestring ≤16noPaybill account reference, unique in your organisation. Default: label.
unit_typestringnobedsitter, single_room, studio, one_bedroom, two_bedroom, three_bedroom, four_bedroom_plus, maisonette, shop, office, godown, other. Default one_bedroom.
market_rent_minormoney stringnoMonthly rent. Used as move-in default.
bedroomsintegerno0–20.
floor_nointegerno−5 to 200.
POST /properties/5b1c…/units
{ "label": "B12", "unit_type": "two_bedroom", "market_rent_minor": "2500000" }

201 Created
{ "id": "9e42…", "public_id": "unt_01J9…", "label": "B12", "code": "B12" }

A duplicate code returns 409 conflict.

Move a tenant in

POST/units/{unit_id}/move-inscope tenure:write

Creates the tenant (or reuses one with the same phone), the lease and the first bill, and marks the unit occupied.

FieldTypeRequiredDescription
tenant_phonestringyesIdentifies the tenant.
tenant_namestring ≤120noRequired when the phone is new to you. Ignored for an existing tenant.
rent_minormoney stringnoDefault: the unit's market_rent_minor.
start_onYYYY-MM-DDnoDefault: today.
deposit_minormoney stringnoDefault: one month's rent.
billing_dayinteger 1–31noDay rent falls due. Default 5.
chargesarray ≤20noReplaces the default bill (pro-rated rent + deposit). See below.

charges[]

FieldTypeRequiredDescription
labelstring ≤60yesShown on the bill.
kindstringyesrent, deposit, service_charge, water, electricity, garbage, security, parking, other
monthsinteger 0–12noCharge as months of rent.
amount_minormoney stringnoFixed amount. Use this or months.
refundablebooleannoDefault false.

Example

POST /units/9e42…/move-in
Idempotency-Key: 7f3a2c1e-…
{
  "tenant_phone": "0722145880",
  "tenant_name": "Faith Wafula",
  "start_on": "2026-10-01",
  "billing_day": 1
}

201 Created
{
  "leaseId": "c07d…",
  "leasePublicId": "lse_01J9…",
  "tenantId": "a81f…",
  "unitLabel": "B12",
  "unitCode": "B12",
  "propertyName": "Riverside Court",
  "startOn": "2026-10-01",
  "endOn": "2027-10-01",
  "billingDay": 1,
  "rentFormatted": "KES 25,000",
  "depositFormatted": "KES 25,000",
  "totalFormatted": "KES 50,000",
  "billSent": true,
  "firstInvoiceOn": "2026-10-01"
}
  • billSent: true means the tenant was sent their bill on WhatsApp; false means a welcome message only.
  • 409 conflict: the unit already has a tenant. A retry after a success also returns this; treat it as done.
  • 422 unprocessable: new phone without tenant_name, an invalid phone, or no rent on the request or the unit.

Get a tenant

GET/tenants/{tenant_id}scope tenure:read
200 OK
{
  "tenant": {
    "id": "a81f…", "public_id": "tnt_01J9…", "full_name": "Faith Wafula",
    "phone": "+254722145880", "alt_phone": null, "email": null,
    "id_number": null, "status": "active", …
  },
  "leases": [{
    "id": "c07d…", "status": "active", "start_on": "2026-10-01",
    "rent_minor": "2500000", "deposit_minor": "2500000", "balance_minor": "5000000",
    "billing_day": 1, "unit_label": "B12", "unit_code": "B12",
    "property_name": "Riverside Court", …
  }],
  "payments": [],
  "channels": [{ "kind": "whatsapp", "msisdn": "+254722145880", "is_primary": true, … }]
}

payments holds the latest 50. Unknown id: 404 not_found.

Add a contact number

POST/tenants/{tenant_id}/channelsscope tenure:write

Record another number for a tenant, e.g. the phone they pay M-Pesa from. List them with GET /tenants/{tenant_id}/channels.

FieldTypeRequiredDescription
kindstringyeswhatsapp, sms, mpesa, email, voice
msisdnstring ≤20noPhone number. Required unless kind is email.
emailstringnoFor kind email.
is_primarybooleannoMake this the tenant's main number of this kind.
confirmed_by_operatorbooleannoYou have checked the number is theirs. Marks it verified.
labelstring ≤60noE.g. “Husband's phone”.
POST /tenants/a81f…/channels
{ "kind": "mpesa", "msisdn": "0733902471", "confirmed_by_operator": true }

201 Created
{
  "id": "e5b0…", "publicId": "tch_01J9…", "kind": "mpesa",
  "msisdn": "+254733902471", "email": null, "isPrimary": false,
  "verified": true, "verifiedAt": "2026-09-25T09:14:03Z", "verificationMethod": "operator",
  "source": "operator", "optedOut": false, "label": null, "masked": "…2471"
}