Setup
Two connections make this real: the Google Sheet that holds the numbers, and an AI key so the assistant can hold a conversation. Everything works without them, on sample data.
Where things stand
Spreadsheet
.data/portfolio.json
Not set
Assistant model
Keyword matching only, until an AI Gateway key is added
Who can do what
Open, and add-only. Anyone with the link can add entries but cannot change existing rows. Set APP_PASSCODE before putting this on the internet.
What is in there now
2 properties · 30 units · 26 current tenants · 77 payments · 12 expense entries
Connect your Google Sheet
A service account is a robot Google account. You share the sheet with its email address, and this app signs in as that robot. No passwords to keep up with.
- 1. Make the spreadsheet. Create a blank Google Sheet and copy the long id out of its address bar, the part between
/d/and/edit. - 2. Make a service account. In the Google Cloud console, enable the Google Sheets API, then create a service account and a JSON key for it.
- 3. Share the sheet with it. Share your spreadsheet with the service account's email address as an Editor.
- 4. Add the environment variables.
GOOGLE_SHEET_ID=your-spreadsheet-id GOOGLE_SERVICE_ACCOUNT_JSON={"client_email":"...","private_key":"..."} AI_GATEWAY_API_KEY=your-ai-gateway-key APP_PASSCODE=her-passcode-adds-entries-only OWNER_PASSCODE=your-passcode-can-also-editLocally that is a
.env.localfile. On Vercel they go in the project's environment variables. - 5. Build the tabs. Once the variables are set, this button creates every tab with the right column headers.
Add the Google variables first, then restart the app.
What each tab holds
You can always type in the sheet by hand. The assistant reads and writes these same columns, so keep the headers as they are.
| Tab | What it is for | Columns |
|---|---|---|
| Properties | One row per apartment complex. | property_id, name, address, city, state, zip, notes |
| Units | One row per door, with market rent and whether it is occupied, vacant, or being renovated. | unit_id, property_id, unit_label, beds, baths, sqft, market_rent, status, notes |
| Tenants | One row per tenant, past and present, with lease dates and monthly rent. | tenant_id, unit_id, name, phone, email, status, move_in_date, move_out_date, lease_start, lease_end, monthly_rent, deposit, rent_due_day, notes |
| Payments | Every dollar collected: rent, late fees, deposits. | payment_id, date, unit_id, tenant_id, period, category, amount, method, reference, notes |
| Charges | Extra amounts owed on top of rent, mostly late fees. | charge_id, date, unit_id, tenant_id, period, type, amount, status, notes |
| Renovations | Renovation projects per unit, with a budget and a status. | renovation_id, property_id, unit_id, scope, status, start_date, target_date, completed_date, budget, contractor, notes |
| Maintenance | Work orders and repair requests. | work_order_id, date_reported, property_id, unit_id, issue, priority, status, vendor, scheduled_date, completed_date, notes |
| Expenses | Money spent, optionally tied to a renovation project or work order. | expense_id, date, property_id, unit_id, category, vendor, amount, description, renovation_id, work_order_id, notes |
| Requests | Changes that need the owner: move-outs, corrections, anything that means editing a row. Added here instead of changed in place. | requested_at, requested_by, about, request, status |
| Settings | Late fee amount, grace period, and other preferences. | key, value, notes |
| ActivityLog | A log of every change the assistant makes, so nothing is a mystery later. | logged_at, actor, action, details |