Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Load test Figma API integrations and plan for scale. Use when benchmarking API throughput, testing rate limit behavior, or planning capacity for high-volume Figma integrations. Trigger with phrases like "figma load test", "figma scale", "figma benchmark", "figma capacity", "figma throughput".
.claude/skills/jeremylongshore-figma-load-scale/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 23% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 75% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 43% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 65% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 74% | 0% |
Test and plan for the throughput limits of your Figma API integration. Figma's rate limits use a leaky bucket algorithm -- this skill helps you find the bucket size for your plan tier and design your integration to stay within it.
brew install k6 or apt install k6)javascript// figma-load-test.js import http from 'k6/http'; import { check, sleep } from 'k6'; import { Rate, Trend } from 'k6/metrics'; const figmaErrors = new Rate('figma_errors'); const figmaLatency = new Trend('figma_latency', true); export const options = { scenarios: { // Test 1: Find your rate limit ceiling rate_limit_probe: { executor: 'constant-arrival-rate', rate: 10, // 10 requests per second timeUnit: '1s', duration: '2m', preAllocatedVUs: 5, maxVUs: 20, }, }, thresholds: { figma_errors: ['rate<0.10'], // Less than 10% errors figma_latency: ['p(95)<3000'], // P95 under 3 seconds http_req_duration: ['p(99)<5000'], // P99 under 5 seconds }, }; const PAT = __ENV.FIGMA_PAT; const FILE_KEY = __ENV.FIGMA_FILE_KEY; export default function () { // Use a lightweight endpoint for rate limit testing const res = http.get( `https://api.figma.com/v1/files/${FILE_KEY}?depth=1`, { headers: { 'X-Figma-Token': PAT }, tags: { endpoint: 'files' }, } ); figmaLatency.add(res.timings.duration); const isError = res.status !== 200; figmaErrors.add(isError); check(res, { 'status is 200': (r) => r.status === 200, 'not rate limited': (r) => r.status !== 429, 'latency < 2s': (r) => r.timings.duration < 2000, }); if (res.status === 429) { const retryAfter = parseInt(res.headers['Retry-After'] || '60'); console.log(`Rate limited. Retry-After: ${retryAfter}s`); sleep(retryAfter); } else { sleep(0.1); // 100ms between requests } }
bash# Probe rate limits k6 run \ --env FIGMA_PAT="${FIGMA_PAT}" \ --env FIGMA_FILE_KEY="${FIGMA_FILE_KEY}" \ figma-load-test.js # Export results to JSON for analysis k6 run \ --env FIGMA_PAT="${FIGMA_PAT}" \ --env FIGMA_FILE_KEY="${FIGMA_FILE_KEY}" \ --out json=results.json \ figma-load-test.js
typescriptinterface FigmaCapacityPlan { planTier: string; measuredLimitPerMinute: number; currentUsagePerMinute: number; headroomPercent: number; recommendation: string; } function planCapacity( measuredLimit: number, currentUsage: number, planTier: string ): FigmaCapacityPlan { const headroom = ((measuredLimit - currentUsage) / measuredLimit) * 100; let recommendation: string; if (headroom > 50) { recommendation = 'Adequate capacity. Monitor monthly.'; } else if (headroom > 20) { recommendation = 'Approaching limits. Implement caching and batching.'; } else { recommendation = 'Near capacity. Upgrade plan or reduce request volume.'; } return { planTier, measuredLimitPerMinute: measuredLimit, currentUsagePerMinute: currentUsage, headroomPercent: Math.round(headroom), recommendation, }; }
typescript// Strategy 1: Request coalescing // Multiple callers requesting the same file get a single API call class RequestCoalescer { private pending = new Map<string, Promise<any>>(); async get(key: string, fetcher: () => Promise<any>): Promise<any> { if (this.pending.has(key)) { return this.pending.get(key)!; } const promise = fetcher().finally(() => this.pending.delete(key)); this.pending.set(key, promise); return promise; } } const coalescer = new RequestCoalescer(); // 10 simultaneous requests for the same file = 1 API call const results = await Promise.all( Array(10).fill(null).map(() => coalescer.get(fileKey, () => figmaClient.getFile(fileKey)) ) ); // Strategy 2: Stagger requests across time import PQueue from 'p-queue'; const figmaQueue = new PQueue({ concurrency: 3, interval: 1000, intervalCap: 5, // Max 5 requests per second }); // Strategy 3: Pre-fetch during off-peak hours // Run design token sync at 3 AM, cache results for the day
markdown## Figma API Benchmark Report **Date:** YYYY-MM-DD **Plan:** [Starter/Pro/Org/Enterprise] **Seat:** [Full/Collab/Viewer] ### Rate Limit Findings | Endpoint | Measured Limit/min | First 429 At | Retry-After | |----------|-------------------|--------------|-------------| | GET /v1/files/:key?depth=1 | ~30 | Request #31 | 60s | | GET /v1/files/:key/nodes | ~30 | Request #32 | 60s | | GET /v1/images/:key | ~20 | Request #21 | 60s | ### Latency | Endpoint | P50 | P95 | P99 | |----------|-----|-----|-----| | /v1/files (depth=1) | 200ms | 500ms | 1200ms | | /v1/files (full) | 800ms | 2000ms | 4000ms | | /v1/images | 300ms | 800ms | 1500ms | ### Recommendations - Cache file metadata (changes infrequently) - Use webhooks instead of polling - Batch node IDs in single requests - Use `depth=1` unless full tree is needed
| Issue | Cause | Solution | |-------|-------|----------| | All requests 429'd | Rate too aggressive | Start lower, ramp gradually | | Inconsistent limits | Shared rate limit bucket | Other services using same token | | k6 connection errors | Too many parallel VUs | Reduce preAllocatedVUs | | Results vary between runs | Leaky bucket state | Wait 5min between test runs |
Run the Step 1 k6 script against a staging file and read the two numbers that matter:
bashk6 run --vus 5 --duration 2m figma-load-test.js
texthttp_req_duration..............: avg=412ms p(95)=890ms http_req_failed................: 2.1% (all 429 — rate limit ceiling found) figma_rate_limited.............: 27 ✗ first 429 at ~55 req/min sustained
That output feeds Step 3 capacity planning directly: at ~55 req/min per token before 429s, a 10,000-file nightly sync needs batching via /nodes?ids= (Step 4) or multiple OAuth users — not more concurrency.
Record results in the benchmark template: references/benchmark-report-template.md.
For reliability patterns, see figma-reliability-patterns.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 19,473 | 19,397 | -0% | 1 | 1 | 0% | 4,102 | 5,034 | +23% | 0 | 0 | — |
case-02 | fail→fail | 20,200 | 17,006 | -16% | 1 | 1 | 0% | 4,261 | 5,001 | +17% | 0 | 0 | — |
case-03 | fail→fail | 19,059 | 17,264 | -9% | 1 | 1 | 0% | 3,626 | 5,692 | +57% | 0 | 0 | — |
case-04 | fail→pass | 16,353 | 12,714 | -22% | 1 | 1 | 0% | 2,601 | 4,554 | +75% | 0 | 0 | — |
case-05 | pass→pass | 15,600 | 10,989 | -30% | 1 | 1 | 0% | 2,871 | 3,369 | +17% | 0 | 0 | — |
case-06 | pass→pass | 13,505 | 9,130 | -32% | 1 | 1 | 0% | 2,321 | 3,525 | +52% | 0 | 0 | — |
case-07 | pass→pass | 6,901 | 1,766 | -74% | 1 | 1 | 0% | 666 | 2,506 | +276% | 0 | 0 | — |
case-08 | pass→pass | 9,866 | 2,197 | -78% | 1 | 1 | 0% | 1,728 | 2,569 | +49% | 0 | 0 | — |
case-09 | fail→pass | 11,873 | 4,281 | -64% | 1 | 1 | 0% | 2,032 | 2,908 | +43% | 0 | 0 | — |
case-10 | pass→pass | 8,740 | 6,443 | -26% | 1 | 1 | 0% | 1,774 | 3,411 | +92% | 0 | 0 | — |
case-11 | fail→pass | 12,198 | 3,242 | -73% | 1 | 1 | 0% | 1,699 | 2,798 | +65% | 0 | 0 | — |
case-12 | fail→pass | 10,679 | 3,376 | -68% | 1 | 1 | 0% | 1,523 | 2,655 | +74% | 0 | 0 | — |
case-13 | pass→fail | 12,620 | 16,899 | +34% | 1 | 1 | 0% | 2,249 | 4,696 | +109% | 0 | 0 | — |
case-14 | pass→pass | 4,759 | 1,724 | -64% | 1 | 1 | 0% | 686 | 2,504 | +265% | 0 | 0 | — |
case-15 | fail→pass | 14,393 | 3,789 | -74% | 1 | 1 | 0% | 2,199 | 3,021 | +37% | 0 | 0 | — |
case-16 | pass→pass | 17,413 | 5,323 | -69% | 1 | 1 | 0% | 2,377 | 2,979 | +25% | 0 | 0 | — |
case-17 | pass→pass | 750,165 | 6,326 | -99% | 1 | 1 | 0% | 2,710 | 3,076 | +14% | 0 | 0 | — |
case-18 | fail→pass | 14,981 | 6,072 | -59% | 1 | 1 | 0% | 2,368 | 3,184 | +34% | 0 | 0 | — |
case-19 | fail→pass | 10,130 | 3,557 | -65% | 1 | 1 | 0% | 1,647 | 2,776 | +69% | 0 | 0 | — |
case-20 | fail→pass | 11,268 | 1,625 | -86% | 1 | 1 | 0% | 1,923 | 2,381 | +24% | 0 | 0 | — |
case-21 | pass→pass | 10,753 | 10,925 | +2% | 1 | 1 | 0% | 2,296 | 4,504 | +96% | 0 | 0 | — |
case-22 | fail→pass | 15,772 | 15,844 | +0% | 1 | 1 | 0% | 2,876 | 5,317 | +85% | 0 | 0 | — |
case-23 | pass→pass | 12,538 | 12,793 | +2% | 1 | 1 | 0% | 2,485 | 4,950 | +99% | 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. 23 cases were attempted. The headline lift of +39 percentage points is the difference between those two pass rates over the 23 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
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.