Agent Skills: Canva Cost Tuning

|

UncategorizedID: jeremylongshore/claude-code-plugins-plus-skills/canva-cost-tuning

Install this agent skill to your local

pnpm dlx add-skill https://github.com/jeremylongshore/claude-code-plugins-plus-skills/tree/HEAD/plugins/saas-packs/canva-pack/skills/canva-cost-tuning

Skill Files

Browse the full folder contents for canva-cost-tuning.

Download Skill

Loading file tree…

plugins/saas-packs/canva-pack/skills/canva-cost-tuning/SKILL.md

Skill Metadata

Name
canva-cost-tuning
Description
|

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 query parameter for server-side search
  • [ ] 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 query parameter |

Resources

Next Steps

For architecture patterns, see canva-reference-architecture.