From appfolio-pack
Build property management dashboard with AppFolio API data. Trigger: "appfolio property dashboard".
How this skill is triggered — by the user, by Claude, or both
Slash command
/appfolio-pack:appfolio-core-workflow-aThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Primary workflow for AppFolio property management integration. Covers the full property
Primary workflow for AppFolio property management integration. Covers the full property
lifecycle: creating and updating property records, managing tenant profiles and lease
agreements, and querying occupancy data. Uses the AppFolio Stack API with OAuth 2.0
client credentials for server-to-server access. All endpoints return JSON and support
pagination via cursor parameters for large portfolios.
const token = await fetch('https://api.appfolio.com/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: process.env.APPFOLIO_CLIENT_ID!,
client_secret: process.env.APPFOLIO_CLIENT_SECRET!,
scope: 'properties tenants leases',
}),
}).then(r => r.json());
const headers = { Authorization: `Bearer ${token.access_token}`, 'Content-Type': 'application/json' };
const property = await fetch('https://api.appfolio.com/v1/properties', {
method: 'POST', headers,
body: JSON.stringify({
name: 'Sunrise Apartments',
address: { street: '100 Main St', city: 'Austin', state: 'TX', zip: '78701' },
type: 'residential',
units: [
{ number: '101', bedrooms: 2, bathrooms: 1, rent: 1450 },
{ number: '102', bedrooms: 1, bathrooms: 1, rent: 1100 },
],
}),
}).then(r => r.json());
console.log(`Property created: ${property.id}`);
const tenant = await fetch('https://api.appfolio.com/v1/tenants', {
method: 'POST', headers,
body: JSON.stringify({
first_name: 'Alex', last_name: 'Rivera',
email: '[email protected]', phone: '512-555-0199',
}),
}).then(r => r.json());
await fetch('https://api.appfolio.com/v1/leases', {
method: 'POST', headers,
body: JSON.stringify({
property_id: property.id, unit_number: '101',
tenant_id: tenant.id, start_date: '2026-05-01', end_date: '2027-04-30',
monthly_rent: 1450, security_deposit: 1450,
}),
}).then(r => r.json());
const units = await fetch(
`https://api.appfolio.com/v1/properties/${property.id}/units?status=vacant`,
{ headers },
).then(r => r.json());
console.log(`Vacant units: ${units.data.length} of ${units.meta.total}`);
| Issue | Cause | Fix |
|---|---|---|
401 Unauthorized | Expired or invalid token | Re-authenticate with client credentials |
404 Not Found | Wrong property/tenant ID | Verify resource ID exists before referencing |
422 Unprocessable | Missing required fields | Check errors[] array in response body |
409 Conflict | Duplicate lease for unit | Query existing leases before creating |
429 Too Many Requests | Rate limit exceeded | Back off using Retry-After header value |
A successful run creates a property with units, adds a tenant, binds them via a lease, and reports vacancy counts. Console output confirms each resource ID on creation.
Continue with appfolio-core-workflow-b for maintenance requests and payment tracking.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub flight505/skill-forge --plugin appfolio-pack