Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Migrate between AppFolio API versions and handle endpoint changes. Trigger: "appfolio upgrade".
.claude/skills/jeremylongshore-appfolio-upgrade-migration/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 44% | 0% |
| case-02 | ✗→✓ | ▲ Improved | -27% | 0% |
| case-03 | ✗→✓ | ▲ Improved | -1% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 8% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 26% | 0% |
AppFolio property management integrations depend on versioned REST API endpoints that evolve with the platform. Upgrades can rename fields on property and tenant objects, change pagination models, deprecate work-order endpoints, and alter the basic-auth flow. This skill detects your current API version, maps deprecated response shapes to replacements, and rolls back automatically if the new version fails.
/api/v1/)After a successful migration the skill produces:
VersionInfo object confirming current, latest, and deprecated versionstypescriptinterface VersionInfo { current: string; latest: string; deprecated: string[]; } async function detectApiVersion(baseUrl: string, headers: Record<string, string>): Promise<VersionInfo> { const res = await fetch(`${baseUrl}/api/status`, { headers }); const body = await res.json(); const current = res.headers.get("X-AppFolio-Api-Version") ?? body.api_version; const deprecated: string[] = body.deprecated_versions ?? []; if (deprecated.includes(current)) { console.warn(`Version ${current} is deprecated. Migrate to ${body.latest_version}.`); } return { current, latest: body.latest_version, deprecated }; }
typescriptinterface LegacyProperty { address_line1: string; unit_count: number; mgr_id: string; } interface CurrentProperty { street_address: string; total_units: number; manager_id: string; } function migrateProperty(old: LegacyProperty): CurrentProperty { return { street_address: old.address_line1, total_units: old.unit_count, manager_id: old.mgr_id }; } interface LegacyTenant { lease_end: string; balance_due: number; } interface CurrentTenant { lease_expiry_date: string; outstanding_balance: number; } function migrateTenant(old: LegacyTenant): CurrentTenant { return { lease_expiry_date: old.lease_end, outstanding_balance: old.balance_due }; }
typescriptasync function versionAwareRequest( baseUrl: string, path: string, headers: Record<string, string>, targetVersion: string, fallbackVersion: string ): Promise<any> { const res = await fetch(`${baseUrl}/api/${targetVersion}${path}`, { headers }); if (res.status === 410 || res.status === 404) { console.warn(`${targetVersion} rejected; falling back to ${fallbackVersion}`); const fallback = await fetch(`${baseUrl}/api/${fallbackVersion}${path}`, { headers }); if (!fallback.ok) throw new Error(`Fallback failed: ${fallback.status}`); return fallback.json(); } if (!res.ok) throw new Error(`Request failed: ${res.status}`); return res.json(); }
typescript// Detect version and migrate if needed const info = await detectApiVersion("https://acme.appfolio.com", authHeaders); if (info.deprecated.includes(info.current)) { const oldProps = await fetchLegacyProperties(); const migrated = oldProps.map(migrateProperty); await smokeTestEndpoints("https://acme.appfolio.com", authHeaders); }
| Migration Issue | Symptom | Fix | |---|---|---| | Deprecated version prefix | 410 Gone on every request | Update base URL to latest version prefix | | Renamed property fields | undefined values in property sync | Apply migrateProperty transform | | Removed pagination offset | Empty result sets after page one | Switch to cursor-based pagination | | Auth header rejected | 401 Unauthorized after upgrade | Regenerate client secret, update env vars | | Webhook envelope change | Event handler parse errors | Update payload parser for new envelope |
appfolio-ci-integration for post-migration CI validation| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 13,457 | 11,491 | -15% | 1 | 1 | 0% | 2,831 | 4,069 | +44% | 0 | 0 | — |
case-02 | fail→pass | 25,847 | 14,032 | -46% | 1 | 1 | 0% | 6,016 | 4,367 | -27% | 0 | 0 | — |
case-03 | fail→pass | 15,480 | 9,761 | -37% | 1 | 1 | 0% | 3,454 | 3,406 | -1% | 0 | 0 | — |
case-09 | pass→pass | 8,959 | 4,639 | -48% | 1 | 1 | 0% | 1,447 | 2,052 | +42% | 0 | 0 | — |
case-04 | fail→pass | 7,420 | 1,691 | -77% | 1 | 1 | 0% | 1,387 | 1,495 | +8% | 0 | 0 | — |
case-05 | fail→pass | 9,810 | 5,712 | -42% | 1 | 1 | 0% | 1,919 | 2,418 | +26% | 0 | 0 | — |
case-06 | fail→pass | 13,270 | 5,586 | -58% | 1 | 1 | 0% | 2,757 | 2,336 | -15% | 0 | 0 | — |
case-07 | pass→pass | 13,364 | 3,664 | -73% | 1 | 1 | 0% | 2,186 | 1,884 | -14% | 0 | 0 | — |
case-08 | pass→pass | 13,524 | 8,734 | -35% | 1 | 1 | 0% | 2,326 | 2,928 | +26% | 0 | 0 | — |
case-10 | pass→pass | 10,526 | 3,329 | -68% | 1 | 1 | 0% | 1,679 | 1,766 | +5% | 0 | 0 | — |
case-11 | fail→pass | 16,648 | 8,139 | -51% | 1 | 1 | 0% | 2,699 | 2,771 | +3% | 0 | 0 | — |
case-12 | pass→pass | 8,240 | 2,068 | -75% | 1 | 1 | 0% | 1,429 | 1,557 | +9% | 0 | 0 | — |
case-13 | fail→pass | 13,879 | 8,267 | -40% | 1 | 1 | 0% | 2,702 | 2,770 | +3% | 0 | 0 | — |
case-14 | fail→pass | 15,609 | 10,611 | -32% | 1 | 1 | 0% | 3,009 | 3,366 | +12% | 0 | 0 | — |
case-15 | fail→pass | 11,426 | 3,033 | -73% | 1 | 1 | 0% | 1,870 | 1,683 | -10% | 0 | 0 | — |
case-16 | pass→pass | 12,833 | 6,209 | -52% | 1 | 1 | 0% | 2,292 | 2,412 | +5% | 0 | 0 | — |
case-17 | fail→pass | 12,091 | 4,108 | -66% | 1 | 1 | 0% | 1,987 | 1,880 | -5% | 0 | 0 | — |
case-18 | pass→pass | 12,275 | 3,857 | -69% | 1 | 1 | 0% | 2,137 | 1,869 | -13% | 0 | 0 | — |
case-19 | pass→pass | 13,176 | 10,727 | -19% | 1 | 1 | 0% | 2,515 | 3,176 | +26% | 0 | 0 | — |
case-20 | pass→pass | 8,682 | 5,029 | -42% | 1 | 1 | 0% | 1,707 | 2,299 | +35% | 0 | 0 | — |
case-21 | pass→pass | 9,617 | 7,580 | -21% | 1 | 1 | 0% | 2,042 | 2,773 | +36% | 0 | 0 | — |
case-22 | pass→pass | 9,698 | 7,617 | -21% | 1 | 1 | 0% | 2,035 | 2,759 | +36% | 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. 22 cases were attempted. The headline lift of +50 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.