Skip to content
Masko logomasko
Docs
Documentation

Canvases

A canvas belongs to a project and references authorized mascot assets. Nodes represent poses; edges define transitions and conditions. Original and named variants can appear in the same graph.

Use the canonical /v1/canvases routes on https://api.masko.ai. For local requests, replace https://api.masko.ai/v1 with http://localhost:3000/api/v1. See the migration guide for existing collection integrations.

Create a canvas

Create an empty draft with the project and starting mascot. Creation does not generate media. The mascot provides the initial identity context; the project controls access.

curl -X POST https://api.masko.ai/v1/canvases \
  -H "Authorization: Bearer $MASKO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"project_id":"PROJECT_ID","mascot_id":"MASCOT_ID","name":"Main Canvas"}'

The response contains data.id, data.graph and data.graph_content_hash. Optional variant_id chooses the default identity for new poses. Existing poses retain their recorded identity.

curl 'https://api.masko.ai/v1/canvases?project_id=PROJECT_ID' \
  -H "Authorization: Bearer $MASKO_API_KEY"

Edit safely

Read the latest draft, edit its complete graph, and pass its revision as expected_graph_hash. A concurrent edit returns 409; read again before deciding how to apply your changes.

const base = 'http://localhost:3000/api/v1';
const headers = { Authorization: `Bearer ${process.env.MASKO_API_KEY}` };
const read = await fetch(`${base}/canvases/${canvasId}`, { headers });
const result = await read.json();
if (!read.ok) throw new Error(result.error.message);
const draft = result.data;
const graph = structuredClone(draft.graph);
// Edit graph.nodes, graph.edges and graph.inputs here.
const saved = await fetch(`${base}/canvases/${canvasId}`, {
  method: 'PATCH',
  headers: { ...headers, 'Content-Type': 'application/json' },
  body: JSON.stringify({ graph, expected_graph_hash: draft.graph_content_hash })
});
const receipt = await saved.json();
if (!saved.ok) throw new Error(receipt.error.message);

See Build a canvas for graph fields. The canonical GET returns the stored draft and revision. GET /v1/canvases/:canvasId/status returns generation progress, described in generation progress.

Generate and validate

Use POST /v1/canvases/CANVAS_ID/generate-all with dry_run:true to review the work and cost. Submit the same selection with its approved_plan_id and expected_graph_hash. Follow the returned job IDs through /v1/jobs/JOB_ID; do not resubmit merely to poll progress.

Read-only keys can use GET /v1/canvases/CANVAS_ID/generation-estimate with URL-encoded JSON in the options query parameter. It returns a plan without starting jobs or spending credits. Execution still requires a write key.

Generation spends credits. Validation and reading history do not generate media. See canvas generation for complete request shapes.

Checkpoints and releases

{
  "action": "checkpoint",
  "name": "Before adding a transition",
  "expected_graph_hash": "CURRENT_GRAPH_HASH"
}

Checkpoints can contain unfinished work. Use action:"release", a fresh UUID operation_id, optional notes and the current hash to freeze a complete playable graph. Releases require complete media and do not publish content or activate an installation.

Restore preserves the current draft first. See Studio migration for restore, release-copy and signed-delivery requests.

Templates and legacy clients

Existing /v1/collections/:id/canvases consumers remain supported. That legacy creation endpoint accepts template_id; canonical POST /v1/canvases does not. New integrations create a draft and then PATCH its graph. See legacy templates if maintaining an existing template integration.

Export reads an editable graph with delivery URLs. Use a frozen release when playback must retain exact media.