Agent Skills: Canva Advanced Troubleshooting

|

UncategorizedID: jeremylongshore/claude-code-plugins-plus-skills/canva-advanced-troubleshooting

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-advanced-troubleshooting

Skill Files

Browse the full folder contents for canva-advanced-troubleshooting.

Download Skill

Loading file tree…

plugins/saas-packs/canva-pack/skills/canva-advanced-troubleshooting/SKILL.md

Skill Metadata

Name
canva-advanced-troubleshooting
Description
|

Canva Advanced Troubleshooting

Overview

Deep debugging for complex Canva Connect API issues — intermittent 5xx errors, stuck export jobs, OAuth token rotation failures, rate limit edge cases, and webhook delivery gaps.

Systematic Layer Testing

interface LayerTest {
  layer: string;
  test: () => Promise<{ pass: boolean; details: string; durationMs: number }>;
}

async function diagnoseCanvaIssue(token: string): Promise<void> {
  const layers: LayerTest[] = [
    {
      layer: 'DNS',
      test: async () => {
        const start = Date.now();
        try {
          const { address } = await import('dns/promises').then(dns => dns.lookup('api.canva.com'));
          return { pass: true, details: `Resolved to ${address}`, durationMs: Date.now() - start };
        } catch (e: any) {
          return { pass: false, details: e.message, durationMs: Date.now() - start };
        }
      },
    },
    {
      layer: 'TLS',
      test: async () => {
        const start = Date.now();
        try {
          const res = await fetch('https://api.canva.com/rest/v1/users/me', {
            method: 'HEAD',
            signal: AbortSignal.timeout(5000),
          });
          return { pass: true, details: `TLS OK, HTTP ${res.status}`, durationMs: Date.now() - start };
        } catch (e: any) {
          return { pass: false, details: e.message, durationMs: Date.now() - start };
        }
      },
    },
    {
      layer: 'Auth',
      test: async () => {
        const start = Date.now();
        const res = await fetch('https://api.canva.com/rest/v1/users/me', {
          headers: { 'Authorization': `Bearer ${token}` },
        });
        return {
          pass: res.status === 200,
          details: `HTTP ${res.status}${res.status === 401 ? ' — token expired' : ''}`,
          durationMs: Date.now() - start,
        };
      },
    },
    {
      layer: 'Scope: design:meta:read',
      test: async () => {
        const start = Date.now();
        const res = await fetch('https://api.canva.com/rest/v1/designs?limit=1', {
          headers: { 'Authorization': `Bearer ${token}` },
        });
        return {
          pass: res.status === 200,
          details: res.status === 403 ? 'Scope not granted' : `HTTP ${res.status}`,
          durationMs: Date.now() - start,
        };
      },
    },
    {
      layer: 'Scope: design:content:write',
      test: async () => {
        const start = Date.now();
        const res = await fetch('https://api.canva.com/rest/v1/designs', {
          method: 'POST',
          headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
          body: JSON.stringify({ design_type: { type: 'custom', width: 100, height: 100 }, title: 'Diag Test' }),
        });
        return {
          pass: res.status === 200,
          details: res.status === 403 ? 'Scope not granted' : `HTTP ${res.status}`,
          durationMs: Date.now() - start,
        };
      },
    },
  ];

  console.log('=== Canva Connect API Layer Diagnostics ===\n');
  for (const { layer, test } of layers) {
    const result = await test();
    const icon = result.pass ? 'PASS' : 'FAIL';
    console.log(`[${icon}] ${layer}: ${result.details} (${result.durationMs}ms)`);
    if (!result.pass) {
      console.log(`  ^ First failure — layers below may fail due to this.\n`);
      break;
    }
  }
}

Export Job Debugging

