Claude-code-plugins-plus figma-rate-limits
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/figma-pack/skills/figma-rate-limits" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-figma-rate-limits && rm -rf "$T"
manifest:
plugins/saas-packs/figma-pack/skills/figma-rate-limits/SKILL.mdsource content
Figma Rate Limits
Overview
Figma uses a leaky bucket algorithm for rate limiting. When the bucket is full, the API returns 429 with a
Retry-After header. Limits vary by plan tier, seat type, and endpoint tier.
Prerequisites
- Figma REST API integration working
- Understanding of async/await patterns
Instructions
Step 1: Understand the Rate Limit Model
Endpoint tiers (limits are per-user, per-minute):
| Tier | Endpoints | Typical Limit |
|---|---|---|
| Tier 1 | , | Higher quota |
| Tier 2 | , | Moderate quota |
| Tier 3 | , | Lower quota |
429 response headers:
| Header | Type | Meaning |
|---|---|---|
| Integer (seconds) | Wait this long before retrying |
| String | Your Figma plan level |
| String | or rate limit |
| String | URL to upgrade for higher limits |
Step 2: Implement Exponential Backoff
async function figmaFetchWithRetry( path: string, token: string, maxRetries = 5 ): Promise<any> { for (let attempt = 0; attempt <= maxRetries; attempt++) { const res = await fetch(`https://api.figma.com${path}`, { headers: { 'X-Figma-Token': token }, }); if (res.status === 429) { const retryAfter = parseInt(res.headers.get('Retry-After') || '60'); const limitType = res.headers.get('X-Figma-Rate-Limit-Type') || 'unknown'; if (attempt === maxRetries) { throw new Error(`Rate limited after ${maxRetries} retries (${limitType})`); } // Use the Retry-After header -- Figma tells you exactly how long to wait const jitter = Math.random() * 1000; const delay = retryAfter * 1000 + jitter; console.warn(`429 (${limitType}). Waiting ${(delay/1000).toFixed(1)}s (attempt ${attempt + 1})`); await new Promise(r => setTimeout(r, delay)); continue; } if (res.status >= 500 && attempt < maxRetries) { // Server errors: exponential backoff without Retry-After const delay = Math.min(1000 * Math.pow(2, attempt), 30000); await new Promise(r => setTimeout(r, delay)); continue; } if (!res.ok) { throw new Error(`Figma API error: ${res.status} ${await res.text()}`); } return res.json(); } }
Step 3: Request Queue with Concurrency Control
import PQueue from 'p-queue'; // Limit concurrent requests to avoid bursting the bucket const figmaQueue = new PQueue({ concurrency: 3, // max 3 parallel requests interval: 1000, // per second intervalCap: 5, // max 5 requests per second }); async function queuedFigmaRequest<T>( path: string, token: string ): Promise<T> { return figmaQueue.add(() => figmaFetchWithRetry(path, token)); } // Usage -- all requests are automatically queued and throttled const [file, comments, images] = await Promise.all([ queuedFigmaRequest(`/v1/files/${fileKey}`, token), queuedFigmaRequest(`/v1/files/${fileKey}/comments`, token), queuedFigmaRequest(`/v1/images/${fileKey}?ids=0:1&format=svg`, token), ]);
Step 4: Rate Limit Monitor
class FigmaRateLimitMonitor { private requestLog: number[] = []; private windowMs = 60_000; // 1 minute window recordRequest() { this.requestLog.push(Date.now()); // Trim old entries const cutoff = Date.now() - this.windowMs; this.requestLog = this.requestLog.filter(t => t > cutoff); } getRequestsInWindow(): number { const cutoff = Date.now() - this.windowMs; return this.requestLog.filter(t => t > cutoff).length; } shouldThrottle(safetyMargin = 0.8): boolean { // If we've used 80% of a conservative estimate, slow down const estimatedLimit = 30; // Conservative estimate return this.getRequestsInWindow() > estimatedLimit * safetyMargin; } } const monitor = new FigmaRateLimitMonitor(); // Wrap every request async function monitoredFigmaFetch(path: string, token: string) { if (monitor.shouldThrottle()) { console.warn('Approaching rate limit, adding delay'); await new Promise(r => setTimeout(r, 2000)); } monitor.recordRequest(); return figmaFetchWithRetry(path, token); }
Step 5: Batch Node Requests
// Instead of N individual /v1/files/:key/nodes requests, // batch node IDs into fewer requests async function batchFetchNodes( fileKey: string, nodeIds: string[], batchSize = 50, token: string ) { const results: Record<string, any> = {}; for (let i = 0; i < nodeIds.length; i += batchSize) { const batch = nodeIds.slice(i, i + batchSize); const ids = encodeURIComponent(batch.join(',')); const data = await queuedFigmaRequest( `/v1/files/${fileKey}/nodes?ids=${ids}`, token ); Object.assign(results, data.nodes); } return results; }
Output
- Automatic retry with
header complianceRetry-After - Request queue preventing burst overload
- Rate limit monitoring with proactive throttling
- Batch operations reducing total request count
Error Handling
| Scenario | Detection | Response |
|---|---|---|
| Single 429 | header | Wait exactly that duration |
| Repeated 429s | Multiple retries exhausted | Log, alert, back off longer |
rate limit type | | Consider upgrading Figma plan |
| Batch too large | 400 Bad Request | Reduce batch size to 50 IDs |
Resources
Next Steps
For security configuration, see
figma-security-basics.