Claude-code-plugins-plus canva-load-scale

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-load-scale" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-canva-load-scale && rm -rf "$T"
manifest: plugins/saas-packs/canva-pack/skills/canva-load-scale/SKILL.md
source content

Canva Load & Scale

Overview

Load test and scale Canva Connect API integrations. Since Canva enforces per-user rate limits, scaling means distributing load across users, not increasing per-user throughput.

Canva Rate Limit Constraints

OperationPer-User LimitImplication
Create design20/minMax 1,200 designs/hr per user
List designs100/minGenerous for reads
Create export75/5min (500/24hr)Max 500 exports/day per user
Integration export750/5min (5,000/24hr)Shared across all users
Upload asset30/minMax 1,800/hr per user
Autofill60/minMax 3,600/hr per user

Key insight: The integration-wide export limit of 5,000/day across ALL users is the most constraining for high-volume scenarios.

k6 Load Test

// canva-load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate, Trend } from 'k6/metrics';

const errorRate = new Rate('canva_error_rate');
const exportDuration = new Trend('canva_export_duration');

export const options = {
  scenarios: {
    design_operations: {
      executor: 'ramping-vus',
      startVUs: 1,
      stages: [
        { duration: '1m', target: 5 },    // Ramp up slowly
        { duration: '3m', target: 5 },    // Steady state
        { duration: '1m', target: 10 },   // Test rate limits
        { duration: '3m', target: 10 },   // Sustained load
        { duration: '1m', target: 0 },    // Ramp down
      ],
    },
  },
  thresholds: {
    http_req_duration: ['p(95)<2000'],       // P95 < 2s
    canva_error_rate: ['rate<0.05'],          // < 5% errors
    canva_export_duration: ['p(95)<30000'],   // Exports < 30s
  },
};

const BASE = 'https://api.canva.com/rest/v1';
const TOKEN = __ENV.CANVA_ACCESS_TOKEN;
const headers = {
  'Authorization': `Bearer ${TOKEN}`,
  'Content-Type': 'application/json',
};

export default function () {
  // 1. List designs (high rate limit — safe to call frequently)
  const listRes = http.get(`${BASE}/designs?limit=5`, { headers });
  check(listRes, { 'list 200': (r) => r.status === 200 });
  errorRate.add(listRes.status !== 200);

  if (listRes.status === 429) {
    const retryAfter = parseInt(listRes.headers['Retry-After'] || '60');
    sleep(retryAfter);
    return;
  }

  // 2. Create a design (20/min limit)
  const createRes = http.post(`${BASE}/designs`, JSON.stringify({
    design_type: { type: 'custom', width: 100, height: 100 },
    title: `k6 test ${Date.now()}`,
  }), { headers });

  check(createRes, { 'create 200': (r) => r.status === 200 });
  errorRate.add(createRes.status !== 200);

  if (createRes.status === 200) {
    const designId = createRes.json('design.id');

    // 3. Export (75/5min limit — most constrained)
    const exportStart = Date.now();
    const exportRes = http.post(`${BASE}/exports`, JSON.stringify({
      design_id: designId,
      format: { type: 'png' },
    }), { headers });

    if (exportRes.status === 200) {
      const jobId = exportRes.json('job.id');

      // Poll for completion
      let status = 'in_progress';
      while (status === 'in_progress') {
        sleep(2);
        const pollRes = http.get(`${BASE}/exports/${jobId}`, { headers });
        status = pollRes.json('job.status');
      }

      exportDuration.add(Date.now() - exportStart);
    }
  }

  sleep(3); // Stay under rate limits
}

Run Load Test

k6 run --env CANVA_ACCESS_TOKEN="${CANVA_ACCESS_TOKEN}" canva-load-test.js

# With Grafana/InfluxDB output
k6 run --out influxdb=http://localhost:8086/k6 canva-load-test.js

Scaling Architecture

Users requesting designs
       │
       ▼
┌─────────────┐
│   Load      │
│   Balancer  │
└──────┬──────┘
       │
       ▼
┌─────────────┐     ┌─────────────┐
│  App Pod 1  │     │  App Pod N  │
│  (per-user  │ ... │  (per-user  │
│   tokens)   │     │   tokens)   │
└──────┬──────┘     └──────┬──────┘
       │                    │
       ▼                    ▼
┌─────────────────────────────────┐
│        Rate Limiter Queue       │
│   (respects per-user + global   │
│    Canva rate limits)           │
└──────────────┬──────────────────┘
               │
               ▼
        api.canva.com
         /rest/v1/*

Capacity Planning

function estimateCanvaCapacity(users: number): {
  designsPerDay: number;
  exportsPerDay: number;
  constrainingFactor: string;
} {
  const perUserExportDaily = 500;
  const integrationExportDaily = 5000;

  const totalUserExports = users * perUserExportDaily;
  const effectiveExports = Math.min(totalUserExports, integrationExportDaily);

  return {
    designsPerDay: users * 1200 * 8,  // 20/min * 60 * 8 work hours
    exportsPerDay: effectiveExports,
    constrainingFactor: effectiveExports === integrationExportDaily
      ? `Integration-wide limit: ${integrationExportDaily}/day (hit at ${Math.ceil(integrationExportDaily / perUserExportDaily)} users)`
      : `Per-user limit: ${perUserExportDaily}/day per user`,
  };
}

// Example
const cap = estimateCanvaCapacity(20);
console.log(`Exports/day: ${cap.exportsPerDay}`);
console.log(`Constraint: ${cap.constrainingFactor}`);
// Exports/day: 5000 (integration limit)
// Constraint: Integration-wide limit: 5000/day (hit at 10 users)

HPA Configuration

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: canva-integration-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: canva-integration
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Pods
      pods:
        metric:
          name: canva_export_queue_depth
        target:
          type: AverageValue
          averageValue: 50

Error Handling

IssueCauseSolution
k6 all 429sRate limit hitIncrease sleep between iterations
Integration quota hit> 5000 exports/dayContact Canva for limit increase
Export timeoutsComplex designsIncrease poll timeout
Inconsistent resultsCold startAdd warm-up phase

Resources

Next Steps

For reliability patterns, see

canva-reliability-patterns
.