AppFolio investor reporting automation works by connecting AppFolio's REST API to a scheduled Google Apps Script that fetches weekly property data, passes it through an AI summarizer, and emails formatted meeting notes directly to each investor. A fully operational system can generate notes for 10 or more weekly meeting types across multiple investor portfolios without any manual input.
If you are a property manager running regular investor meetings, this guide covers how the system is built, what it takes to set it up, and where most implementations get complicated.
The problem it solves
Property managers running multiple investor relationships typically spend 3 to 5 hours a week writing meeting prep notes. For each recurring meeting -- leasing reviews, maintenance walkthroughs, asset management check-ins -- someone has to pull data from AppFolio, figure out which investors it applies to, write a readable summary, and send it.
Multiply that across 12 recurring weekly meetings and three investor entities and you have 36 individual notes per week. Every week. With no tooling to help.
The work is not hard. It is just relentlessly manual. The same reports, the same structure, the same formatting decisions, repeated until someone builds a system to handle it.
How the automation works

The architecture has five components working together:
-
AppFolio REST API -- the data source. AppFolio exposes a v1 REST API on Property Manager Plus and higher plans. It covers most operational reports: rent roll, vacancy, work orders, guest cards, delinquency, unit turn detail, gross potential rent, income statement, and balance sheet.
-
Google Apps Script -- the automation engine. A container-bound script attached to a Google Sheet handles fetching, processing, and sending. A time-based trigger fires every Monday morning.
-
Google Sheets -- configuration and logging. A structured spreadsheet holds your investor list (with their associated property IDs or property groups), your meeting schedule, and a log of every note generated.
-
An AI summarization layer -- the conversion step. Raw API data is structured but not readable. A GPT prompt converts the JSON into a plain-language meeting note, grouped by property, that an investor can actually read in two minutes.
-
Gmail delivery -- per-investor output. Each investor gets exactly the note relevant to their portfolio. No manual routing.
Step-by-step: how to build it
Step 1: Enable AppFolio API access
AppFolio API access is managed at the account level. Log into your AppFolio portal, go to Settings, and look for the API section. You will need to generate a client ID and client secret. AppFolio uses HTTP Basic Auth on all v1 API requests.
The base URL follows the pattern https://yoursubdomain.appfolio.com/api/v1/. Your subdomain is the one you use to log in.
Step 2: Set up the Google Sheet
Create a new Google Sheet with five tabs: Config, Investors, Meetings, Property Groups, and Log.
Config holds global settings: your AppFolio subdomain, API credentials (stored as Script Properties, not in cells), a Drive folder ID for archiving notes, and sender display name.
Investors holds one row per investor: name, email, and either a list of AppFolio property IDs (comma-separated) or a Property Group name.
Meetings holds one row per recurring meeting: name, day of week, the AppFolio report IDs to pull (comma-separated), and a frequency column (weekly or monthly).
Property Groups maps group names to property ID lists -- useful when investors own a named portfolio rather than a flat ID list.
Log is written automatically on every run. Each row records the meeting name, investor, week label, status (OK, PARTIAL_OK, or ERROR), and any error messages.
Step 3: Write the AppFolio fetch function
In Apps Script, write a function that accepts a report type and an array of property IDs, constructs the API URL, and returns the parsed JSON.
function fetchAppFolioReport(reportId, propertyIds) {
const base = `https://${getConfig().subdomain}.appfolio.com/api/v1/reports/${reportId}.json`;
const params = propertyIds.map(id => `property_ids[]=${id}`).join("&");
const url = params ? `${base}?${params}` : base;
const options = {
headers: { Authorization: "Basic " + Utilities.base64Encode(
getConfig().clientId + ":" + getConfig().clientSecret
)}
};
const resp = UrlFetchApp.fetch(url, options);
return JSON.parse(resp.getContentText()).results;
}This handles the authenticated request and returns the results array. You will want to add retry logic and error capture around it -- AppFolio occasionally returns 429s under load.
Step 4: Map investors to property IDs
Read the Investors tab, resolve Property Group names to their ID arrays from the Property Groups tab, and build a per-investor map: { investorName: [propertyId1, propertyId2, ...] }.
This mapping drives the fan-out. For each meeting, you loop over investors, fetch the reports filtered to their property IDs, and generate one note per investor.
Step 5: Build the AI summarization step
Pass the raw report data to the OpenAI API with a structured prompt. The prompt should specify the meeting type, instruct the model to write in past tense as a recap, and tell it to group observations by property.
function summarizeReport(meetingName, reportData) {
const payload = {
model: "gpt-4o-mini",
messages: [{
role: "system",
content: "You write concise weekly meeting notes for a real estate fund manager. Write in past tense. Be factual. Do not invent dollar amounts or tenant names not present in the data."
}, {
role: "user",
content: `Meeting: ${meetingName}\n\nData:\n${JSON.stringify(reportData)}`
}]
};
// ... fetch call to OpenAI API
}At current token rates, a full weekly run across 12 meetings costs under $0.15 in API fees.
Step 6: Set the weekly trigger
In Apps Script, go to Triggers and create a time-based trigger on your main run function. Set it to fire weekly on Monday mornings, in the timezone your investors expect.
One important note: the user who installs the trigger owns it. The script runs as that user and sends email from their Gmail account. If you want a display name like "Iron Age Reporting" to appear as the sender, pass a name parameter to GmailApp.sendEmail. A true send-as alias requires that the alias be configured in Gmail settings first.
Where it gets complicated
The six steps above are the skeleton. Here is what adds the real development hours.
Property group resolution. AppFolio does not expose a property groups API endpoint. If your investors are organized into named groups (which is the right approach for multi-entity funds), you need to maintain that mapping manually in your Sheet and keep it in sync as properties move between groups.
Fund-wide reports break per-investor fan-out. AppFolio's balance_sheet and income_statement endpoints do not support property ID filtering. Passing property_ids[] parameters is accepted but silently ignored -- the API returns fund-wide data regardless. Any meeting that pulls these reports cannot produce a per-investor note without post-processing. You have two options: send a consolidated fund-wide note to one recipient, or switch to filterable reports like rent_roll and owner_statements for investor-level breakdowns.
Error handling at the report level, not the meeting level. If a meeting pulls four report types and one returns a 400, a naive implementation throws on the first failure and generates nothing. A production system needs per-report try/catch so that partial data still produces a PARTIAL_OK note rather than a silent failure.
Monthly vs weekly frequency. Some meetings (delinquency reviews, for example) should only run on the first Monday of the month. A simple date check on getDate() <= 7 works, but you need this logic in place before adding monthly meetings or they will fire every week.
Trigger ownership for shared Sheets. If the Sheet is owned by one Google account but the trigger is installed under another, future credential issues can silently break the weekly run. Establish clear ownership before handing off to a client.
Real-world results
A real estate fund manager running this system across three investor entities and 12 weekly meeting types eliminated roughly 4 hours of manual note-writing per week. The system generates 30 to 36 notes every Monday morning -- one per investor per applicable meeting -- each tailored to that investor's specific property portfolio.
The setup took about a week of development time, including the property group resolution layer, per-report error handling, and the per-investor fan-out fix for fund-wide meetings. The Google Sheet configuration interface means the client can add investors, change meeting schedules, and adjust report mappings without touching the code.
You can see the full case study in our investor documentation automation case study.
Frequently asked questions
Does AppFolio have an official API?
Yes. AppFolio's v1 REST API is available on Property Manager Plus and higher subscription plans. Access is enabled through the AppFolio admin settings and uses HTTP Basic Auth with a client ID and secret.
Which AppFolio reports can you pull via the API?
The most useful for investor reporting are rent_roll, guest_cards, unit_turn_detail, work_order, delinquency, gross_potential_rent_enhanced, owner_statements, balance_sheet, and income_statement. Most support property ID filtering. Balance sheet and income statement do not.
Can you filter AppFolio reports by investor or property group?
For most reports you can pass property_ids[] parameters to filter by specific properties. For balance_sheet and income_statement, AppFolio's v1 API ignores property filters and always returns fund-wide data. Per-investor financial summaries from those reports require a different approach, such as switching to owner_statements which does support property filtering.
How much does this cost to run monthly?
Infrastructure costs are effectively zero -- Google Apps Script and Google Sheets are free within generous usage limits. The only recurring cost is OpenAI API usage for the summarization step, which runs under $5 per month for most weekly cadences. Development time for the initial build is the primary cost.
Can a property manager set this up without a developer?
The setup requires working knowledge of Google Apps Script (JavaScript) and REST APIs. Most property managers commission the initial build from a developer and then self-manage ongoing configuration -- adding investors, adjusting meetings, changing report types -- through the Google Sheet interface, which requires no code changes.
If you are running AppFolio and spending hours each week on investor reporting, we have built this exact system for a fund with 12 weekly meetings and multiple investor entities. Book a 15-minute call and we will tell you in the first five minutes whether your setup maps to this pattern or needs something different. See also our automation services for a broader look at what we build.
Want us to build this for you?
15-minute discovery call. No pitch. We tell you what to automate first.
Book a Discovery Call