Skip to content
cccskills
FreePublished on GitHub

appfolio-sdk-patterns

Create a structured AppFolio REST API client for property management data such as properties, tenants, leases, and work orders. It centralizes credentials, typed responses, pagination, timeouts, and error handling, including deliberate handling of rate limits and uncertain write outcomes.

OriginalShow the original description

Apply production-ready patterns for AppFolio REST API integration. Trigger: "appfolio patterns".

Run score Not tested

See how to install

Good for

  • Building an AppFolio API client
  • Listing properties or tenants with pagination
  • Handling API errors and rate limits
  • Testing integrations with synthetic fixtures

Files included (1)

  • SKILL.md6.5 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

AppFolio SDK Patterns

Overview

Production-ready patterns for the AppFolio property management REST API. AppFolio uses HTTP Basic Auth with client credentials and returns JSON responses for properties, tenants, leases, and work orders. A structured singleton client prevents credential sprawl, enforces consistent error handling, and centralizes pagination logic across all property management endpoints.

Prerequisites

  • A provider-verified base URL, auth method, endpoint scope, and managed secret injection path for the target portfolio.
  • Schema validation, request timeouts, endpoint-specific rate limits, and idempotency keys for all mutation-capable service methods.
  • Synthetic fixtures for unit testing; production tenant, lease, and payment payloads must not become default mock, log, or error-message content.

Instructions

  1. Construct one contract-bound client per runtime and reject missing or invalid configuration before requests begin.
  2. Validate query bounds and response shapes at the service boundary, then pass only minimized typed fields to callers.
  3. Return a classified 429 or unknown-write failure to the caller; retry only idempotent operations after the caller records the cursor/key and delay.
  4. Keep write workflows behind explicit authorization, audit, and reconciliation controls rather than embedding them in generic SDK convenience helpers.

Singleton Client

import axios, { AxiosInstance } from 'axios';
let _client: AxiosInstance | null = null;
export function getClient(): AxiosInstance {
  if (!_client) {
    const clientId = process.env.APPFOLIO_CLIENT_ID;
    const clientSecret = process.env.APPFOLIO_CLIENT_SECRET;
    const baseURL = process.env.APPFOLIO_BASE_URL;
    if (!clientId || !clientSecret || !baseURL) throw new Error('APPFOLIO_CLIENT_ID, SECRET, and BASE_URL required');
    _client = axios.create({ baseURL, auth: { username: clientId, password: clientSecret }, timeout: 30000 });
  }
  return _client;
}

Error Wrapper

export class AppFolioError extends Error {
  constructor(public status: number, public code: string, message: string) { super(message); }
}
export async function safeCall<T>(operation: string, fn: () => Promise<T>): Promise<T> {
  try { return await fn(); }
  catch (err: any) {
    const status = err.response?.status ?? 0;
    if (status === 429) {
      const retryAfter = err.response?.headers?.['retry-after'];
      throw new AppFolioError(429, 'RATE_LIMIT', `Retry after ${retryAfter ?? 'provider-directed delay'}; do not replay an unknown write automatically`);
    }
    if (status === 401) throw new AppFolioError(401, 'AUTH', 'Invalid APPFOLIO_CLIENT_ID or SECRET');
    throw new AppFolioError(status, 'API_ERROR', `${operation} failed [${status}]: ${err.message}`);
  }
}

Request Builder

class AppFolioQuery {
  private params: Record<string, string> = {};
  status(s: 'active' | 'past' | 'future') { this.params.status = s; return this; }
  propertyId(id: string) { this.params.property_id = id; return this; }
  page(n: number) { this.params.page = String(n); return this; }
  perPage(n: number) { this.params.per_page = String(Math.min(n, 200)); return this; }
  since(date: string) { this.params.updated_since = date; return this; }
  build() { return this.params; }
}
// Usage: new AppFolioQuery().status('active').perPage(50).build();

Response Types

interface Property {
  id: string; name: string; property_type: 'residential' | 'commercial' | 'mixed';
  address: { street: string; city: string; state: string; zip: string };
  unit_count: number; status: string;
}
interface Tenant {
  id: string; first_name: string; last_name: string;
  email: string; phone: string; unit_id: string; lease_id: string;
}
interface Lease {
  id: string; unit_id: string; tenant_id: string;
  start_date: string; end_date: string; rent_amount: number; status: 'active' | 'expired' | 'future';
}
interface WorkOrder {
  id: string; property_id: string; unit_id: string;
  description: string; priority: 'low' | 'medium' | 'high' | 'emergency';
  status: 'open' | 'in_progress' | 'completed';
}

