appfolio sdk patterns | sed 's/\b(.)/\u\1/g'
Overview
Production patterns for AppFolio API: typed client, pagination, response caching, and error handling.
Instructions
Step 1: Typed API Client
// src/appfolio/typed-client.ts
import axios, { AxiosInstance } from "axios";
interface Property {
id: string; name: string; property_type: string;
address: { street: string; city: string; state: string; zip: string };
unit_count: number;
}
interface Tenant {
id: string; first_name: string; last_name: string;
email: string; phone: string; unit_id: string;
}
interface Lease {
id: string; unit_id: string; tenant_name: string;
start_date: string; end_date: string; rent_amount: number; status: string;
}
class AppFolioTypedClient {
private api: AxiosInstance;
constructor() {
this.api = axios.create({
baseURL: process.env.APPFOLIO_BASE_URL,
auth: { username: process.env.APPFOLIO_CLIENT_ID!, password: process.env.APPFOLIO_CLIENT_SECRET! },
timeout: 30000,
});
}
async getProperties(): Promise<Property[]> { return (await this.api.get("/properties")).data; }
async getTenants(): Promise<Tenant[]> { return (await this.api.get("/tenants")).data; }
async getLeases(): Promise<Lease[]> { return (await this.api.get("/leases")).data; }
}
export { AppFolioTypedClient, Property, Tenant, Lease };
Step 2: Response Cache
const cache = new Map<string, { data: any; expiry: number }>();
async function cachedGet<T>(client: AxiosInstance, path: string, ttlMs = 60000): Promise<T> {
const cached = cache.get(path);
if (cached && cached.expiry > Date.now()) return cached.data;
const { data } = await client.get(path);
cache.set(path, { data, expiry: Date.now() + ttlMs });
return data;
}
Resources
1---2name: appfolio-sdk-patterns3description: |4 Apply production-ready patterns for AppFolio REST API integration.5 Trigger: "appfolio patterns".6allowed-tools: Read, Write, Edit, Bash(npm:*), Bash(curl:*), Grep7version: 1.0.08license: MIT9author: Jeremy Longshore <jeremy@intentsolutions.io>10tags: [saas, property-management, appfolio, real-estate]11compatible-with: claude-code12---1314# appfolio sdk patterns | sed 's/\b\(.\)/\u\1/g'1516## Overview17Production patterns for AppFolio API: typed client, pagination, response caching, and error handling.1819## Instructions2021### Step 1: Typed API Client22```typescript23// src/appfolio/typed-client.ts24import axios, { AxiosInstance } from "axios";2526interface Property {27 id: string; name: string; property_type: string;28 address: { street: string; city: string; state: string; zip: string };29 unit_count: number;30}3132interface Tenant {33 id: string; first_name: string; last_name: string;34 email: string; phone: string; unit_id: string;35}3637interface Lease {38 id: string; unit_id: string; tenant_name: string;39 start_date: string; end_date: string; rent_amount: number; status: string;40}4142class AppFolioTypedClient {43 private api: AxiosInstance;4445 constructor() {46 this.api = axios.create({47 baseURL: process.env.APPFOLIO_BASE_URL,48 auth: { username: process.env.APPFOLIO_CLIENT_ID!, password: process.env.APPFOLIO_CLIENT_SECRET! },49 timeout: 30000,50 });51 }5253 async getProperties(): Promise<Property[]> { return (await this.api.get("/properties")).data; }54 async getTenants(): Promise<Tenant[]> { return (await this.api.get("/tenants")).data; }55 async getLeases(): Promise<Lease[]> { return (await this.api.get("/leases")).data; }56}5758export { AppFolioTypedClient, Property, Tenant, Lease };59```6061### Step 2: Response Cache62```typescript63const cache = new Map<string, { data: any; expiry: number }>();6465async function cachedGet<T>(client: AxiosInstance, path: string, ttlMs = 60000): Promise<T> {66 const cached = cache.get(path);67 if (cached && cached.expiry > Date.now()) return cached.data;68 const { data } = await client.get(path);69 cache.set(path, { data, expiry: Date.now() + ttlMs });70 return data;71}72```7374## Resources7576- [AppFolio Stack APIs](https://www.appfolio.com/stack/partners/api)77- [AppFolio Engineering Blog](https://engineering.appfolio.com)78