인터페이스는 현지화되어 있으며, Skill 소개와 설명 문서는 선택한 언어로 AI가 자동 번역합니다. 코드·명령어·경로·전문 용어는 원문을 유지하며 원작자 문서가 우선합니다.
Skills 둘러보기
생산성 및 협업 · SKILL.md

fondo-rate-limits

fondo-rate-limits

Manage rate limits for Fondo-connected services including Gusto API, QuickBooks API, Plaid, and Stripe when building parallel integrations. Trigger: "fondo rate limit", "gusto API limits", "QuickBooks throttling".

Independent GitHub snapshot · Automated classification · Original documentation governs

Skill 문서

GitHub

name: fondo-rate-limits description: 'Manage rate limits for Fondo-connected services including Gusto API,

QuickBooks API, Plaid, and Stripe when building parallel integrations.

Trigger: "fondo rate limit", "gusto API limits", "QuickBooks throttling".

' allowed-tools: Read, Write, Edit version: 1.6.0 license: MIT author: Jeremy Longshore jeremy@intentsolutions.io tags:

  • saas
  • accounting
  • fondo compatibility: Designed for Claude Code

Fondo Rate Limits

Prerequisites

Confirmed provider limits, an approved concurrency/retry policy, opaque queue telemetry, a finance owner, and synthetic test workload.

Instructions

Honor throttle signals, use bounded backoff and idempotency, route exhausted work to reviewed handling, and reduce load before resuming.

Output

Publish a rate-control receipt with policy version, aggregate throttles, queue outcome, owner, and manual disposition—never financial payloads or credentials.

Examples

Simulate a throttle for a fictional export, verify the retry runs once under its operation ID, and send repeated failure to review without exposing records.

Overview

Fondo itself is a managed tax and accounting service without direct API rate limits, but startups building parallel integrations to the same financial providers Fondo connects to (Gusto, QuickBooks, Plaid, Stripe, Mercury) must coordinate their own API calls to avoid shared-limit conflicts. During Fondo's nightly sync windows, your direct API calls compete for the same provider quotas, making careful scheduling and throttling critical for tax-season workloads and month-end reconciliation batches.

Rate Limit Reference

Endpoint / ProviderLimitWindowScope
Gusto payroll API50 req1 minutePer access token
QuickBooks Online API500 req, 10 concurrent1 minutePer realm (company)
Plaid transactions100 req1 minutePer client_id
Stripe reads100 req/sec1 secondPer API key
Mercury banking API50 req1 minutePer API key

Rate Limiter Implementation

class MultiProviderLimiter {
  private limiters: Map<string, { tokens: number; max: number; lastRefill: number; rate: number }> = new Map();

  register(provider: string, maxPerMinute: number) {
    this.limiters.set(provider, {
      tokens: maxPerMinute, max: maxPerMinute,
      lastRefill: Date.now(), rate: maxPerMinute / 60_000,
    });
  }

  async acquire(provider: string): Promise<void> {
    const l = this.limiters.get(provider);
    if (!l) throw new Error(`Unknown provider: ${provider}`);
    const now = Date.now();
    l.tokens = Math.min(l.max, l.tokens + (now - l.lastRefill) * l.rate);
    l.lastRefill = now;
    if (l.tokens >= 1) { l.tokens -= 1; return; }
    const waitMs = (1 - l.tokens) / l.rate;
    await new Promise(r => setTimeout(r, waitMs));
    l.tokens = 0;
  }
}

const limiter = new MultiProviderLimiter();
limiter.register("gusto", 40);      // 40/min leaves room for Fondo syncs
limiter.register("quickbooks", 400); // buffer under 500/min
limiter.register("plaid", 80);

Retry Strategy

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

Batch Processing

async function batchReconcile(transactions: any[], provider: string, batchSize = 15) {
  const results: any[] = [];
  for (let i = 0; i < transactions.length; i += batchSize) {
    const batch = transactions.slice(i, i + batchSize);
    const batchResults = await Promise.all(
      batch.map(txn => providerRetry(provider, () =>
        fetch(`${PROVIDER_URLS[provider]}/transactions`, {
          method: "POST", headers: providerHeaders[provider],
          body: JSON.stringify(txn),
        })
      ))
    );
    results.push(...batchResults);
    if (i + batchSize < transactions.length) await new Promise(r => setTimeout(r, 5000));
  }
  return results;
}

Error Handling

IssueCauseFix
Gusto 429 during payroll syncFondo nightly sync competingSchedule calls outside midnight UTC window
QuickBooks concurrent limit10+ parallel requestsCap concurrency at 8 with p-queue
Plaid ITEM_LOGIN_REQUIREDToken expired mid-batchRe-authenticate, resume from last offset
Stripe idempotency conflictDuplicate key on retryUse unique idempotency keys per transaction
Mercury timeout on bulk exportLarge date range querySplit into monthly chunks

Resources

Next Steps

See fondo-performance-tuning.

저장한 Skills