Testing Utilities

export function mockProperty(overrides: Partial<Property> = {}): Property {
  return { id: 'prop-001', name: 'Maple Ridge Apts', property_type: 'residential',
    address: { street: '100 Main St', city: 'Austin', state: 'TX', zip: '78701' },
    unit_count: 24, status: 'active', ...overrides };
}
export function mockLease(overrides: Partial<Lease> = {}): Lease {
  return { id: 'lease-001', unit_id: 'unit-001', tenant_id: 'ten-001',
    start_date: '2025-01-01', end_date: '2026-01-01', rent_amount: 1500, status: 'active', ...overrides };
}

Error Handling

PatternWhen to UseExample
safeCall wrapperAll API callsPrevents uncaught 4xx/5xx from crashing flows
Caller-owned retry on 429Rate-limited idempotent batch readsPreserve cursor/key, honor Retry-After, then retry deliberately
Auth validationClient initThrows early if credentials are missing
Pagination loopListing properties/tenantsIncrement page until empty response

Output

  • One validated, contract-bound API client with centralized timeout and error classification behavior
  • Bounded query parameters and minimized typed responses for service callers
  • Explicit rate-limit and unknown-write outcomes that require caller-owned cursor/idempotency/reconciliation decisions

Examples

For a property-list read, build a query with a capped page size, execute it through safeCall, and verify the returned property IDs and count against a synthetic fixture. For a mutation, persist an idempotency key before dispatch and treat a timeout or 429 as a pause/reconcile condition rather than calling the function again. If client configuration, response schema, or write outcome is unverified, stop the workflow and surface a redacted error to the owner.

Resources

Next Steps

Apply patterns in appfolio-core-workflow-a.

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Add a synthetic-data CI gate for customer-owned Abridge integration code. Test consent handling, note-review handoffs, redaction, and rollback behavior with mocked vendor boundaries, without connecting CI to a live Abridge or EHR tenant.

  • Adding regression checks to an Abridge integration repository
  • Testing consent and note-release handling
  • Checking logs for sensitive data exposure
jeremylongshore/tons-of-skills-marketplace2.9KUpdated Oct 25, 2026

Triage Abridge recording, note review, Epic handoff, and access problems using observed symptoms and approved guidance. Identify the affected workflow stage, organize a minimal timeline, and prepare a redacted escalation record for the responsible team.

  • Separate recording problems from upload failures
  • Investigate note creation delays and editor issues
  • Identify the responsible team for Epic handoff failures
jeremylongshore/tons-of-skills-marketplace2.9KUpdated Oct 25, 2026

Map and review an Abridge clinical documentation workflow from consent and recording through draft review, evidence checks, edits, and final disposition. Keep clinician approval explicit, identify failure exits, and separate verified facts from tenant-specific details and pending approvals.

  • Map a clinical note workflow for a care setting
  • Check where consent and patient identity are verified
  • Review draft notes against linked evidence before sign-off
jeremylongshore/tons-of-skills-marketplace2.9KUpdated Oct 25, 2026

Audit how clinician-reviewed Abridge note sections map into Epic templates and downstream workflows. Use approved test records to document destinations, exceptions, evidence, rollback steps, and a go or no-go decision for a specific environment.

  • Checking Abridge note section mappings to Epic templates
  • Auditing a department’s approved documentation handoff
  • Preparing test evidence and a go or no-go decision
jeremylongshore/tons-of-skills-marketplace2.9KUpdated Oct 25, 2026

Review Abridge adoption and workflow value using local usage records, clinician feedback, and verified contract costs. Build an evidence based decision table covering adoption, quality, support burden, and options to expand, hold, or improve the rollout.

  • Reviewing adoption after an Abridge rollout
  • Preparing evidence for a renewal decision
  • Comparing workflow outcomes across care settings
jeremylongshore/tons-of-skills-marketplace2.9KUpdated Oct 25, 2026

Prepare a bounded support bundle for an Abridge issue using allowlisted operational details such as versions, coarse timestamps, and correlation references. Exclude recordings, transcripts, patient identifiers, credentials, and unrestricted logs, then document redactions and review details.

  • Preparing reviewed evidence for an Abridge support case
  • Documenting redactions and excluded data
  • Collecting approved operational details for an incident
jeremylongshore/tons-of-skills-marketplace2.9KUpdated Oct 25, 2026

See all skills by jeremylongshore

Report a problem with this skill