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.