Canvases and releases
A canvas is the state machine behind an interactive mascot: nodes are poses, and edges are the animated transitions between them with the conditions that trigger them. A canvas belongs to a project and can mix Original and variant poses in one graph.
Create a canvas
Creating an empty draft generates no media. The mascot sets the starting identity and 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 has data.id, data.graph and data.graph_content_hash. Optional variant_id sets the default identity for new poses.
curl 'https://api.masko.ai/v1/canvases?project_id=PROJECT_ID' \
-H "Authorization: Bearer $MASKO_API_KEY"Edit safely
Read the draft, edit the complete graph and send its revision as expected_graph_hash. A concurrent edit returns 409, so read again before reapplying your change.
const base = 'https://api.masko.ai/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);Graph fields are in Build a canvas.
Generate and validate
Dry-run POST /v1/canvases/CANVAS_ID/generate-all to review the work and cost, then submit the same selection with approved_plan_id and expected_graph_hash. Track it with GET /v1/canvases/CANVAS_ID/status instead of resubmitting; see canvas generation. Validation and history reads are free.
Checkpoints and releases
{
"action": "checkpoint",
"name": "Before adding a transition",
"expected_graph_hash": "CURRENT_GRAPH_HASH"
}GET /v1/canvases/CANVAS_ID/history returns the draft, checkpoints and releases. POST one action to the same route:
| Action | Additional fields |
|---|---|
checkpoint | name, expected_graph_hash |
restore | checkpoint_id, expected_graph_hash |
release | operation_id (UUID), notes, expected_graph_hash |
restore_release | release_id, expected_graph_hash |
copy_release | release_id, name, expected_graph_hash |
- Checkpoints can hold unfinished work. A release freezes a complete, playable graph with its exact dependencies, so it needs complete media and a fresh UUID
operation_id;notesis optional. - A release the Masko player would not play is refused with 422 and the reasons: a condition it cannot read (an unknown comparison, a value that is not true, false or a number), an input without a type or starting value, a move to a pose that is not in the canvas, a speed of 0, or a pose without a picture.
- Restore saves the current work in a checkpoint first.
- No action regenerates media, spends credits, publishes content or activates a mascot on a Mac. An installed release stays pinned until explicitly replaced.
Deliver a release
Your backend fetches the release's delivery with your API key and your app plays it with the Masko player, which runs every canvas rule: Play a canvas has the backend route, the targets and the set and fire calls. target=desktop returns the full media set, for Masko for Mac. The delivery holds temporary signed playback links, not a credential. Never put an API key in a website or a shipped app.
Templates and export
To start from a ready-made graph, save a canvas template into an empty draft. Export reads an editable graph with delivery URLs; use a release when playback must keep exact media.
