Skills Development AppFolio API Rate Limit Management

AppFolio API Rate Limit Management

v20260423
appfolio-rate-limits
This skill provides robust strategies for managing AppFolio API rate limits. It implements token bucket throttling, exponential backoff, and comprehensive retry logic to ensure reliable execution of high-volume operations, such as bulk tenant imports, rent-roll syncs, and work-order batch updates. It prevents API quota exhaustion during peak usage times.
Get Skill
95 downloads
Overview

AppFolio Rate Limits

Overview

AppFolio's Stack API enforces per-partner rate limits to protect shared property management infrastructure. High-volume operations like bulk tenant imports, rent-roll syncs, and work-order batch updates can quickly exhaust quotas. Property managers running nightly portfolio syncs across hundreds of units must throttle carefully, especially during month-end when lease renewals and payment processing spike concurrently.

Rate Limit Reference

Endpoint Limit Window Scope
Properties list/get 120 req 1 minute Per partner key
Tenant create/update 30 req 1 minute Per partner key
Work orders 60 req 1 minute Per partner key
Bulk data export 5 req 1 hour Per partner key
Webhooks registration 10 req 1 minute Per partner key

Rate Limiter Implementation

class AppFolioRateLimiter {
  private tokens: number;
  private lastRefill: number;
  private readonly maxTokens: number;
  private readonly refillRate: number; // tokens per ms
  private queue: Array<{ resolve: () => void }> = [];

  constructor(maxPerMinute: number) {
    this.maxTokens = maxPerMinute;
    this.tokens = maxPerMinute;
    this.lastRefill = Date.now();
    this.refillRate = maxPerMinute / 60_000;
  }

  async acquire(): Promise<void> {
    this.refill();
    if (this.tokens >= 1) { this.tokens -= 1; return; }
    return new Promise(resolve => this.queue.push({ resolve }));
  }

  private refill() {
    const now = Date.now();
    this.tokens = Math.min(this.maxTokens, this.tokens + (now - this.lastRefill) * this.refillRate);
    this.lastRefill = now;
    while (this.tokens >= 1 && this.queue.length) {
      this.tokens -= 1;
      this.queue.shift()!.resolve();
    }
  }
}

const limiter = new AppFolioRateLimiter(100);

Retry Strategy

async function appfolioRetry<T>(fn: () => Promise<Response>, maxRetries = 4): Promise<T> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    await limiter.acquire();
    const res = await fn();
    if (res.ok) return res.json();
    if (res.status === 429) {
      const retryAfter = parseInt(res.headers.get("Retry-After") || "10", 10);
      const delay = retryAfter * 1000 + Math.random() * 2000;
      await new Promise(r => setTimeout(r, delay));
      continue;
    }
    if (res.status >= 500 && attempt < maxRetries) {
      await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 1000));
      continue;
    }
    throw new Error(`AppFolio API ${res.status}: ${await res.text()}`);
  }
  throw new Error("Max retries exceeded");
}

Batch Processing

async function batchSyncTenants(tenants: any[], batchSize = 25) {
  const results: any[] = [];
  for (let i = 0; i < tenants.length; i += batchSize) {
    const batch = tenants.slice(i, i + batchSize);
    const batchResults = await Promise.all(
      batch.map(t => appfolioRetry(() =>
        fetch(`${BASE}/api/v1/tenants`, {
          method: "POST", headers, body: JSON.stringify(t),
        })
      ))
    );
    results.push(...batchResults);
    if (i + batchSize < tenants.length) await new Promise(r => setTimeout(r, 2000));
  }
  return results;
}

Error Handling

Issue Cause Fix
429 Too Many Requests Exceeded partner rate limit Backoff using Retry-After header
403 on bulk export Hourly export cap reached Queue exports with 15-min spacing
Timeout on property list Large portfolio (500+ units) Paginate with per_page=50
409 Conflict on tenant update Concurrent write to same tenant Retry with fresh ETag
503 during maintenance Scheduled nightly window (2-4 AM PT) Skip requests, retry after window

Resources

Next Steps

See appfolio-performance-tuning.

Info
Category Development
Name appfolio-rate-limits
Version v20260423
Size 4.2KB
Updated At 2026-04-28
Language