Generate canvas assets
Generate a canvas's missing pose images and transition videos in one call. Plan with dry_run: true, then execute with the returned plan_id and graph_content_hash so a changed graph is rejected before credits are spent.
Use a write key for the mascot's workspace. Dry runs check the same permissions as generation, so a personal key cannot plan an organization canvas.
Read-only estimates
GET /v1/canvases/{canvasId}/generation-estimate returns the same plan and cost preview with a read-only key, without changing anything or spending credits. Pass the options as URL-encoded JSON in options:
const options = { animation_model: 'standard', duration: 5, targets: 'all' };
const query = new URLSearchParams({ options: JSON.stringify(options) });
const response = await fetch(
`https://api.masko.ai/v1/canvases/${canvasId}/generation-estimate?${query}`,
{ headers: { Authorization: `Bearer ${apiKey}` } }
);
const result = await response.json();
if (!response.ok) throw new Error(result.error.message);
const plan = result.data;- Defaults: Standard, 5 seconds, all missing media, no extra stickers. Completed media is always skipped.
node_idsandedge_idsselect specific work;include_stickersadds sticker costs.dry_run,approved_plan_id,expected_graph_hashandmax_creditsare rejected here.
To execute, POST the same options, with the explicit model and duration, to generate-all with a write key, dry_run: false, approved_plan_id: plan.plan_id, expected_graph_hash: plan.graph_content_hash and an approved max_credits.
For a graph with several character forms, set generationVariantId on each new node that should use an approved variant of the same mascot; untagged new nodes use the canvas identity. Existing poses keep their identity, a conflicting variant is rejected, and variant access is checked in the dry run. To finish an unfinished section with its stickers, send targets: "all", skip_completed: true and include_stickers: true.
curl -X POST https://api.masko.ai/v1/canvases/CANVAS_ID/generate-all \
-H "Authorization: Bearer masko_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"dry_run": true,
"targets": "animations",
"edge_ids": ["wave-entry", "wave-loop"],
"skip_completed": true,
"animation_model": "standard",
"duration": 5
}'How It Works
targets picks node images, edge videos or both. skip_completed (default true) skips parts that already have assigned assets, so a second run never duplicates in-flight or completed work. node_ids and edge_ids limit the selection, and unknown IDs are rejected before any job or charge. A changed graph, prompt, option or selection returns 409.
Response fields:
dry_run: true when nothing was created or charged.plan_id: identity of the exact graph, prompts, scope and options.graph_content_hash: canvas revision the plan was made from.planned_jobs: node, sticker and edge work with prompts. Animation and sticker jobs include signed source and target URLs when those images exist; media the run would create shows only its prompt and cost.generated: newly queued node or edge jobs.skipped: number of skipped edges.skipped_itemslists every part not queued, filtered out bytargetsor already assigned.reverse_free: reverse edges this run makes at 0 credits, by playing their forward clip backwards.already_complete: parts whose assigned assets are completed.estimated_cost,actual_cost,would_charge_credits: cost before dispatch, credits deducted, and the dry-run estimate.
Execution returns 200 OK with the queued jobs; generation runs asynchronously.
Edge Classification
| Type | Condition | Cost |
|---|---|---|
| Loop | source == target | Paid |
| Forward | source != target | Paid |
| Reverse | Reverse of a forward edge | 0 credits |
| Any State | source = "*" | Skipped: resolved at runtime, no video |
Cost Calculation
Only loops and forward transitions cost credits: (loops + forwards) x credits per second x duration. animation_model: "standard" costs 2 credits/sec for 2–15 s and "premium" 6 credits/sec for 4–30 s, also for single-edge generation. Changing the model or duration after the dry run needs a new plan.
For example, 4 loops and 3 forwards on Standard at 5 seconds:
Paid edges: 4 loops + 3 forwards = 7
Cost per edge: 2 credits/sec x 5 sec = 10 credits
Total: 7 x 10 = 70 credits
Reverse edges (auto): 3 (one per forward) = 0 credits
Any State edges: skipped = 0 creditsWithout enough credits, the request returns 402 with the required amount.
When a forward transition completes, Masko fills its reverse edge with the reversed video at 0 credits, with no separate request. Only reverse edges declared in the graph are filled, so declare each return path you want.
With edge_ids, listing either edge of a pair is enough: a forward edge brings its reverse, and a reverse whose forward clip is already finished is made from that clip. Until the forward edge has a finished clip, its reverse is listed in skipped_items with forward_clip_not_ready. A forward clip that cannot be reversed is listed with forward_clip_not_reversible; generate the forward edge again to make its reverse.
Poll Progress
Poll GET /v1/canvases/CANVAS_ID/status every 15 to 30 seconds for one summary of the whole canvas; read-only keys can call it. To follow one job, use the poll_url returned with it in jobs.
status.generation.nodes and .edges count total, completed, pending (neither finished nor failed) and failed parts. status.failed_nodes and status.failed_edges give each failed part, its job ID and a customer-safe error. A retry is a new paid attempt and should require approval.
curl https://api.masko.ai/v1/canvases/CANVAS_ID/status \
-H "Authorization: Bearer masko_YOUR_API_KEY"
# Response:
# {
# "data": {
# "canvas_id": "abc-123",
# "graph_content_hash": "sha256:…",
# "status": {
# "generation": {
# "ready": false,
# "nodes": { "total": 4, "completed": 4, "pending": 0, "failed": 0 },
# "edges": { "total": 14, "completed": 10, "pending": 3, "failed": 1 }
# },
# "preview": { "ready": false, "waiting_edges": 0, "repairable": false, … },
# "variants": { "sizes": [], "missing": [] },
# "failed_nodes": [],
# "failed_edges": [
# { "edge_id": "wave-idle", "job_id": "…", "error": "The provider could not animate this pose." }
# ],
# "edge_media": [ … ]
# }
# }
# }Regenerate a Single Edge
Re-run one edge after a failure or a prompt edit:
curl -X POST https://api.masko.ai/v1/canvases/CANVAS_ID/edges/EDGE_ID/generate \
-H "Authorization: Bearer masko_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"animation_model": "standard",
"duration": 5
}'It returns 202 Accepted with a job_id. Reverse edges cannot be generated here: list one in edge_ids on generate-all to make it from its forward clip.
