Canva Rate Limits
Overview
The Canva Connect API enforces per-user, per-endpoint rate limits. Each endpoint has different thresholds. A 429 response means you must wait before retrying.
Canva Connect API Rate Limits
| Endpoint | Method | Limit |
|---|
/v1/users/me | GET | 10 req/min |
/v1/users/me/profile | GET | 10 req/min |
/v1/designs | GET | 100 req/min |
/v1/designs | POST | 20 req/min |
/v1/designs/{id} | GET | 100 req/min |
/v1/exports | POST | 75 req/5min, 500/24hr per user |
/v1/exports (integration) | POST | 750 req/5min, 5000/24hr |
/v1/exports (per document) | POST | 75 req/5min |
/v1/asset-uploads | POST | 30 req/min |
/v1/autofills | POST | 60 req/min |
/v1/folders | POST | 20 req/min |
/v1/brand-templates | GET | 100 req/min |
All limits are per user of your integration unless noted otherwise.
Exponential Backoff with Jitter
async function canvaRequestWithBackoff<T>(
fn: () => Promise<T>,
config = { maxRetries: 5, baseDelayMs: 1000, maxDelayMs: 60000 }
): Promise<T> {
for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
try {
return await fn();
} catch (error: any) {
if (attempt === config.maxRetries) throw error;
const status = error.status || error.response?.status;
if (status !== 429 && (status < 500 || status >= 600)) throw error;
const retryAfter = error.headers?.get?.('Retry-After');
const delay = retryAfter
? parseInt(retryAfter) * 1000
: Math.min(
config.baseDelayMs * Math.pow(2, attempt) + Math.random() * 1000,
config.maxDelayMs
);
console.warn(`Rate limited (attempt ${attempt + 1}/${config.maxRetries}). Waiting ${(delay / 1000).toFixed(1)}s`);
await new Promise(r => setTimeout(r, delay));
}
}
throw new Error('Unreachable');
}
Queue-Based Rate Limiting
import PQueue from 'p-queue';
const canvaQueues = {
designs: new PQueue({ concurrency: 1, interval: 3000, intervalCap: 1 }),
exports: new PQueue({ concurrency: 1, interval: 4000, intervalCap: 1 }),
assets: new PQueue({ concurrency: 1, interval: 2000, intervalCap: 1 }),
reads: new PQueue({ concurrency: 3, interval: 1000, intervalCap: 3 }),
};
const design = await canvaQueues.designs.add(() =>
client.createDesign({ design_type: { : , : , : }, : })
);
designIds = [, , , , ];
= .(
designIds.(
canvaQueues..(
client.({ : id, : { : } })
)
)
);
Rate Limit Monitor
class CanvaRateLimitTracker {
private windows: Map<string, { count: number; resetAt: number }> = new Map();
track(endpoint: string, response: Response): void {
const remaining = response.headers.get('X-RateLimit-Remaining');
const reset = response.headers.get('X-RateLimit-Reset');
if (remaining !== null) {
this.windows.set(endpoint, {
count: parseInt(remaining),
resetAt: reset ? parseInt(reset) * 1000 : Date.now() + 60000,
});
}
}
shouldThrottle(endpoint: string): boolean {
const window = this.windows.get(endpoint);
if (!window) return ;
. < && .() < .;
}
(: ): {
= ..(endpoint);
(!) ;
.(, . - .());
}
(): <, { : ; : }> {
: <, > = {};
( [ep, w] .) {
report[ep] = {
: w.,
: ,
};
}
report;
}
}
Proactive Throttling
async function throttledCanvaRequest<T>(
tracker: CanvaRateLimitTracker,
endpoint: string,
fn: () => Promise<T>
): Promise<T> {
if (tracker.shouldThrottle(endpoint)) {
const waitMs = tracker.getWaitMs(endpoint);
console.log(`Proactively waiting ${waitMs}ms for ${endpoint}`);
await new Promise(r => setTimeout(r, waitMs));
}
return fn();
}
Error Handling
| Scenario | Detection | Action |
|---|
| Single 429 | HTTP status | Wait Retry-After seconds, retry |
| Sustained 429s | Multiple retries fail | Reduce request rate, increase backoff |
| Export quota hit | 500/24hr per user | Queue exports, spread across hours |
| Integration quota | 5000/24hr exports | Distribute across users |
Resources
Next Steps
For security configuration, see canva-security-basics.