Claude-code-plugins-plus canva-cost-tuning
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/canva-pack/skills/canva-cost-tuning" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-canva-cost-tuning && rm -rf "$T"
manifest:
plugins/saas-packs/canva-pack/skills/canva-cost-tuning/SKILL.mdsource content
Canva Cost Tuning
Overview
Optimize Canva Connect API usage. While the Connect API itself is free to call, rate limits constrain throughput. Canva Enterprise (required for autofill) has per-seat licensing costs. Optimize by reducing unnecessary calls, caching effectively, and batching operations.
Canva Pricing Model
| Tier | Cost | Connect API Access | Autofill API | Brand Templates |
|---|---|---|---|---|
| Canva Free | $0/user | Yes | No | No |
| Canva Pro | $15/user/mo | Yes | No | No |
| Canva Teams | $10/user/mo (5+) | Yes | No | Limited |
| Canva Enterprise | Custom | Yes | Yes | Yes |
Key insight: The REST API is free — costs come from Canva subscriptions. Autofill and brand template APIs require Enterprise.
API Call Reduction Strategies
Cache Design Metadata
// Design metadata rarely changes — cache aggressively // Save: ~100 GET /designs/{id} calls/min per user const designMetadata = await cachedCanvaCall( `design:${designId}`, () => canvaAPI(`/designs/${designId}`, token), 300 // 5 min TTL );
Avoid Redundant Exports
// Track exported designs to prevent duplicate exports class ExportTracker { private exportedDesigns = new Map<string, { urls: string[]; expiresAt: number }>(); async exportIfNeeded(designId: string, format: object, token: string): Promise<string[]> { const cached = this.exportedDesigns.get(designId); // Export URLs valid for 24 hours — reuse if still valid if (cached && Date.now() < cached.expiresAt) { return cached.urls; } const { job } = await canvaAPI('/exports', token, { method: 'POST', body: JSON.stringify({ design_id: designId, format }), }); const urls = await pollExport(job.id, token); this.exportedDesigns.set(designId, { urls, expiresAt: Date.now() + 23 * 60 * 60 * 1000, // 23 hours (1h buffer) }); return urls; } }
Pagination with Early Exit
// Stop listing when you find what you need async function findDesignByTitle(title: string, token: string): Promise<any | null> { let continuation: string | undefined; do { const params = new URLSearchParams({ query: title, // Use server-side search instead of client filtering limit: '25', ...(continuation && { continuation }), }); const data = await canvaAPI(`/designs?${params}`, token); const match = data.items.find((d: any) => d.title === title); if (match) return match; // Early exit — don't fetch remaining pages continuation = data.continuation; } while (continuation); return null; }
Usage Monitoring
class CanvaUsageTracker { private calls: Map<string, number> = new Map(); track(endpoint: string): void { const key = `${new Date().toISOString().slice(0, 13)}:${endpoint}`; // Hourly bucket this.calls.set(key, (this.calls.get(key) || 0) + 1); } report(): { endpoint: string; callsPerHour: number }[] { const hourly: Record<string, number> = {}; for (const [key, count] of this.calls) { const endpoint = key.split(':').slice(1).join(':'); hourly[endpoint] = (hourly[endpoint] || 0) + count; } return Object.entries(hourly) .map(([endpoint, callsPerHour]) => ({ endpoint, callsPerHour })) .sort((a, b) => b.callsPerHour - a.callsPerHour); } }
Optimization Checklist
- Design metadata cached (5+ min TTL)
- Brand template list cached (1+ hour TTL)
- Export URLs reused within 24-hour window
- Pagination uses
parameter for server-side searchquery - Thumbnail URLs refreshed only when displayed (15-min expiry)
- Asset uploads deduplicated (don't re-upload same file)
- Autofill results cached by template+data hash
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| Rate limits hit frequently | Too many calls | Add caching layer |
| Export quota exceeded | Duplicate exports | Track and reuse URLs |
| Autofill not available | Not Enterprise tier | Upgrade Canva plan |
| Slow list queries | No search filter | Use parameter |
Resources
Next Steps
For architecture patterns, see
canva-reference-architecture.