Connecting Yardi and AppFolio to Real-Time NOI Analytics

API integration diagram connecting Yardi and AppFolio to NOI analytics

Property management systems like Yardi Voyager and AppFolio hold most of the data that matters for daily NOI tracking. The challenge has never been that the data doesn't exist — it's that getting it out in a reliable, normalized form, every day, at a level of detail useful for variance analysis, requires more engineering work than most mid-size operators realize going in.

This post covers what a working integration between Yardi or AppFolio and a NOI analytics layer actually looks like: the API surface, the field mapping decisions that matter, and the normalization patterns that separate a usable daily NOI position from a pile of raw exports.

The Two API Models

Yardi Voyager and AppFolio take meaningfully different approaches to their integration surfaces, and those differences affect what you can build on top of them.

Yardi Voyager exposes data primarily through its SOAP-based webservices layer (Yardi Web Services) and, for clients on more recent versions, through the REST-based Yardi Elevate APIs. The webservices layer has been around for over a decade and is well-understood, but it requires a Yardi-authorized integration agreement and is schema-heavy — the response structure assumes you know which Yardi modules are active and which fields are populated in your client's configuration. Voyager's data model is normalized across many related entities: ResidentActivity, Charges, LeaseTerms, WorkOrders, and GLTransactions are separate endpoints with their own pagination and filter parameters.

AppFolio provides a REST API that is, by comparison, more accessible to smaller engineering teams. Authentication is via API key pairs assigned at the property management company level. The core endpoints for NOI analytics are /leases, /units, /owner_reports, and /work_orders. The response model is JSON with standard pagination headers. One limitation: AppFolio's API does not expose granular GL transaction data with the same fidelity as a full Yardi implementation. For clients with complex accounting configurations, that means some variance categories require derivation from multiple endpoints rather than a direct ledger pull.

Field Mapping That Actually Matters

The field mapping layer is where most integrations either work well or break down silently. The conceptually simple question — what is the current effective rent for this unit? — requires careful handling of at least four layers:

  • Face rent — the contracted rent per the lease agreement
  • Concession amortization — a one-month free concession on a 12-month lease reduces effective monthly rent by 1/12th of one month's face rent, not by the full face rent in the concession month
  • Recurring charges — pet fees, parking, storage, utility billing, and other line items that are part of total household revenue but separate from base rent
  • Delinquent balance carryover — a resident who owes $800 from last month is not, from a collections standpoint, paying their current month's rent in full even if a payment was received

In Yardi, effective rent is assembled from the Charges table joined to LeaseTerm and then adjusted for active concessions in the Concession table. In AppFolio, it requires combining the /leases response with the /lease_financials endpoint and applying concession logic client-side. The normalization step — outputting a single effective_rent_monthly value that means the same thing regardless of which system it came from — is not trivial to get right the first time.

Vacancy Loss Calculation

Vacancy loss is conceptually the most straightforward NOI driver to calculate, but it requires a reliable unit-level occupancy time series to do correctly. The formula is simple: for each vacant unit, multiply the budgeted rent for that unit by the number of days vacant divided by days in the month.

The practical challenge is that Yardi and AppFolio both have edge cases that distort a naive calculation:

  • Units in notice status are technically occupied but revenue for the upcoming vacancy should be flagged as at-risk
  • Units in make-ready status carry different implications depending on whether the make-ready is running on schedule or delayed
  • Down units (offline for renovation) should be excluded from vacancy rate calculations and tracked separately against a renovation timeline

A daily integration that pulls unit status changes as they occur — rather than a nightly full extract — produces a more accurate intra-month vacancy loss accrual. Yardi's ResidentActivity endpoint supports incremental pulls by timestamp. AppFolio's /units endpoint does not support delta queries natively, which means the integration layer needs to maintain its own prior-state snapshot and diff it against the current pull.

Delinquency Data and Aging Buckets

For delinquency tracking, the goal is a daily receivables aging view: how much is owed, by how many residents, bucketed by how long it's been outstanding. Standard aging buckets for multifamily are 1-5 days, 6-15 days, 16-30 days, and 30+ days, though operators with active legal processes often want a separate bucket for amounts currently in eviction proceedings.

Yardi's Charges and Payments tables provide the raw ledger entries needed to reconstruct aging. AppFolio's /delinquencies endpoint returns a pre-computed aging summary, which is convenient but less flexible — if an operator wants custom bucket definitions, they need to pull raw receivables from /owner_reports and recompute.

Maintenance Cost Normalization

Work order data from Yardi and AppFolio includes estimated costs, actual costs when invoices are attached, and categorical codes (HVAC, plumbing, appliance, grounds, etc.). Normalizing this to a per-unit monthly maintenance spend figure requires summing closed work orders by property by calendar month, then dividing by occupied unit count.

One important distinction: Yardi work orders are connected to the property management module and may or may not be reconciled to the accounting GL depending on whether the client uses Yardi's accounts payable module. AppFolio work orders are similarly sometimes tracked in a separate maintenance coordination system and only partially reflected in the financial data. An integration that relies only on the GL will undercount maintenance spend mid-month; one that reads work orders directly will see costs before they are invoiced, which requires a clear policy on how to handle estimates vs. actuals in the daily NOI position.

Building Reliable Daily Pipelines

The operational requirement for a daily NOI analytics product is that the pipeline runs successfully every morning before business hours. That means the integration layer needs to handle API rate limits, authentication token refresh, partial response failures, and schema changes in the underlying system without manual intervention.

Yardi's webservices layer has strict rate limiting at the database connection level, which varies by client configuration. AppFolio enforces per-API-key rate limits documented in their developer portal. Both require exponential backoff and intelligent retry logic, particularly for large portfolios where a full nightly pull touches hundreds of endpoints.

At Rentnoi, the integration layer validates each daily pull against a set of completeness checks — unit count consistency, revenue total reconciliation against prior-day figures — before the data is promoted to the analytics model. A pull that fails validation triggers an alert before it affects the morning dashboard, rather than silently distorting the numbers that operators are relying on to make decisions.

If you're evaluating how to connect your Yardi or AppFolio instance to daily NOI analytics, reach out to our team. We can walk through what a Rentnoi integration looks like against your specific system configuration.