// Debug stuck or failed export jobs
async function debugExportJob(exportId: string, token: string): Promise<void> {
  console.log(`\n=== Export Job Debug: ${exportId} ===`);

  const startTime = Date.now();
  let pollCount = 0;

  while (Date.now() - startTime < 120000) { // 2 min max
    pollCount++;
    const res = await fetch(`https://api.canva.com/rest/v1/exports/${exportId}`, {
      headers: { 'Authorization': `Bearer ${token}` },
    });

    const data = await res.json();
    const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);

    console.log(`[${elapsed}s] Poll #${pollCount}: status=${data.job.status}`);

    if (data.job.status === 'success') {
      console.log(`URLs (valid 24h): ${data.job.urls.length} files`);
      data.job.urls.forEach((url: string, i: number) => console.log(`  ${i + 1}. ${url.substring(0, 80)}...`));
      return;
    }

    if (data.job.status === 'failed') {
      console.error(`FAILED: ${data.job.error?.code} — ${data.job.error?.message}`);
      console.error('Common causes:');
      if (data.job.error?.code === 'license_required') {
        console.error('  -> Design contains premium elements. User needs Canva Pro.');
      } else if (data.job.error?.code === 'internal_failure') {
        console.error('  -> Canva server error. Retry after a delay.');
      }
      return;
    }

    await new Promise(r => setTimeout(r, 3000));
  }

  console.error('Export timed out after 2 minutes. Possible causes:');
  console.error('  - Very large or complex design');
  console.error('  - Canva export service under load');
  console.error('  - Video/animation exports take longer');
}

Token Lifecycle Debugging

async function debugTokenLifecycle(
  clientId: string,
  clientSecret: string,
  refreshToken: string
): Promise<void> {
  console.log('\n=== Token Lifecycle Debug ===');

  // 1. Try to refresh
  const basicAuth = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');

  const res = await fetch('https://api.canva.com/rest/v1/oauth/token', {
    method: 'POST',
    headers: {
      'Authorization': `Basic ${basicAuth}`,
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: new URLSearchParams({
      grant_type: 'refresh_token',
      refresh_token: refreshToken,
    }),
  });

  if (res.ok) {
    const data = await res.json();
    console.log(`[PASS] Token refresh successful`);
    console.log(`  Access token length: ${data.access_token.length} chars`);
    console.log(`  Expires in: ${data.expires_in} seconds (${(data.expires_in / 3600).toFixed(1)} hours)`);
    console.log(`  New refresh token: ${data.refresh_token ? 'YES (store this!)' : 'NO'}`);
  } else {
    const error = await res.json();
    console.log(`[FAIL] Token refresh failed: ${error.error}`);
    console.log(`  Description: ${error.error_description}`);
    console.log('');
    console.log('Common causes:');
    console.log('  - Refresh token already used (single-use)');
    console.log('  - User revoked access to your integration');
    console.log('  - Client credentials changed');
    console.log('  - Integration was deleted');
    console.log('');
    console.log('Resolution: User must re-authorize via OAuth flow');
  }
}

Network-Level Debug

#!/bin/bash
# Capture low-level Canva API interaction

echo "=== Network Debug ==="

# DNS resolution time
echo -n "DNS: "
dig api.canva.com +short +time=5 | tail -1

# TCP + TLS timing
echo "Connection timing:"
curl -w "DNS: %{time_namelookup}s\nTCP: %{time_connect}s\nTLS: %{time_appconnect}s\nTotal: %{time_total}s\n" \
  -o /dev/null -s \
  -H "Authorization: Bearer $CANVA_ACCESS_TOKEN" \
  "https://api.canva.com/rest/v1/users/me"

# HTTP/2 multiplexing check
echo -n "Protocol: "
curl -sI -H "Authorization: Bearer $CANVA_ACCESS_TOKEN" \
  "https://api.canva.com/rest/v1/users/me" | grep -i "^http/"

Support Escalation Template

## Canva Developer Support Request

**Integration ID:** [from Canva dashboard]
**Severity:** P[1-4]
**Timestamp:** [ISO 8601 when issue first observed]

### Issue Summary
[1-2 sentence description]

### Steps to Reproduce
1. Call POST /v1/exports with design_id: DAVxxx
2. Poll GET /v1/exports/{jobId}
3. Job stays in_progress for > 5 minutes then returns internal_failure

### Expected vs Actual
- Expected: Export completes within 30s
- Actual: Fails with internal_failure after 5 minutes

### Evidence
- Layer diagnostics output (attached)
- Export job ID: EXPxxx
- Response body: { "job": { "status": "failed", "error": { ... } } }

### Environment
- Node.js 20.x
- Region: us-east-1
- Traffic: ~50 exports/hour

Error Handling

| Issue | Cause | Solution | |-------|-------|----------| | Intermittent 5xx | Canva backend issue | Retry with backoff, file support ticket | | Export stuck in_progress | Large design or server load | Increase timeout to 120s | | Token refresh fails | Refresh token already used | Store new refresh token every time | | Webhook not arriving | URL unreachable from Canva | Check HTTPS, firewall, ngrok |

Resources

Next Steps

For load testing, see canva-load-scale.