Skillshub canva-rate-limits
install
source · Clone the upstream repo
git clone https://github.com/ComeOnOliver/skillshub
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/jeremylongshore/claude-code-plugins-plus-skills/canva-rate-limits" ~/.claude/skills/comeonoliver-skillshub-canva-rate-limits && rm -rf "$T"
manifest:
skills/jeremylongshore/claude-code-plugins-plus-skills/canva-rate-limits/SKILL.mdsource content
Canva Rate Limits
Overview
The Canva Connect API enforces per-user, per-endpoint rate limits. Each endpoint has different thresholds. A 429 response means you must wait before retrying.
Canva Connect API Rate Limits
| Endpoint | Method | Limit |
|---|---|---|
| GET | 10 req/min |
| GET | 10 req/min |
| GET | 100 req/min |
| POST | 20 req/min |
| GET | 100 req/min |
| POST | 75 req/5min, 500/24hr per user |
(integration) | POST | 750 req/5min, 5000/24hr |
(per document) | POST | 75 req/5min |
| POST | 30 req/min |
| POST | 60 req/min |
| POST | 20 req/min |
| GET | 100 req/min |
All limits are per user of your integration unless noted otherwise.
Exponential Backoff with Jitter
async function canvaRequestWithBackoff<T>( fn: () => Promise<T>, config = { maxRetries: 5, baseDelayMs: 1000, maxDelayMs: 60000 } ): Promise<T> { for (let attempt = 0; attempt <= config.maxRetries; attempt++) { try { return await fn(); } catch (error: any) { if (attempt === config.maxRetries) throw error; // Only retry on 429 or 5xx const status = error.status || error.response?.status; if (status !== 429 && (status < 500 || status >= 600)) throw error; // Honor Retry-After header if present const retryAfter = error.headers?.get?.('Retry-After'); const delay = retryAfter ? parseInt(retryAfter) * 1000 : Math.min( config.baseDelayMs * Math.pow(2, attempt) + Math.random() * 1000, config.maxDelayMs ); console.warn(`Rate limited (attempt ${attempt + 1}/${config.maxRetries}). Waiting ${(delay / 1000).toFixed(1)}s`); await new Promise(r => setTimeout(r, delay)); } } throw new Error('Unreachable'); }
Queue-Based Rate Limiting
import PQueue from 'p-queue'; // Match per-user endpoint limits const canvaQueues = { designs: new PQueue({ concurrency: 1, interval: 3000, intervalCap: 1 }), // ~20/min exports: new PQueue({ concurrency: 1, interval: 4000, intervalCap: 1 }), // ~15/min (conservative) assets: new PQueue({ concurrency: 1, interval: 2000, intervalCap: 1 }), // ~30/min reads: new PQueue({ concurrency: 3, interval: 1000, intervalCap: 3 }), // ~100/min (shared reads) }; // Usage — automatically queued to stay under limits const design = await canvaQueues.designs.add(() => client.createDesign({ design_type: { type: 'custom', width: 1080, height: 1080 }, title: 'Queued' }) ); // Batch export with rate control const designIds = ['DAV1', 'DAV2', 'DAV3', 'DAV4', 'DAV5']; const exports = await Promise.all( designIds.map(id => canvaQueues.exports.add(() => client.createExport({ design_id: id, format: { type: 'pdf' } }) ) ) );
Rate Limit Monitor
class CanvaRateLimitTracker { private windows: Map<string, { count: number; resetAt: number }> = new Map(); track(endpoint: string, response: Response): void { const remaining = response.headers.get('X-RateLimit-Remaining'); const reset = response.headers.get('X-RateLimit-Reset'); if (remaining !== null) { this.windows.set(endpoint, { count: parseInt(remaining), resetAt: reset ? parseInt(reset) * 1000 : Date.now() + 60000, }); } } shouldThrottle(endpoint: string): boolean { const window = this.windows.get(endpoint); if (!window) return false; return window.count < 3 && Date.now() < window.resetAt; } getWaitMs(endpoint: string): number { const window = this.windows.get(endpoint); if (!window) return 0; return Math.max(0, window.resetAt - Date.now()); } report(): Record<string, { remaining: number; resetsIn: string }> { const report: Record<string, any> = {}; for (const [ep, w] of this.windows) { report[ep] = { remaining: w.count, resetsIn: `${Math.max(0, (w.resetAt - Date.now()) / 1000).toFixed(0)}s`, }; } return report; } }
Proactive Throttling
// Wrap the client to throttle before hitting limits async function throttledCanvaRequest<T>( tracker: CanvaRateLimitTracker, endpoint: string, fn: () => Promise<T> ): Promise<T> { if (tracker.shouldThrottle(endpoint)) { const waitMs = tracker.getWaitMs(endpoint); console.log(`Proactively waiting ${waitMs}ms for ${endpoint}`); await new Promise(r => setTimeout(r, waitMs)); } return fn(); }
Error Handling
| Scenario | Detection | Action |
|---|---|---|
| Single 429 | HTTP status | Wait seconds, retry |
| Sustained 429s | Multiple retries fail | Reduce request rate, increase backoff |
| Export quota hit | 500/24hr per user | Queue exports, spread across hours |
| Integration quota | 5000/24hr exports | Distribute across users |
Resources
Next Steps
For security configuration, see
canva-security-basics.