Canva Integration Guide

Technology: canva · Category: design · Last reviewed: 2026-08-23

Source: https://tech-stack.codeamanilabs.org/guide/canva

Insight:

Canva's Connect API mass-produces on-brand graphics — design a Brand Template once, then autofill it from data to generate social/marketing assets at scale. Highest-leverage feature for non-designers; the OAuth client secret and tokens stay server-side.

 ██████╗ █████╗ ███╗   ██╗██╗   ██╗ █████╗
██╔════╝██╔══██╗████╗  ██║██║   ██║██╔══██╗
██║     ███████║██╔██╗ ██║██║   ██║███████║
██║     ██╔══██║██║╚██╗██║╚██╗ ██╔╝██╔══██║
╚██████╗██║  ██║██║ ╚████║ ╚████╔╝ ██║  ██║
 ╚═════╝╚═╝  ╚═╝╚═╝  ╚═══╝  ╚═══╝  ╚═╝  ╚═╝

Canva Integration Guide

Focus: Programmatic design with the Canva Connect API (REST + OAuth) — create designs, autofill brand templates, upload assets, and export to PNG/PDF/JPG. A Canva MCP connector is also available inside Claude Code for design operations.

Overview

Canva exposes two developer surfaces:

No server SDK is required — the Connect API is plain REST/OAuth. Canva does not publish an npm client for the Connect API; instead it ships a public OpenAPI description (https://www.canva.dev/sources/connect/api/latest/api.yml, API version 2024-06-18) you can feed to openapi-generator to generate a typed client in any language, plus a Starter Kit repo (github.com/canva-sdks/canva-connect-api-starter-kit) that bundles a generated TypeScript client and a demo app. In Claude Code, the Canva MCP also offers search-designs, create-design, export-design, upload-asset-from-url, etc.

Here is the big picture — once you see how the pieces connect, the rest is easy:

flowchart LR
  A["Your server<br/>codeAmani"] -->|"OAuth 2.0 Bearer token"| B["Connect API<br/>api.canva.com/rest/v1"]
  B --> C["Create design"]
  B --> D["Upload asset"]
  B --> E["Autofill<br/>Brand Template"]
  B --> F["Export job<br/>PNG/PDF/JPG"]
  F --> G["Download URLs<br/>expire after 24h"]

Official Documentation

The API reference is organized per resource (there is no single /api-reference/ index page).

Resource URL
Connect API docs https://www.canva.dev/docs/connect/
Quickstart + Starter Kit https://www.canva.dev/docs/connect/quickstart/
Authentication (OAuth) https://www.canva.dev/docs/connect/authentication/
Autofill guide https://www.canva.dev/docs/connect/autofill-guide/
Autofills reference https://www.canva.dev/docs/connect/api-reference/autofills/
Exports reference https://www.canva.dev/docs/connect/api-reference/exports/
Assets reference https://www.canva.dev/docs/connect/api-reference/assets/
OpenAPI description https://www.canva.dev/docs/connect/api-reference/openapi-description/
Apps SDK https://www.canva.dev/docs/apps/
Canva MCP server https://www.canva.dev/docs/apps/mcp-server/
Developer portal https://www.canva.dev/

1. Authentication (OAuth 2.0)

  1. Create an integration in the Developer portal and note the Client ID / Client Secret.
  2. Send the user to https://www.canva.com/api/oauth/authorize (Authorization Code + PKCE) requesting the scopes you need (e.g. design:content:write, asset:write, design:content:read), then exchange the returned code for a user access token.
  3. Exchange/refresh at the token endpoint: POST https://api.canva.com/auth/v1/oauth/token. (The older https://api.canva.com/rest/v1/oauth/token host still works but is now deprecated — prefer the /auth host.) Authenticate the request with HTTP Basic auth — Authorization: Basic base64(client_id:client_secret) (recommended) — or with client_id/client_secret body params.
  4. Call the API with Authorization: Bearer <token>. Access tokens now expire after ~4 hours (expires_in is 14400, up from the earlier 1 hour, and is "subject to change"), so read expires_in and refresh proactively.

Scopes are not cumulative — asset:write does not imply asset:read; request each scope you use. Note the exact scope spelling for templates is brandtemplate:meta:read / brandtemplate:content:read (no underscore). Store the client secret + tokens server-side only (codeAmani: .env.local / Vercel env).

2. Create a design

curl -X POST 'https://api.canva.com/rest/v1/designs' \
  -H "Authorization: Bearer $CANVA_TOKEN" -H "Content-Type: application/json" \
  -d '{"type":"type_and_asset","design_type":{"type":"preset","name":"doc"},
       "asset_id":"Msd59349ff","title":"My Holiday Presentation"}'

