Identify and avoid Canva Connect API anti-patterns and common integration mistakes.
Use when reviewing Canva code, onboarding developers,
or auditing existing Canva integrations for best practices violations.
Trigger with phrases like "canva mistakes", "canva anti-patterns",
"canva pitfalls", "canva what not to do", "canva code review".
Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
Identify and avoid Canva Connect API anti-patterns and common integration mistakes.
Use when reviewing Canva code, onboarding developers,
or auditing existing Canva integrations for best practices violations.
Trigger with phrases like "canva mistakes", "canva anti-patterns",
"canva pitfalls", "canva what not to do", "canva code review".
allowed-tools
Read, Grep
version
1.5.0
license
MIT
author
Jeremy Longshore <jeremy@intentsolutions.io>
tags
["saas","design","canva"]
compatibility
Designed for Claude Code
Canva Known Pitfalls
Overview
Common mistakes when integrating with the Canva Connect API. Each pitfall includes the anti-pattern, why it fails, and the correct approach with real API endpoints.
Pitfall #1: Not Handling Token Expiry
// WRONG — token expires after ~4 hours, then all calls failconst token = awaitgetTokenOnce();
// ... 5 hours later ...awaitcanvaAPI('/designs', token); // 401 Unauthorized// RIGHT — auto-refresh before expiryclassCanvaClient {
asyncrequest(path: string, init?: RequestInit) {
if (Date.now() > this.tokens.expiresAt - 300_000) {
awaitthis.refreshToken(); // Refresh 5 min before expiry
}
// ... make request
}
}
Pitfall #2: Reusing Refresh Tokens
// WRONG — refresh tokens are single-use in Canva's OAuthconst tokens = awaitrefreshAccessToken(storedRefreshToken);
// Later, using the SAME refresh token again:const tokens2 = awaitrefreshAccessToken(storedRefreshToken); // FAILS// RIGHT — always store the new refresh token immediately
tokens = (storedRefreshToken);
db.(userId, {
: tokens.,
: tokens.,
: .() + tokens. * ,
});
const
await
refreshAccessToken
await
saveTokens
accessToken
access_token
refreshToken
refresh_token
// NEW token — store it!
expiresAt
Date
now
expires_in
1000
Pitfall #3: Synchronous Export Polling in Request Handler
// WRONG — client secret exposed in browser// frontend.jsconst tokens = awaitfetch('https://api.canva.com/rest/v1/oauth/token', {
body: newURLSearchParams({
client_secret: 'EXPOSED_TO_USERS', // Anyone can see this// ...
}),
});
// RIGHT — token exchange MUST happen server-side// Canva docs: "Requests that require authenticating with your client ID// and client secret can't be made from a web-browser client"
Pitfall #7: Not Checking Enterprise Requirements
// WRONG — calling autofill without Enterprise, getting 403const result = awaitcanvaAPI('/autofills', token, { method: 'POST', body: ... });
// 403: "User must be a member of a Canva Enterprise organization"// RIGHT — check capabilities firstconst capabilities = awaitcanvaAPI('/users/me/capabilities', token);
if (!capabilities.capabilities?.includes('autofill')) {
thrownewError('Autofill requires Canva Enterprise subscription');
}
Pitfall #8: Not Validating Webhook Signatures
// WRONG — accepts any POST as a valid webhook
app.post('/webhooks/canva', (req, res) => {
processEvent(req.body); // Attacker can send fake events!
res.status(200).send();
});
// RIGHT — verify JWK signature
app.post('/webhooks/canva', express.text({ type: '*/*' }), async (req, res) => {
const payload = awaitverifyCanvaWebhook(req.body); // JWK verificationif (!payload) return res.status(401).send('Invalid');
res.status(200).send('OK'); // Return 200 firstprocessEvent(payload).catch(console.error); // Process async
});
Pitfall #9: Ignoring Blank Design Auto-Delete
// WRONG — create designs and expect them to persistconst { design } = awaitcanvaAPI('/designs', token, {
method: 'POST',
body: JSON.stringify({ design_type: { type: 'custom', width: 1080, height: 1080 } }),
});
// Design auto-deleted after 7 days if user never edits it!// RIGHT — warn users or track unedited designsawaitnotifyUser(`Edit your design before ${sevenDaysFromNow}: ${design.urls.edit_url}`);
Pitfall #10: Not Handling Export Failures
// WRONG — assumes exports always succeedconst { job } = awaitcanvaAPI('/exports', token, { method: 'POST', body: ... });
const urls = (awaitpollExport(job.id)).urls; // Crashes if failed// RIGHT — handle all export error codesconst result = awaitpollExport(job.id);
if (result.status === 'failed') {
switch (result.error?.code) {
case'license_required':
thrownewError('Design uses premium elements — user needs Canva Pro');
case'approval_required':
thrownewError('Design requires approval before export');
case'internal_failure':
// Retry after delaybreak;
}
}