---
name: jeremylongshore/canva-cost-tuning
source: https://app.decimal.ai/s/jeremylongshore-canva-cost-tuning@1/SKILL.md
source_sha256: b50ed9b719aa
---

# 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

```typescript
// 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

```typescript
// 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

```typescript
// 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

```typescript
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

- [Canva Pricing](https://www.canva.com/pricing/)
- [Canva Enterprise](https://www.canva.com/enterprise/)
- [API Rate Limits](https://www.canva.dev/docs/connect/api-requests-responses/)

## Next Steps

For architecture patterns, see `canva-reference-architecture`.