agentsclimarketplace

Canva install auth

Skill jeremylongshore/claude-code-plugins-plus-skills/plugins/saas-packs/canva-pack/skills/canva-install-auth

425 plugins, 2,810 skills, 200 agents for Claude Code. Open-source marketplace at tonsofskills.com with the ccpi CLI package manager.

Install
npx -y skills add jeremylongshore/claude-code-plugins-plus-skills --skill canva-install-auth

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its author says it does

Copied from the file, not written here

'Set up Canva Connect API OAuth 2.0 PKCE authentication and project scaffolding.

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

6.7 KB, as published. Nobody here has run it

Canva Connect API — Install & Auth

Overview

Set up a Canva Connect API integration with OAuth 2.0 Authorization Code flow with PKCE (SHA-256). The Canva Connect API is a REST API at https://api.canva.com/rest/v1/* — there is no SDK package. All calls use fetch or axios with Bearer tokens.

Prerequisites

  • Node.js 18+ (for native crypto.subtle and fetch)
  • A Canva account at canva.com
  • An integration registered at canva.dev

Instructions

Step 1: Register Your Integration

  1. Go to Settings > Integrations at canva.com/developers
  2. Create a new integration — note your Client ID and Client Secret
  3. Add redirect URI(s): e.g. http://localhost:3000/auth/canva/callback
  4. Enable required scopes under Permissions

Step 2: Store Credentials

# .env (NEVER commit — add to .gitignore)
CANVA_CLIENT_ID=OCAxxxxxxxxxxxxxxxx
CANVA_CLIENT_SECRET=xxxxxxxxxxxxxxxx
CANVA_REDIRECT_URI=http://localhost:3000/auth/canva/callback
echo '.env' >> .gitignore
echo '.env.local' >> .gitignore

Step 3: Implement OAuth 2.0 PKCE Flow

// src/canva/auth.ts
import crypto from 'crypto';

// 1. Generate PKCE code verifier and challenge
export function generatePKCE(): { verifier: string; challenge: string } {
  const verifier = crypto.randomBytes(64).toString('base64url'); // 43-128 chars
  const challenge = crypto
    .createHash('sha256')
    .update(verifier)
    .digest('base64url');
  return { verifier, challenge };
}

// 2. Build the authorization URL
export function getAuthorizationUrl(opts: {
  clientId: string;
  redirectUri: string;
  scopes: string[];
  codeChallenge: string;
  state: string;
}): string {
  const params = new URLSearchParams({
    response_type: 'code',
    client_id: opts.clientId,
    redirect_uri: opts.redirectUri,
    scope: opts.scopes.join(' '),
    code_challenge: opts.codeChallenge,
    code_challenge_method: 'S256',
    state: opts.state,
  });
  return `https://www.canva.com/api/oauth/authorize?${params}`;
}

// 3. Exchange authorization code for access token
export async function exchangeCodeForToken(opts: {
  code: string;
  codeVerifier: string;
  clientId: string;
  clientSecret: string;
  redirectUri: string;
}): Promise<{ access_token: string; refresh_token: string; expires_in: number }> {
  const basicAuth = Buffer.from(
    `${opts.clientId}:${opts.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: 'authorization_code',
      code: opts.code,
      code_verifier: opts.codeVerifier,
      redirect_uri: opts.redirectUri,
    }),
  });

  if (!res.ok) {
    const err = await res.json();
    throw new Error(`Token exchange failed: ${err.error} — ${err.error_description}`);
  }
  return res.json();
}

// 4. Refresh an expired access token (access tokens expire in ~4 hours)
export async function refreshAccessToken(opts: {
  refreshToken: string;
  clientId: string;
  clientSecret: string;
}): Promise<{ access_token: string; refresh_token: string; expires_in: number }> {
  const basicAuth = Buffer.from(
    `${opts.clientId}:${opts.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: opts.refreshToken,
    }),
  });

  if (!res.ok) throw new Error('Token refresh failed');
  return res.json();
}

Step 4: Verify Connection

// Verify token works by calling GET /v1/users/me (no scopes required)
async function verifyConnection(accessToken: string): Promise<void> {
  const res = await fetch('https://api.canva.com/rest/v1/users/me', {
    headers: { 'Authorization': `Bearer ${accessToken}` },
  });

  if (!res.ok) throw new Error(`Verification failed: ${res.status}`);

  const { team_user } = await res.json();
  console.log(`Connected — user_id: ${team_user.user_id}, team_id: ${team_user.team_id}`);
}

Available OAuth Scopes

ScopeDescription
design:content:readRead design contents, export designs
design:content:writeCreate designs, autofill brand templates
design:meta:readList designs, get design metadata
asset:readView uploaded asset metadata
asset:writeUpload, update, delete assets
brandtemplate:content:readRead brand template content
brandtemplate:meta:readList and view brand template metadata
folder:readView folder contents
folder:writeCreate, update, delete folders
folder:permission:writeManage folder permissions
comment:readRead design comments
comment:writeCreate comments and replies
collaboration:eventReceive webhook notifications
profile:readRead user profile information

Error Handling

ErrorCauseSolution
invalid_clientWrong client_id or secretVerify credentials in Canva dashboard
invalid_grantExpired or reused auth codeRestart OAuth flow — codes are single-use
invalid_scopeScope not enabledEnable scope in integration settings
access_deniedUser rejected consentPrompt user again
Token expired (401)Access token > 4 hours oldCall refresh token endpoint

Resources

Next Steps

After successful auth, proceed to canva-hello-world for your first API call.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.