Skill Tracer
Skills › Knowledge Work Plugins › canva-creator › canva-api.md

canva-api.md

208 lines · small-business/skills/canva-creator/reference/canva-api.md at 2574e29

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1# Canva Connect API Reference
2
3Base URL: `https://api.canva.com/rest/v1`
4Auth: Bearer token (OAuth 2.0). Scopes needed: `design:content:read`, `design:content:write`, `asset:read`, `asset:write`, `brandtemplate:content:read` (Enterprise only).
5
6---
7

Canva Connect API Reference

Base URL: https://api.canva.com/rest/v1 Auth: Bearer token (OAuth 2.0). Scopes needed: design:content:read, design:content:write, asset:read, asset:write, brandtemplate:content:read (Enterprise only).


Raw lines 1 to 7
8## Table of contents
9
101. [Tier requirements](#tier-requirements)
112. [Brand templates (Enterprise)](#brand-templates-enterprise)
123. [Autofill (Enterprise)](#autofill-enterprise)
134. [Design copy (Pro/Teams)](#design-copy-proteams)
145. [Asset upload](#asset-upload)
156. [Export](#export)
167. [Error codes](#error-codes)
18---

Table of contents

  1. Tier requirements
  2. Brand templates (Enterprise)
  3. Autofill (Enterprise)
  4. Design copy (Pro/Teams)
  5. Asset upload
  6. Export
  7. Error codes

Raw lines 8 to 19
20## Tier requirements
22| Feature | Free | Pro | Teams | Enterprise |
23|---------|------|-----|-------|------------|
24| Create designs | ✓ | ✓ | ✓ | ✓ |
25| List own designs | ✓ | ✓ | ✓ | ✓ |
26| Brand templates (read) | — | — | — | ✓ |
27| Autofill brand templates | — | — | — | ✓ |
28| Asset upload (brand kit) | — | — | — | ✓ |
29| Asset upload (user) | — | ✓ | ✓ | ✓ |
31---

Tier requirements

FeatureFreeProTeamsEnterprise
Create designs✓✓✓✓
List own designs✓✓✓✓
Brand templates (read)———✓
Autofill brand templates———✓
Asset upload (brand kit)———✓
Asset upload (user)—✓✓✓

Raw lines 20 to 32
33## Brand templates (Enterprise)
35**List brand templates:**
36```
37GET /brand-templates?query={keyword}&ownership=organization
38```
39Response fields of interest:
40- `id` — pass this to autofill
41- `title` — human-readable name
42- `thumbnail.url` — preview image
43- `dataset[].label` — autofill field labels (e.g., "Headline", "ProductName")
45Filter by `title` contains the asset type keyword (e.g., "square post", "story", "email header").
47---

Brand templates (Enterprise)

List brand templates:

GET /brand-templates?query={keyword}&ownership=organization

Response fields of interest:

  • id — pass this to autofill
  • title — human-readable name
  • thumbnail.url — preview image
  • dataset[].label — autofill field labels (e.g., "Headline", "ProductName")

Filter by title contains the asset type keyword (e.g., "square post", "story", "email header").


Raw lines 33 to 48
49## Autofill (Enterprise)
51Populates a brand template's variable fields and creates a new design:
53```
54POST /autofills
56 "brand_template_id": "<template_id>",
57 "title": "Summer Sale — Post 1",
58 "data": {
59 "Headline": { "type": "text", "text": "Summer Sale: 30% off candles" },
60 "ProductImage": { "type": "image", "asset_id": "<uploaded_asset_id>" }
61 }
63```
65Response: `{ "job": { "id": "<job_id>", "status": "queued" } }`
67Poll `GET /autofills/{job_id}` until `status == "success"`. The response
68includes `result.design.id` — use this for export.
70---

Autofill (Enterprise)

Populates a brand template's variable fields and creates a new design:

POST /autofills
{
  "brand_template_id": "<template_id>",
  "title": "Summer Sale — Post 1",
  "data": {
    "Headline": { "type": "text", "text": "Summer Sale: 30% off candles" },
    "ProductImage": { "type": "image", "asset_id": "<uploaded_asset_id>" }
  }
}

Response: { "job": { "id": "<job_id>", "status": "queued" } }

Poll GET /autofills/{job_id} until status == "success". The response includes result.design.id — use this for export.


Raw lines 49 to 71
72## Design copy (Pro/Teams)
74There is no direct "copy template" endpoint for Pro/Teams users. Workflow:
761. `GET /designs?ownership=any&query={template name}` — list designs by name
772. Show top 3 to user; get confirmation
783. `POST /designs` with `asset_type` to create a blank design of the right
79 dimensions, then describe what the user needs to update manually in Canva.
81Pro/Teams asset generation is semi-manual: Claude creates the design shell and
82populates what the API allows; the user applies brand-specific edits in Canva
83and returns the design ID for export.
85---

Design copy (Pro/Teams)

There is no direct "copy template" endpoint for Pro/Teams users. Workflow:

  1. GET /designs?ownership=any&query={template name} — list designs by name
  2. Show top 3 to user; get confirmation
  3. POST /designs with asset_type to create a blank design of the right dimensions, then describe what the user needs to update manually in Canva.

Pro/Teams asset generation is semi-manual: Claude creates the design shell and populates what the API allows; the user applies brand-specific edits in Canva and returns the design ID for export.


Raw lines 72 to 86
87## Asset upload
89Use when the brief references product photos stored on the user's Desktop or
90file system.
92**Step 1 — Initialize upload:**
93```
94POST /asset-uploads
95{ "name_base64": "<base64(filename)>", "types": ["image/jpeg"] }
96```
97Response: `{ "job": { "id": "<job_id>" }, "upload_url": "<presigned_s3_url>" }`
99**Step 2 — Upload file:**
100```
101PUT <upload_url>
102Content-Type: image/jpeg
103Body: <raw file bytes>
104```
106**Step 3 — Poll until ready:**
107`GET /asset-uploads/{job_id}` → wait for `status == "success"` → capture `asset.id`.
109Maximum file size: 100 MB. Supported types: `image/jpeg`, `image/png`, `image/webp`.
111---

Asset upload

Use when the brief references product photos stored on the user's Desktop or file system.

Step 1 — Initialize upload:

POST /asset-uploads
{ "name_base64": "<base64(filename)>", "types": ["image/jpeg"] }

Response: { "job": { "id": "<job_id>" }, "upload_url": "<presigned_s3_url>" }

Step 2 — Upload file:

PUT <upload_url>
Content-Type: image/jpeg
Body: <raw file bytes>

Step 3 — Poll until ready: GET /asset-uploads/{job_id} → wait for status == "success" → capture asset.id.

Maximum file size: 100 MB. Supported types: image/jpeg, image/png, image/webp.


Raw lines 87 to 112
113## Export
115**Why export every design:** the autofill response thumbnail returned at
116`design.canva.ai/...` is a short-lived authenticated CDN URL — it expires
117within minutes and renders as a broken "Show Image" placeholder if embedded
118in a markdown image after expiry. The `POST /exports` output is a
119**permanent URL** that's safe to embed in chat previews, attach to HubSpot
120posts, and share with the owner.
122Always export each design after generation, before showing previews.
124```
125POST /exports
127 "design_id": "<design_id>",
128 "format": {
129 "type": "png",
130 "export_quality": "regular",
131 "pages": [1]
132 }
134```
136Poll `GET /exports/{job_id}` until `status == "success"`. Response includes
137`urls[]` — use `urls[0]` as the preview link and HubSpot attachment URL.
139**Canva MCP equivalents** (when using Cowork's Canva connector instead of
140direct REST):
142| Need | MCP tool | Returns |
143|------|---------|---------|
144| Permanent preview URL | `export-design` | Permanent download URL (safe to embed) |
145| Per-page thumbnail (more stable than autofill response, not permanent) | `get-design-thumbnail` | Page thumbnail URL |
146| Design metadata only | `get-design` | Title, owner, page count, thumbnail |
147| Upload from owner's machine | `upload-asset-from-url` | `asset_id` (one URL per call) |
149**Format guidance by channel:**
151| Channel | Type | Dimensions |
152|---------|------|-----------|
153| Instagram feed | `png` | 1080×1080 (square) |
154| Instagram story / Reels cover | `png` | 1080×1920 |
155| Facebook feed | `png` | 1200×630 |
156| Email header | `png` | 600×200 |
158---

Export

Why export every design: the autofill response thumbnail returned at design.canva.ai/... is a short-lived authenticated CDN URL — it expires within minutes and renders as a broken "Show Image" placeholder if embedded in a markdown image after expiry. The POST /exports output is a permanent URL that's safe to embed in chat previews, attach to HubSpot posts, and share with the owner.

Always export each design after generation, before showing previews.

POST /exports
{
  "design_id": "<design_id>",
  "format": {
    "type": "png",
    "export_quality": "regular",
    "pages": [1]
  }
}

Poll GET /exports/{job_id} until status == "success". Response includes urls[] — use urls[0] as the preview link and HubSpot attachment URL.

Canva MCP equivalents (when using Cowork's Canva connector instead of direct REST):

NeedMCP toolReturns
Permanent preview URLexport-designPermanent download URL (safe to embed)
Per-page thumbnail (more stable than autofill response, not permanent)get-design-thumbnailPage thumbnail URL
Design metadata onlyget-designTitle, owner, page count, thumbnail
Upload from owner's machineupload-asset-from-urlasset_id (one URL per call)

Format guidance by channel:

ChannelTypeDimensions
Instagram feedpng1080×1080 (square)
Instagram story / Reels coverpng1080×1920
Facebook feedpng1200×630
Email headerpng600×200

Raw lines 113 to 159
Uses an outside tool MCP :139
160## Rate limits & generation budgeting
162**Hard limit:** 100 requests per minute per token.
164**Cost per design:** ~5 API calls — 1 `POST /autofills` + 1 `POST /exports`
165+ ~3 polling rounds (`GET /autofills/{job_id}`, `GET /exports/{job_id}`).
166Polling cadence should be 3-5 seconds; faster polling burns quota without
167speeding up completion.
169**Safe ceiling:** ~15-20 designs per minute leaves comfortable headroom
170under the 100 req/min cap.
172**Recommended pacing (used by Stage 3):**
174```
1753 candidates per row × 1 row at a time × 30s gap between rows
176= 6 designs (~30 API calls) per 30 seconds
177= 12 designs / minute, about half the ceiling
178```
180**Pre-flight budget formula:**
182```
183total_designs = (Canva-bound rows) × (candidates per row, default 3)
184total_api_calls ≈ total_designs × 5
185generation_time ≈ (total_designs / 12) minutes
186```
188**Back-off pattern for `RATE_LIMIT_EXCEEDED`:**
1901. First hit in a session → wait 60s, retry that one candidate. Treats
191 the error as a transient spike.
1922. Second hit in the same session, or any `quota_exceeded` / daily-cap
193 error → stop generation and surface progress to the owner. Do not
194 retry. The skill asks the owner whether to (a) drop to 1 candidate
195 per remaining row, (b) pause and resume in 60 minutes, or (c) stop
196 and move to captions with what's already generated.
198---

Rate limits & generation budgeting

Hard limit: 100 requests per minute per token.

Cost per design: ~5 API calls — 1 POST /autofills + 1 POST /exports

  • ~3 polling rounds (GET /autofills/{job_id}, GET /exports/{job_id}).

Polling cadence should be 3-5 seconds; faster polling burns quota without speeding up completion.

Safe ceiling: ~15-20 designs per minute leaves comfortable headroom under the 100 req/min cap.

Recommended pacing (used by Stage 3):

3 candidates per row × 1 row at a time × 30s gap between rows
= 6 designs (~30 API calls) per 30 seconds
= 12 designs / minute, about half the ceiling

Pre-flight budget formula:

total_designs = (Canva-bound rows) × (candidates per row, default 3)
total_api_calls ≈ total_designs × 5
generation_time ≈ (total_designs / 12) minutes

**Back-off pattern for RATE_LIMIT_EXCEEDED:**

  1. First hit in a session → wait 60s, retry that one candidate. Treats the error as a transient spike.
  2. Second hit in the same session, or any quota_exceeded / daily-cap error → stop generation and surface progress to the owner. Do not retry. The skill asks the owner whether to (a) drop to 1 candidate per remaining row, (b) pause and resume in 60 minutes, or (c) stop and move to captions with what's already generated.

Raw lines 160 to 199
200## Error codes
202| Code | Meaning | What to do |
203|------|---------|-----------|
204| `PERMISSION_DENIED` | Scope missing or wrong tier | Check tier; ask user to reconnect Canva with correct scopes |
205| `DESIGN_NOT_FOUND` | Wrong design ID | Re-list designs and confirm ID |
206| `AUTOFILL_FIELD_NOT_FOUND` | Template field name mismatch | Call `GET /brand-templates/{id}` and re-read `dataset[].label` |
207| `RATE_LIMIT_EXCEEDED` | Too many requests | First hit: wait 60s, retry once. Second hit: stop and ask owner (see Rate limits above) |
208| `JOB_FAILED` | Async job failed | Check `job.error.message`; common cause is oversized asset |

Error codes

CodeMeaningWhat to do
PERMISSION_DENIEDScope missing or wrong tierCheck tier; ask user to reconnect Canva with correct scopes
DESIGN_NOT_FOUNDWrong design IDRe-list designs and confirm ID
AUTOFILL_FIELD_NOT_FOUNDTemplate field name mismatchCall GET /brand-templates/{id} and re-read dataset[].label
RATE_LIMIT_EXCEEDEDToo many requestsFirst hit: wait 60s, retry once. Second hit: stop and ask owner (see Rate limits above)
JOB_FAILEDAsync job failedCheck job.error.message; common cause is oversized asset
Raw lines 200 to 208
Involves a person stop and ask :207