---
title: "Create Canvas"
method: POST
path: "/canvases"
tags: ["canvas-actions"]
---

# Create Canvas

`POST /canvases`

## Request

- Base URL: `https://api.moda.app/v1`
- URL: `https://api.moda.app/v1/canvases`
- Auth: HTTP bearer

Create a canvas — blank pages, or a one-call copy of a team template.

Returns page ids + editor URL + revision. With ``template_canvas_id`` set,
the new canvas is a full copy of that source canvas (every page, node, and
variable; description, category, and theme carried over; the source's brand
kit kept — brand consistency is the point of team templates) instead of
blank pages — the same copy seam the in-app agent's
``create_canvas(mode="copy_canvas")`` uses. The copy never inherits the
source's template flag, and ``name`` replaces the default copy naming.
Discover the team's flagged templates (with thumbnails) via
``GET /v1/templates``. A missing, deleted, or cross-team source is a 404
``not_found``; a source whose collaboration state is still syncing is a
retryable 503 ``canvas_crdt_state_not_ready``.

A blank create with ``category: "slides"`` and a ``brand_kit_id`` also
attaches that kit's saved default slides theme — the layouts Add Slide
draws from. ``theme_canvas_id`` in the response is the canvas's effective
theme on both lanes (null when unthemed); a kit with no usable theme still
creates, unthemed.

Honors ``idempotency_key`` through the shared pipeline: a retried create
replays the stored result instead of minting a second canvas; the same key
with a different payload is a 409 ``idempotency_conflict``.

Create-specific key semantics (deliberate, documented on the field):

- Replay is CLIENT-KEY-ONLY. Without a key every call creates a new
  canvas; the server never derives a replay key from the payload
  (same-args creates from different sessions are distinct intents).
- A replayed create is UNMISTAKABLY loud: ``replayed: true`` plus
  ``reused_existing: true``, the ORIGINAL ``created_at``, and a steering
  ``note`` — never a bare ``committed`` — because the replayed canvas may
  have been authored since, and a caller that treats it as a fresh blank
  canvas would author over existing content.
- A stored result whose canvas was DELETED since does not replay: the
  stale record is invalidated and the create re-executes fresh (loud
  ``stale_replay_invalidated`` marker), minting a NEW canvas.

## Headers

- `Moda-Version` '2026-04-12' | '2026-05-01'

## Request body

- CanvasCreateRequest
  - `name` string, required: Canvas display name.
  - `width` integer, nullable: Page width in px. Omit BOTH width and height to use the category's default size (see category); with no category either, pages default to 1920×1080.
  - `height` integer, nullable: Page height in px. Omit BOTH width and height to use the category's default size.
  - `page_count` integer: Number of blank pages.
  - `intent` string, nullable: What you are about to build, in a few words — recorded on the canvas for the person you are handing it to, who otherwise opens an empty page with nothing to explain it. Collapsed to one line and clamped server-side; over-length truncates rather than failing the create. Mutually exclusive with ``template_canvas_id`` (a copy arrives with the source's content, so there is no empty page to explain).
  - `category` 'slides' | 'social' | 'carousel' | 'pdf' | 'diagram' | 'ui' | 'animation' | 'prints' | 'web-ads' | 'other', nullable: Canvas category (slides, social, carousel, pdf, diagram, ui, animation, prints, web-ads, other). Drives slides/motion semantics, export defaults, and — when width/height are both omitted — the page size, matching the in-app format picker: slides → 960×540, social → 1080×1080, carousel → 1080×1350, pdf → 816×1056, animation → 1920×1080, prints → 816×1056, web-ads → 300×250, diagram/ui/other → 1080×1080. Explicit width/height always win. Omit to let the server infer. To change an existing canvas's size, resize it in place (normal and supported: group the page's nodes, set the new page size, scale uniformly via the edit lane) — creating a new canvas is not the way to change size.
  - `template_canvas_id` string, nullable: Prefixed ``cvs_`` wire ID (Crockford base32 body) — the canonical, recommended form. For back-compat, **a bare UUID string is also accepted** in both path parameters and JSON request bodies (older integrations that stored raw UUIDs keep working), as is **the prefix over a UUID body** (``cvs_00000000-0000-4000-8000-000000000000``). All three are permanent, supported inputs; only the canonical form is ever emitted.
  - `folder_id` string, nullable: Prefixed ``fld_`` wire ID (Crockford base32 body) — the canonical, recommended form. For back-compat, **a bare UUID string is also accepted** in both path parameters and JSON request bodies (older integrations that stored raw UUIDs keep working), as is **the prefix over a UUID body** (``fld_00000000-0000-4000-8000-000000000000``). All three are permanent, supported inputs; only the canonical form is ever emitted.
  - `visibility` 'team' | 'private', nullable: Explicit visibility for the new canvas: ``private`` hides it from teammates; ``team`` makes it visible to the whole team. Ignored in favor of the folder's visibility when ``folder_id`` is set. Omit for the workspace default.
  - `brand_kit_id` string, nullable: Prefixed ``bk_`` wire ID (Crockford base32 body) — the canonical, recommended form. For back-compat, **a bare UUID string is also accepted** in both path parameters and JSON request bodies (older integrations that stored raw UUIDs keep working), as is **the prefix over a UUID body** (``bk_00000000-0000-4000-8000-000000000000``). All three are permanent, supported inputs; only the canonical form is ever emitted.
  - `idempotency_key` string, nullable: A retried create with the same key replays the stored result (replayed:true + reused_existing:true + the original created_at) instead of creating a second canvas. Replay is CLIENT-KEY-ONLY: omit the key and every call creates a NEW canvas — the server never derives a key from the payload. Never derive this key from the payload yourself (e.g. a hash of name+dimensions): same-args creates are legitimately distinct intents, and a payload-derived key reused from a later session returns the ORIGINAL (possibly authored) canvas instead of a new one. Use a fresh random key per intended create; reuse it only to retry that same create.

## Response `200`

Successful Response

- object

## Other responses

- `401`: Authentication required.
- `402`: Insufficient credits for a metered operation.
- `403`: Permission denied for this scope.
- `404`: Resource not found.
- `409`: Conflict (idempotency / resource state).
- `422`: Request validation failed.
- `429`: Rate limit exceeded.
- `500`: Internal error.

## Changes

- **2026-11-03** `9ac6a3abc0f8`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/moda/apis/moda-public-api/changes/canvases/post.md)

---

[API](https://skmtc.dev/moda/apis/moda-public-api.md) - [All operations](https://skmtc.dev/moda/apis/moda-public-api/llms.txt) - [OpenAPI document](https://skmtc.dev/moda/apis/moda-public-api/revisions/9ac6a3abc0f8?raw)
