Claude-code-plugins appfolio-local-dev-loop
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/appfolio-pack/skills/appfolio-local-dev-loop" ~/.claude/skills/jeremylongshore-claude-code-plugins-appfolio-local-dev-loop && rm -rf "$T"
manifest:
plugins/saas-packs/appfolio-pack/skills/appfolio-local-dev-loop/SKILL.mdsource content
AppFolio Local Dev Loop
Overview
Local development workflow for AppFolio property management API integration. Provides a fast feedback loop with mock property, tenant, and lease endpoints so you can build and test integrations without consuming live API quota. Toggle between mock mode for rapid iteration and sandbox mode for pre-deployment validation against the real AppFolio Stack API.
Environment Setup
cp .env.example .env # Set your credentials: # APPFOLIO_API_KEY=af_live_xxxxxxxxxxxx # APPFOLIO_BASE_URL=https://api.appfolio.com/api/v1 # MOCK_MODE=true npm install express axios dotenv tsx typescript @types/node npm install -D vitest supertest @types/express
Dev Server
// src/dev/server.ts import express from "express"; import { createProxyMiddleware } from "http-proxy-middleware"; const app = express(); app.use(express.json()); const MOCK = process.env.MOCK_MODE === "true"; if (!MOCK) { app.use("/api/v1", createProxyMiddleware({ target: process.env.APPFOLIO_BASE_URL, changeOrigin: true, headers: { Authorization: `Bearer ${process.env.APPFOLIO_API_KEY}` }, })); } else { const { mountMockRoutes } = require("./mocks"); mountMockRoutes(app); } app.listen(3001, () => console.log(`AppFolio dev server on :3001 [mock=${MOCK}]`));
Mock Mode
// src/dev/mocks.ts — realistic property management responses export function mountMockRoutes(app: any) { app.get("/api/v1/properties", (_req: any, res: any) => res.json([ { id: "prop_1", name: "Sunset Apartments", address: { street: "123 Sunset Blvd", city: "Los Angeles", state: "CA" }, property_type: "residential", unit_count: 24 }, { id: "prop_2", name: "Downtown Office", address: { street: "456 Main St", city: "San Francisco", state: "CA" }, property_type: "commercial", unit_count: 8 }, ])); app.get("/api/v1/tenants", (_req: any, res: any) => res.json([ { id: "t1", first_name: "Jane", last_name: "Smith", email: "jane@example.com", unit_id: "u1", lease_id: "l1" }, ])); app.get("/api/v1/leases", (_req: any, res: any) => res.json([ { id: "l1", unit_id: "u1", start_date: "2025-01-01", end_date: "2026-01-01", rent_amount: 2500, status: "active" }, ])); app.post("/api/v1/work-orders", (req: any, res: any) => res.status(201).json({ id: "wo_1", ...req.body, status: "open" })); }
Testing Workflow
npm run dev:mock & # Start mock server in background npm run test # Unit tests with vitest npm run test -- --watch # Watch mode for rapid iteration MOCK_MODE=false npm run test:integration # Integration test against real API
Debug Tips
- Set
to trace all route matching and middleware executionDEBUG=express:* - Use
to inspect raw responsescurl -v http://localhost:3001/api/v1/properties - Check
header when testing against the live APIX-RateLimit-Remaining - AppFolio sandbox returns
for properties you do not own — verify your API key scope403 - Enable
interceptors to log request/response pairs during developmentaxios
Error Handling
| Issue | Cause | Fix |
|---|---|---|
| Invalid or expired API key | Regenerate at AppFolio Stack portal |
| Key lacks scope for endpoint | Request additional permissions |
| Wrong property ID or path | Verify resource exists in sandbox |
| Rate limit exceeded | Add exponential backoff, use mock mode |
| Dev server not running | Run first |
Resources
Next Steps
See
appfolio-debug-bundle.