Generate Canvas Assets
Generate pending node images and/or transition videos for your canvas. Use dry_run: true first. The plan returns plan_id and graph_content_hash; send both back when executing so Masko rejects a changed graph, prompt, or selection before spending credits.
Use a write key for the mascot’s workspace. Dry runs enforce the same organization and project permissions as generation. A personal key cannot plan or generate an organization canvas.
Read-only estimates
GET /v1/canvases/{canvasId}/generation-estimate accepts a read-only key in
the canvas workspace. Pass an options query parameter containing URL-encoded
JSON. It returns the same plan identifiers and cost preview without changing
the graph, creating jobs or assets, or spending credits.
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;Options default to Standard, 5 seconds, all missing media, and no additional
stickers. Optional node_ids and edge_ids arrays select specific work.
include_stickers includes sticker costs. skip_completed is accepted for
compatibility; completed media is always skipped. Execution controls such as
dry_run, approved_plan_id, expected_graph_hash and max_credits are rejected.
After approval, use a write key to POST the same options to generate-all, with
dry_run: false, approved_plan_id: plan.plan_id,
expected_graph_hash: plan.graph_content_hash, and an approved max_credits.
Send the explicit model and duration shown above so the execution matches the
estimate. The existing POST dry-run flow below remains supported for write keys.
For a graph containing several character forms, set generationVariantId on each
new node that should use an approved variant from the same mascot. Untagged
new nodes use the canvas identity. Existing pose receipts retain their identity;
a conflicting variant is rejected. This authoring field does not change runtime
behavior inputs. Use targets: "all", skip_completed: true, and
include_stickers: true to generate the unfinished section and its stickers while
keeping completed media. Variant authorization is checked during the dry run,
before credits are charged.
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
The endpoint scans the canvas and dispatches missing node images and/or edge videos based on targets. The skip_completed parameter defaults to true, meaning graph parts that already have assigned assets are skipped so running the endpoint twice does not duplicate in-flight or completed work.
Use node_ids and edge_ids to plan an exact graph selection. Omit either field to consider every part in that category. Unknown IDs are rejected before any job is created or credit is spent. Execute with the returned plan_id as approved_plan_id and graph_content_hash as expected_graph_hash. A changed graph, prompt, option, or selection returns 409 instead of silently changing approved work.
The response separates generation accounting into:
dry_run: true when no jobs/assets were created and no credits were deducted.plan_id: identity of the exact graph, prompts, scope, and generation options.graph_content_hash: canvas revision used to create the plan.planned_jobs: node, sticker, and edge work that would be queued, including prompts. Planned animation and sticker jobs include signed source/target asset URLs when those input images already exist; media that would be generated by the run is represented by its prompt and cost, not a fake preview URL.generated: newly queued node or edge jobs.skipped: legacy skipped edge count.skipped_items: graph parts not queued, including target-filtered nodes/edges and graph parts with existing asset IDs.reverse_free: reverse edges that cost 0 credits because they reuse a forward transition.already_complete: nodes and edges whose assigned assets are already completed.estimated_cost: cost calculated before dispatch.actual_cost: credits deducted for this request.would_charge_credits: dry-run estimate for the credits a real request would deduct.
Each generated job follows the same lifecycle as a regular animation job - you can poll individual jobs or read the status field on the canvas detail response to track overall progress.
Edge Classification
Not all edges cost the same. The endpoint classifies each edge before generation:
| Type | Condition | Cost | Notes |
|---|---|---|---|
| Loop | source == target | Paid | Looping animation on a single pose |
| Forward | source != target | Paid | Transition from one pose to another |
| Reverse | Auto-generated from forward | 0 credits | Triggered automatically when forward completes |
| Any State | source = "*" | Skipped | Virtual edges resolved at runtime, no video needed |
Cost Calculation
Only loops and forward transitions cost credits. The formula is:
(loops + forwards) x selected credits per second x duration
Use animation_model: "standard" for 2 credits/sec and 5–15s, or "premium" for 6 credits/sec and 4–30s. Both default to 5s. Omit the field to preserve legacy behavior: 5 credits/sec, 4–10s, default 4s. Send the same model and duration for dry run and approved execution; changing either requires a new plan. These options also apply to individual edge generation.
For example, a canvas with 4 loop edges and 3 forward edges using Standard at 5 seconds each:
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 creditsThe response includes estimated_cost, actual_cost, and legacy total_cost fields so you know the exact cost. If you do not have enough credits, the request returns a 402 error with the required amount.
This endpoint returns 200 OK with the list of queued jobs - generation runs asynchronously.
Auto-Reverse
When a forward transition completes and its reverse edge is declared in the graph, the reverse video can be created at 0 generation credits. You do not need to request it separately. The reverse job appears in the canvas status response alongside the forward jobs.
If a forward edge already has a matching reverse edge in the canvas, the auto-reverse fills in the reverse edge video. Declare the intended reverse edge in your graph; do not assume the endpoint creates a return path for every transition.
Poll Progress
Poll GET /v1/canvases/CANVAS_ID/status for one progress summary of the whole canvas, every 15 to 30 seconds. It never changes the canvas, and read-only keys can call it. To follow a single job, use the poll_url returned with it in jobs.
status.generation.nodes and status.generation.edges count total, completed, pending and failed parts. Pending means neither finished nor failed. Before retrying, inspect status.failed_nodes and status.failed_edges. Each entry includes the failed part, its job ID, and a customer-safe error message. 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
To re-run one edge without re-dispatching the whole canvas, use the per-edge endpoint:
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
}'The endpoint returns 202 Accepted and a job_id. Only non-reverse edges can be dispatched individually. Poll the job as usual.
Use this when one edge failed or you updated the edge prompt and want to regenerate it without spending credits on the rest of the canvas.