Landlord Dashboard API
Landlord Dashboard API
The landlord API provides all data needed to power the landlord portal dashboard. It is exposed as a tRPC router under the landlord namespace and backed by the AgentOS (letmc.com) service layer.
All endpoints require an authenticated session (protectedProcedure). Inputs are validated with Zod.
Endpoints
landlord.status
Health check. Returns whether the AgentOS API is configured for the current agency context.
Input: none
landlord.insights
Returns aggregated KPIs for the landlord dashboard summary panel.
Input: { shortName: string, landlordId: string }
Response: LandlordInsights
{
totalProperties: number;
occupiedProperties: number;
vacantProperties: number;
occupancyRate: number; // 0–100, as a percentage
totalRentExpected: number;
totalArrearsAmount: number;
activeMaintenanceJobs: number;
completedMaintenanceJobs: number;
totalTenancies: number;
currentTenancies: number;
}
Partial failure resilience: If one underlying data source fails (e.g. arrears or maintenance), the remaining KPIs are still returned. Individual errors are captured for observability.
landlord.portfolio
Paginated list of properties in the landlord's portfolio.
Input:
{
shortName: string;
landlordId: string;
offset?: number; // default: 0
count?: number; // default: 20
}
Response: LandlordPortfolio
{
properties: LandlordProperty[];
totalCount: number;
}
LandlordProperty fields:
{
propertyId: string;
address: string; // Full address, assembled from parts
addressLine1: string | null;
addressLine2: string | null;
addressLine3: string | null;
addressLine4: string | null;
postcode: string | null;
propertyType: string | null;
bedrooms: number | null;
bathrooms: number | null;
receptions: number | null;
rentAmount: number | null;
rentFrequency: string | null;
isManaged: boolean;
isVacant: boolean;
branchId: string | null;
currentTenancyId: string | null;
imageUrl: string | null;
metadata: Record<string, unknown>;
}
landlord.propertyDetails
Full details for a single property, with optional block/development information.
Input:
{
shortName: string;
landlordId: string;
propertyId: string;
}
Response:
{
property: LandlordProperty | null;
block: PropertyBlock | null; // null if no block or block fetch fails
}
PropertyBlock fields:
{
blockId: string;
name: string;
address: string | null;
totalUnits: number | null;
managingAgent: string | null;
metadata: Record<string, unknown>;
}
Block info is fetched as a non-critical sub-request. If it fails,
blockisnulland the property record is still returned.
landlord.maintenanceJobs
Paginated list of maintenance jobs across the landlord's portfolio.
Input:
{
shortName: string;
landlordId: string;
offset?: number;
count?: number;
status?: string; // Optional status filter (e.g. "Active", "Completed")
}
Response:
{
jobs: MaintenanceJob[];
totalCount: number;
}
MaintenanceJob fields:
{
jobId: string;
propertyId: string;
propertyAddress: string | null;
title: string;
description: string | null;
status: string;
priority: string | null;
reportedDate: string | null;
completedDate: string | null;
estimatedCost: number | null;
actualCost: number | null;
contractorName: string | null;
category: string | null;
metadata: Record<string, unknown>;
}
landlord.maintenanceJobDetails
Full details for a single maintenance job, including any attached notes.
Input:
{
shortName: string;
landlordId: string;
jobId: string;
}
Response:
{
job: MaintenanceJob | null;
notes: JobNote[]; // Empty array if notes fetch fails
}
JobNote fields:
{
noteId: string;
jobId: string;
text: string;
createdAt: string | null;
createdBy: string | null;
type: string | null;
metadata: Record<string, unknown>;
}
landlord.arrears
All outstanding rent arrears entries across the landlord's portfolio.
Input:
{
shortName: string;
landlordId: string;
}
Response:
{
arrears: RentArrearsEntry[];
totalArrearsAmount: number;
}
RentArrearsEntry fields:
{
tenancyId: string;
propertyId: string | null;
propertyAddress: string | null;
tenantName: string | null;
arrearsAmount: number;
rentAmount: number | null;
lastPaymentDate: string | null;
daysBehind: number | null;
status: string | null;
metadata: Record<string, unknown>;
}
landlord.tenancies
Paginated list of tenancies (current and historical).
Input:
{
shortName: string;
landlordId: string;
offset?: number;
count?: number;
currentOnly?: boolean; // If true, returns only active tenancies
}
Response:
{
tenancies: LandlordTenancy[];
totalCount: number;
}
LandlordTenancy fields:
{
tenancyId: string;
propertyId: string;
propertyAddress: string | null;
tenantNames: string[];
startDate: string | null;
endDate: string | null;
rentAmount: number | null;
rentFrequency: string | null;
tenancyType: string | null;
status: string | null;
depositAmount: number | null;
isCurrent: boolean;
branchId: string | null;
metadata: Record<string, unknown>;
}
landlord.tenancyDetails
Full details for a single tenancy.
Input:
{
shortName: string;
landlordId: string;
tenancyId: string;
}
Response: LandlordTenancy | null
landlord.statements
Financial statement entries for the landlord, with optional date range filtering.
Input:
{
shortName: string;
landlordId: string;
dateFrom?: string; // ISO 8601 date string
dateTo?: string; // ISO 8601 date string
offset?: number;
count?: number;
}
Response: LandlordStatement
{
entries: StatementEntry[];
openingBalance: number | null;
closingBalance: number | null;
totalDebits: number | null;
totalCredits: number | null;
periodStart: string | null;
periodEnd: string | null;
}
StatementEntry fields:
{
entryId: string;
date: string | null;
description: string;
debit: number | null;
credit: number | null;
balance: number | null;
propertyAddress: string | null;
category: string | null;
metadata: Record<string, unknown>;
}
Error Handling
| Scenario | Behaviour |
|---|---|
| AgentOS returns 404 | Returns null or empty array — no error thrown |
| Non-critical sub-request fails (block info, job notes) | Returns null/[] for that field; parent response succeeds |
| AgentOS API not configured | landlord.status returns false; other endpoints throw LetmcNotConfiguredError |
| Other API errors | Captured via captureError() for observability; error propagated to caller |
Field Mapping
AgentOS API responses use PascalCase field names, some of which have alternative names depending on the endpoint version. The service layer normalises all fields to camelCase and handles fallbacks automatically. Examples:
| AgentOS field(s) | Mapped to |
|---|---|
RentAmount | Rent | rentAmount |
IsManaged | Managed | isManaged |
IsVacant | Vacant | isVacant |
OID | PropertyID | propertyId |
MainPhoto | PhotoURL | imageUrl |
JobTitle | Title | title |
ReportedDate | DateReported | reportedDate |
TenancyStart | StartDate | startDate |
Unmapped fields are preserved in the metadata object on each response type.