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
autofillit 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:
- Connect API — a REST API (
https://api.canva.com/rest/v1/) authenticated with OAuth 2.0. Use it from a server to create/export designs, manage assets, and autofill Brand Templates. This is the codeAmani integration path. - Apps SDK — browser-based apps that run inside the Canva editor (
@canva/app-ui-kit,@canva/design). Use only if building an in-editor Canva app.
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)
- Create an integration in the Developer portal and note the Client ID / Client Secret.
- 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. - Exchange/refresh at the token endpoint:
POST https://api.canva.com/auth/v1/oauth/token. (The olderhttps://api.canva.com/rest/v1/oauth/tokenhost still works but is now deprecated — prefer the/authhost.) Authenticate the request with HTTP Basic auth —Authorization: Basic base64(client_id:client_secret)(recommended) — or withclient_id/client_secretbody params. - Call the API with
Authorization: Bearer <token>. Access tokens now expire after ~4 hours (expires_inis14400, up from the earlier 1 hour, and is "subject to change"), so readexpires_inand refresh proactively.
Scopes are not cumulative —
asset:writedoes not implyasset:read; request each scope you use. Note the exact scope spelling for templates isbrandtemplate: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
datakey must exactly match a named field in that template — unmatched keys are ignored and the template's defaults remain. Image fields take anasset_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
- Brand Templates + Autofill are the highest-leverage feature: design once in Canva,
then
POST /v1/autofillsto mass-produce on-brand graphics from data. - Security: OAuth client secret and tokens are server-side only; never expose to the
browser. For webhooks, request the
collaboration:eventscope and verify the signature on every callback before processing (see the developer portal for the current scheme). - Design pairing: marketing assets here can feed the same R2/Vercel pipeline used for the dashboard thumbnails.
Official docs: