Faster Tenancy Loading: How We Fixed an N+1 API Call Problem in AgentOS
Faster Tenancy Loading: How We Fixed an N+1 API Call Problem in AgentOS
Release: v1.0.48
In v1.0.48 we resolved a significant performance bottleneck in the AgentOS integration that caused tenancy data to load far more slowly than necessary. This post explains what the problem was, why it happened, and what we did to fix it.
The Problem: N+1 API Calls
When fetching tenancies for a landlord, the listTenancies procedure in src/lib/routers/agentos.ts followed this pattern:
- Call
agentosClient.getProperties()to retrieve all properties for the landlord. - Loop over each property and call
agentosClient.getTenancies(shortName, landlordOID, prop.id)— one request per property.
This is the classic N+1 query problem, applied to external HTTP calls rather than a database. For a landlord with 10 properties, the application was making 11 sequential HTTP requests to the AgentOS API before it could return a result.
// Previous behaviour (simplified)
const properties = await agentosClient.getProperties(); // 1 request
for (const prop of properties) {
// 1 request per property — sequential, blocking
const tenancies = await agentosClient.getTenancies(shortName, landlordOID, prop.id);
results.push(...tenancies);
}
Because these calls were sequential, every network round-trip had to complete before the next one started. At typical API latencies, this created a 10–50× slowdown relative to a single bulk request.
The Fix: Bulk Fetch + Client-Side Filtering
The AgentOS API already supports fetching all tenancies for a landlord in one call — agentosClient.getTenancies() accepts an optional propertyId, meaning it can be called without one to return everything.
The fix has two parts:
1. Single Bulk Request (No propertyId Filter)
When no propertyId is requested by the caller, listTenancies now makes a single call to /tenancies and retrieves all tenancies at once. If the caller does supply a propertyId, filtering is applied client-side after the bulk fetch — no extra API call required.
// New behaviour (simplified)
const allTenancies = await agentosClient.getTenancies(shortName, landlordOID);
// ↑ no propertyId = bulk fetch
// Filter client-side if the caller requested a specific property
const result = propertyId
? allTenancies.filter(t => t.propertyId === propertyId)
: allTenancies;
2. Parallel Requests with Promise.all()
For landlords who have multiple AgentOS account links, we still need one request per linked account. The previous for...of loop processed these sequentially. We now use Promise.all() so all account requests are dispatched simultaneously and resolved together.
// New behaviour for multiple landlord links
const results = await Promise.all(
landlordLinks.map(link =>
agentosClient.getTenancies(link.shortName, link.landlordOID)
)
);
Impact
| Scenario | Before | After |
|---|---|---|
| 1 landlord link, 10 properties | 11 sequential requests | 1 request |
| 1 landlord link, 50 properties | 51 sequential requests | 1 request |
| 3 landlord links, 10 properties each | 33 sequential requests | 3 parallel requests |
Estimated speedup: 5–20× faster tenancy loading, depending on the number of properties and AgentOS API latency.
Why This Matters for Landlords
Tenancy data underpins much of the platform — transaction imports, quarterly reporting, and Making Tax Digital submissions all depend on having an accurate, up-to-date view of which tenancies are active across a portfolio. Slow tenancy loading directly delays these downstream operations. This fix ensures the integration scales cleanly whether a landlord has 1 property or 100.
Affected File
src/lib/routers/agentos.ts—listTenanciesprocedure