3. Export a design (async job)

Exports are asynchronous — kick off the job, then poll until it is ready. This little dance is quick to wire up:

sequenceDiagram
  participant S as "Your server"
  participant API as "Connect API"
  S->>API: "POST /exports - design_id + format"
  API-->>S: "job id + status in_progress"
  loop "until status success"
    S->>API: "GET /exports/jobId"
    API-->>S: "status + urls when done"
  end
  S->>S: "Download files before 24h expiry"
# Start the export job
curl -X POST 'https://api.canva.com/rest/v1/exports' \
  -H "Authorization: Bearer $CANVA_TOKEN" -H "Content-Type: application/json" \
  -d '{"design_id":"DAVZr1z5464","format":{"type":"pdf"}}'
# -> { "job": { "id": "...", "status": "in_progress" } }

Poll GET /rest/v1/exports/{jobId} until status is success; the response urls[] are download links that expire after 24h (failures return an error.code such as license_required).

Brand Template autofill

This is the highest-leverage feature: design a Brand Template once in Canva, then POST /v1/autofills with a data object to mass-produce on-brand graphics from your data. The data keys must match the named fields inside the template, and each value declares a type (text with text, image with an asset_id, or chart with chart_data). Like exports, autofill is an async job — kick it off, then poll until status is success and read the produced design from job.result.design.

sequenceDiagram
  participant S as "Your server"
  participant API as "Connect API"
  S->>API: "POST /autofills - brand_template_id + data"
  API-->>S: "job id + status in_progress"
  loop "until status success"
    S->>API: "GET /autofills/jobId"
    API-->>S: "status + result.design when done"
  end
  S->>S: "Use design.id or open edit_url"
# Start the autofill job — keys (price, hero) must match the template's named fields
curl -X POST 'https://api.canva.com/rest/v1/autofills' \
  -H "Authorization: Bearer $CANVA_TOKEN" -H "Content-Type: application/json" \
  -d '{"brand_template_id":"DAFVztcvd9z","title":"M-Pesa promo - June",
       "data":{
         "price":{"type":"text","text":"KES 499"},
         "hero":{"type":"image","asset_id":"Msd59349ff"}
       }}'
# -> { "job": { "id": "...", "status": "in_progress" } }

Poll GET /rest/v1/autofills/{jobId} until status is success; the new design is at job.result.design (id, plus urls.edit_url / urls.view_url) and is saved to the user's Canva account. Requires the design:content:write scope. (chart fields and video autofill are currently preview features — expect unannounced changes.)

Gotcha: the target design must be a Brand Template (a plain design cannot be autofilled), and every data key must exactly match a named field in that template — unmatched keys are ignored and the template's defaults remain. Image fields take an asset_id (upload first via the asset endpoints), not a URL.

4. Upload an asset

# Binary upload
curl -X POST 'https://api.canva.com/rest/v1/asset-uploads' \
  -H "Authorization: Bearer $CANVA_TOKEN" -H "Content-Type: application/octet-stream" \
  -H 'Asset-Upload-Metadata: {"name_base64":"TXkgVXBsb2Fk"}' \
  --data-binary '@/path/to/file'

# Or from a public URL (30 req/min/user)
curl -X POST 'https://api.canva.com/rest/v1/url-asset-uploads' \
  -H "Authorization: Bearer $CANVA_TOKEN" -H "Content-Type: application/json" \
  -d '{"name":"my_asset","url":"https://example.com/image.png"}'

Rate limits

The Connect API returns 429 Too Many Requests when you exceed a per-client-user per-minute budget (plus daily/design throttles on export, and a feature quota on autofill for free/trial users). Current limits (requests/min/user):

Operation Limit
Create design (POST /v1/designs) 20
Create asset upload — binary or URL 30
Poll an asset-upload / url-upload job 180
Create autofill job (POST /v1/autofills) 60
Poll an autofill job 120
List brand templates 120
Create export job (POST /v1/exports) 20
Poll an export job 120

Poll async jobs with exponential backoff (fast enough for good UX, slow enough to stay under the poll budget), and honour Retry-After on 429s.

codeAmani notes

Official docs: