Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Optimize AppFolio API costs through efficient usage patterns. Trigger: "appfolio cost".
.claude/skills/jeremylongshore-appfolio-cost-tuning/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 30% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 125% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 90% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 17% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 48% | 0% |
AppFolio Stack API pricing is partner-agreement based, with costs scaling by API call volume per managed property. Property management portfolios generate high-frequency reads for tenant lookups, lease status checks, and maintenance requests. Each redundant API call erodes margin on per-unit revenue. Optimizing call patterns directly impacts operational profitability, especially for portfolios managing hundreds or thousands of units where even small per-call costs compound rapidly.
| Component | Cost Driver | Optimization | |-----------|------------|--------------| | Property/unit reads | Per-call pricing on tenant and unit endpoints | Cache with 10-15 min TTL; property data changes infrequently | | Lease operations | Bulk lease queries across entire portfolio | Fetch all leases once, filter locally instead of per-unit calls | | Maintenance requests | Polling for new work orders | Use webhooks to receive push notifications | | Reporting exports | Large payload downloads for financial reports | Schedule off-peak, cache results for 24h | | Vendor/owner lookups | Repeated lookups for the same contacts | Build a local lookup table, refresh daily |
typescriptclass AppFolioCache { private cache = new Map<string, { data: any; expiry: number }>(); get(key: string): any | null { const entry = this.cache.get(key); if (!entry || Date.now() > entry.expiry) return null; return entry.data; } set(key: string, data: any, ttlMs = 600_000): void { this.cache.set(key, { data, expiry: Date.now() + ttlMs }); } async fetchWithCache(endpoint: string, ttlMs?: number): Promise<any> { const cached = this.get(endpoint); if (cached) return cached; const response = await fetch(endpoint); const data = await response.json(); this.set(endpoint, data, ttlMs); return data; } }
typescriptclass AppFolioUsageMonitor { private calls: Array<{ endpoint: string; timestamp: number }> = []; private budgetLimit = 10_000; // daily call budget record(endpoint: string): void { this.calls.push({ endpoint, timestamp: Date.now() }); const todayCalls = this.getTodayCount(); if (todayCalls > this.budgetLimit * 0.8) { console.warn(`AppFolio API budget 80% consumed: ${todayCalls}/${this.budgetLimit}`); } } getTodayCount(): number { const startOfDay = new Date().setHours(0, 0, 0, 0); return this.calls.filter(c => c.timestamp > startOfDay).length; } }
modified_since parameter| Issue | Cause | Fix | |-------|-------|-----| | 429 Too Many Requests | Exceeded rate limit | Implement exponential backoff with jitter | | Stale cache serving old data | TTL too long for volatile data | Reduce TTL for maintenance/lease endpoints to 2-5 min | | Budget alerts firing daily | Polling loop running on short interval | Switch to webhook-driven architecture | | Duplicate API calls | Multiple services fetching same data | Centralize through shared cache layer | | Large payload timeouts | Fetching full portfolio in single call | Paginate requests, process in batches of 100 |
See appfolio-performance-tuning.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 21,176 | 19,926 | -6% | 1 | 1 | 0% | 3,465 | 4,848 | +40% | 0 | 0 | — |
case-02 | fail→pass | 18,463 | 16,970 | -8% | 1 | 1 | 0% | 3,457 | 4,480 | +30% | 0 | 0 | — |
case-03 | fail→pass | 22,641 | 8,477 | -63% | 1 | 1 | 0% | 1,195 | 2,690 | +125% | 0 | 0 | — |
case-04 | pass→pass | 11,874 | 10,060 | -15% | 1 | 1 | 0% | 2,065 | 2,815 | +36% | 0 | 0 | — |
case-05 | fail→pass | 4,121 | 2,603 | -37% | 1 | 1 | 0% | 727 | 1,378 | +90% | 0 | 0 | — |
case-06 | fail→pass | 12,160 | 7,813 | -36% | 1 | 1 | 0% | 1,958 | 2,284 | +17% | 0 | 0 | — |
case-07 | fail→pass | 13,572 | 14,163 | +4% | 1 | 1 | 0% | 2,527 | 3,740 | +48% | 0 | 0 | — |
case-08 | pass→pass | 16,166 | 15,229 | -6% | 1 | 1 | 0% | 2,556 | 3,806 | +49% | 0 | 0 | — |
case-09 | fail→pass | 10,456 | 2,800 | -73% | 1 | 1 | 0% | 1,753 | 1,384 | -21% | 0 | 0 | — |
case-10 | pass→pass | 13,334 | 11,769 | -12% | 1 | 1 | 0% | 2,341 | 3,195 | +36% | 0 | 0 | — |
case-11 | pass→pass | 8,781 | 5,191 | -41% | 1 | 1 | 0% | 1,362 | 1,895 | +39% | 0 | 0 | — |
case-12 | pass→pass | 14,954 | 12,170 | -19% | 1 | 1 | 0% | 2,455 | 3,246 | +32% | 0 | 0 | — |
case-13 | fail→pass | 8,895 | 2,262 | -75% | 1 | 1 | 0% | 1,403 | 1,350 | -4% | 0 | 0 | — |
case-14 | fail→pass | 10,280 | 3,017 | -71% | 1 | 1 | 0% | 2,033 | 1,561 | -23% | 0 | 0 | — |
case-15 | fail→pass | 13,702 | 7,497 | -45% | 1 | 1 | 0% | 2,273 | 2,251 | -1% | 0 | 0 | — |
case-16 | pass→pass | 7,526 | 4,228 | -44% | 1 | 1 | 0% | 1,305 | 1,641 | +26% | 0 | 0 | — |
case-17 | fail→pass | 14,653 | 7,738 | -47% | 1 | 1 | 0% | 2,192 | 2,306 | +5% | 0 | 0 | — |
case-18 | pass→pass | 16,610 | 9,307 | -44% | 1 | 1 | 0% | 2,466 | 2,408 | -2% | 0 | 0 | — |
case-19 | fail→fail | 17,977 | 14,960 | -17% | 1 | 1 | 0% | 2,949 | 3,897 | +32% | 0 | 0 | — |
case-20 | fail→pass | 16,412 | 4,315 | -74% | 1 | 1 | 0% | 2,357 | 1,504 | -36% | 0 | 0 | — |
case-21 | pass→pass | 8,939 | 7,968 | -11% | 1 | 1 | 0% | 1,694 | 2,566 | +51% | 0 | 0 | — |
case-22 | pass→pass | 18,017 | 13,380 | -26% | 1 | 1 | 0% | 3,631 | 3,834 | +6% | 0 | 0 | — |
case-23 | pass→pass | 27,152 | 25,686 | -5% | 1 | 1 | 0% | 5,831 | 7,162 | +23% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted, and 22 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +48 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.