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".
| 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 validationOther measured skills in the registry, with their headline benchmark lift.