Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Optimize Figma API usage to minimize costs and stay within plan limits. Use when analyzing request volumes, reducing unnecessary API calls, or choosing the right Figma plan for your integration needs. Trigger with phrases like "figma cost", "figma pricing", "reduce figma API calls", "figma plan limits", "figma budget".
.claude/skills/jeremylongshore-figma-cost-tuning/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 20% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 32% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 68% | 0% |
| case-18 | ✗→✓ | ▲ Improved | 37% | 0% |
| case-19 | ✗→✓ | ▲ Improved | 58% | 0% |
Optimize Figma API usage costs. Figma's REST API rate limits are determined by plan tier and seat type. Reducing unnecessary requests keeps you within limits and avoids upgrading prematurely.
Figma rate limits vary by plan tier and seat type:
| Plan | Seat Types | Rate Limit Tier | Variables API | |------|------------|----------------|---------------| | Starter (Free) | Free | Lowest | No | | Professional | Full, Viewer | Standard | No | | Organization | Full, Collab, Viewer | Higher | No | | Enterprise | Full, Collab, Viewer | Highest | Yes |
Key facts:
/v1/files/:key/variables/*) requires Enterprisetypescript// Instrument all Figma API calls to track volume class FigmaUsageTracker { private calls: Array<{ endpoint: string; timestamp: number; cached: boolean }> = []; record(endpoint: string, cached: boolean) { this.calls.push({ endpoint, timestamp: Date.now(), cached }); } getReport(windowMs = 24 * 60 * 60 * 1000) { const cutoff = Date.now() - windowMs; const recent = this.calls.filter(c => c.timestamp > cutoff); // Group by endpoint const byEndpoint = new Map<string, { total: number; cached: number }>(); for (const call of recent) { const key = call.endpoint.replace(/[a-zA-Z0-9]{20,}/, ':key'); const entry = byEndpoint.get(key) || { total: 0, cached: 0 }; entry.total++; if (call.cached) entry.cached++; byEndpoint.set(key, entry); } return { totalCalls: recent.length, cachedCalls: recent.filter(c => c.cached).length, cacheHitRate: recent.length > 0 ? (recent.filter(c => c.cached).length / recent.length * 100).toFixed(1) + '%' : '0%', byEndpoint: Object.fromEntries(byEndpoint), }; } } const tracker = new FigmaUsageTracker();
typescript// 1. Use depth parameter to avoid fetching full file trees // Saves bandwidth and processing time const fileMeta = await figmaFetch(`/v1/files/${key}?depth=1`); // 2. Batch node IDs into single requests // Instead of 50 individual /nodes calls, make 1 call with 50 IDs const ids = nodeIds.join(','); await figmaFetch(`/v1/files/${key}/nodes?ids=${ids}`); // 3. Cache with webhooks instead of polling // Polling every 30s = 2,880 calls/day per file // Webhooks = 0 polling calls (events push to you) // 4. Cache image URLs (they're valid for 30 days) // Re-rendering the same nodes wastes Tier 1 quota // 5. Use GET /v1/files/:key?depth=1 to check lastModified // before fetching the full file (skip if unchanged) async function fetchFileIfChanged( fileKey: string, lastKnownVersion: string, token: string ) { const meta = await fetch( `https://api.figma.com/v1/files/${fileKey}?depth=1`, { headers: { 'X-Figma-Token': token } } ).then(r => r.json()); if (meta.version === lastKnownVersion) { console.log('File unchanged, skipping full fetch'); return null; } // File changed -- fetch the full version return fetch( `https://api.figma.com/v1/files/${fileKey}`, { headers: { 'X-Figma-Token': token } } ).then(r => r.json()); }
Polling Architecture (expensive):
App → GET /v1/files/:key every 30s → 2,880 calls/day/file
Webhook Architecture (efficient):
Figma → POST /webhooks/figma (only when file changes)
App → GET /v1/files/:key (only after webhook) → ~10-50 calls/day/file
Savings: 95%+ fewer API callstypescript// Log API calls to a database for analysis interface ApiCallLog { timestamp: Date; endpoint: string; fileKey: string; status: number; latencyMs: number; cached: boolean; } // Monthly usage summary function getMonthlyReport(logs: ApiCallLog[]) { const now = new Date(); const monthStart = new Date(now.getFullYear(), now.getMonth(), 1); const monthLogs = logs.filter(l => l.timestamp >= monthStart); return { totalRequests: monthLogs.length, uniqueFiles: new Set(monthLogs.map(l => l.fileKey)).size, cacheHitRate: monthLogs.filter(l => l.cached).length / monthLogs.length, errorRate: monthLogs.filter(l => l.status >= 400).length / monthLogs.length, topEndpoints: Object.entries( monthLogs.reduce((acc, l) => { acc[l.endpoint] = (acc[l.endpoint] || 0) + 1; return acc; }, {} as Record<string, number>) ).sort(([,a], [,b]) => b - a).slice(0, 5), }; }
depth parameter| Issue | Cause | Solution | |-------|-------|----------| | Hitting rate limits often | No caching or batching | Implement caching + batch requests | | Variables API 403 | Not on Enterprise plan | Use styles API (free on all plans) | | High bandwidth costs | Fetching full file trees | Use depth=1 and /nodes endpoint | | Polling waste | No webhooks configured | Set up FILE_UPDATE webhook |
Find your most expensive call pattern with the Step 2 usage tracker, then apply the Step 3 fix:
textTop API consumers (last 24h) /v1/files/{key} 1,847 calls ← full-tree fetches from the preview service /v1/files/{key}/nodes 312 calls /v1/images/{key} 119 calls
bash# Before: full tree (~4 MB, counts hard against the budget) curl -s -H "X-Figma-Token: ${FIGMA_PAT}" "https://api.figma.com/v1/files/${FIGMA_FILE_KEY}" # After: shallow fetch (~40 KB) — same top-level structure the preview needs curl -s -H "X-Figma-Token: ${FIGMA_PAT}" "https://api.figma.com/v1/files/${FIGMA_FILE_KEY}?depth=1"
That single change cut the daily call payload ~99% for the hot path. Plan-tier limits and the dashboard query: references/understand-plan-based-rate-limits.md, references/usage-dashboard-query.md.
For architecture patterns, see figma-reference-architecture.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 29,729 | 18,605 | -37% | 1 | 1 | 0% | 4,575 | 5,506 | +20% | 0 | 0 | — |
case-02 | fail→pass | 28,336 | 26,499 | -6% | 1 | 1 | 0% | 4,775 | 6,291 | +32% | 0 | 0 | — |
case-03 | pass→pass | 12,635 | 8,009 | -37% | 1 | 1 | 0% | 1,764 | 3,404 | +93% | 0 | 0 | — |
case-04 | pass→pass | 10,451 | 9,915 | -5% | 1 | 1 | 0% | 1,941 | 3,772 | +94% | 0 | 0 | — |
case-05 | pass→pass | 3,944 | 4,830 | +22% | 1 | 1 | 0% | 699 | 2,927 | +319% | 0 | 0 | — |
case-06 | pass→pass | 15,427 | 9,725 | -37% | 1 | 1 | 0% | 2,283 | 3,765 | +65% | 0 | 0 | — |
case-07 | pass→pass | 8,455 | 7,285 | -14% | 1 | 1 | 0% | 1,474 | 3,393 | +130% | 0 | 0 | — |
case-08 | fail→pass | 12,629 | 7,909 | -37% | 1 | 1 | 0% | 1,894 | 3,187 | +68% | 0 | 0 | — |
case-09 | pass→pass | 15,498 | 10,002 | -35% | 1 | 1 | 0% | 2,151 | 3,429 | +59% | 0 | 0 | — |
case-10 | fail→fail | 16,582 | 18,314 | +10% | 1 | 1 | 0% | 3,429 | 5,167 | +51% | 0 | 0 | — |
case-11 | pass→pass | 14,111 | 15,217 | +8% | 1 | 1 | 0% | 2,855 | 4,545 | +59% | 0 | 0 | — |
case-12 | pass→pass | 13,689 | 7,713 | -44% | 1 | 1 | 0% | 2,035 | 3,221 | +58% | 0 | 0 | — |
case-13 | fail→fail | 16,192 | 13,620 | -16% | 1 | 1 | 0% | 2,410 | 4,554 | +89% | 0 | 0 | — |
case-14 | fail→fail | 7,915 | 4,996 | -37% | 1 | 1 | 0% | 1,181 | 2,857 | +142% | 0 | 0 | — |
case-15 | pass→pass | 9,538 | 5,273 | -45% | 1 | 1 | 0% | 1,739 | 2,770 | +59% | 0 | 0 | — |
case-16 | pass→pass | 8,955 | 8,881 | -1% | 1 | 1 | 0% | 2,019 | 3,926 | +94% | 0 | 0 | — |
case-17 | pass→pass | 234,355 | 5,808 | -98% | 1 | 1 | 0% | 1,424 | 2,845 | +100% | 0 | 0 | — |
case-18 | fail→pass | 18,231 | 933,992 | +5023% | 1 | 1 | 0% | 2,952 | 4,058 | +37% | 0 | 0 | — |
case-19 | fail→pass | 14,271 | 13,476 | -6% | 1 | 1 | 0% | 3,067 | 4,857 | +58% | 0 | 0 | — |
case-20 | fail→fail | 19,584 | 16,503 | -16% | 1 | 1 | 0% | 3,330 | 5,000 | +50% | 0 | 0 | — |
case-21 | pass→pass | 12,844 | 9,777 | -24% | 1 | 1 | 0% | 2,563 | 4,023 | +57% | 0 | 0 | — |
case-22 | pass→pass | 9,346 | 9,013 | -4% | 1 | 1 | 0% | 2,032 | 3,921 | +93% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +23 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.