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| Field | Type | Required | Description |
|---|---|---|---|
name | string ≤120 | yes | Building name. |
kind | string | no | apartment_block, maisonette, bungalow, single_unit, gated_community, hostel, commercial, mixed_use, godown |
estate | string ≤120 | no | Estate or neighbourhood. |
town | string ≤80 | no | Town. |
owner_name | string ≤120 | no | Landlord, 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| Field | Type | Required | Description |
|---|---|---|---|
label | string ≤16 | yes | Door label, e.g. B12. |
code | string ≤16 | no | Paybill account reference, unique in your organisation. Default: label. |
unit_type | string | no | bedsitter, single_room, studio, one_bedroom, two_bedroom, three_bedroom, four_bedroom_plus, maisonette, shop, office, godown, other. Default one_bedroom. |
market_rent_minor | money string | no | Monthly rent. Used as move-in default. |
bedrooms | integer | no | 0–20. |
floor_no | integer | no | −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:writeCreates the tenant (or reuses one with the same phone), the lease and the first bill, and marks the unit occupied.
| Field | Type | Required | Description |
|---|---|---|---|
tenant_phone | string | yes | Identifies the tenant. |
tenant_name | string ≤120 | no | Required when the phone is new to you. Ignored for an existing tenant. |
rent_minor | money string | no | Default: the unit's market_rent_minor. |
start_on | YYYY-MM-DD | no | Default: today. |
deposit_minor | money string | no | Default: one month's rent. |
billing_day | integer 1–31 | no | Day rent falls due. Default 5. |
charges | array ≤20 | no | Replaces the default bill (pro-rated rent + deposit). See below. |
charges[]
| Field | Type | Required | Description |
|---|---|---|---|
label | string ≤60 | yes | Shown on the bill. |
kind | string | yes | rent, deposit, service_charge, water, electricity, garbage, security, parking, other |
months | integer 0–12 | no | Charge as months of rent. |
amount_minor | money string | no | Fixed amount. Use this or months. |
refundable | boolean | no | Default 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:truemeans the tenant was sent their bill on WhatsApp;falsemeans 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 withouttenant_name, an invalid phone, or no rent on the request or the unit.
Get a tenant
GET
/tenants/{tenant_id}scope tenure:read200 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:writeRecord another number for a tenant, e.g. the phone they pay M-Pesa from. List them with GET /tenants/{tenant_id}/channels.
| Field | Type | Required | Description |
|---|---|---|---|
kind | string | yes | whatsapp, sms, mpesa, email, voice |
msisdn | string ≤20 | no | Phone number. Required unless kind is email. |
email | string | no | For kind email. |
is_primary | boolean | no | Make this the tenant's main number of this kind. |
confirmed_by_operator | boolean | no | You have checked the number is theirs. Marks it verified. |
label | string ≤60 | no | E.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"
}