Build a Canvas

Create an interactive state machine with poses, transitions, and conditions. A canvas defines how your mascot performs resolved app and user behavior.

What is a Canvas?

A canvas is a state machine where each state is a mascot pose (an image or animation) and transitions are animated videos that play when moving between states. You define conditions and inputs that trigger transitions, usually resolved inputs such as behavior::interact, behavior::attention, or node::nodeTime. The Masko embed player reads the canvas configuration and handles playback.

Nodes & Edges

Nodes represent poses or states. Each node references an image asset ID and has a position on the canvas editor grid. The itemName is the human-readable label.

Edges are transitions between nodes. Each edge connects a source node to a target node and can have:

  • conditions - Rules that must be met to trigger the transition (see below).
  • priority - When multiple edges can fire, the highest priority wins. Higher number = higher priority.
  • speed - Playback speed multiplier (e.g. 1.5 for faster, 0.5 for slower). Default is 1.
  • duration - Video duration in seconds. Used for generation.
  • description - Animation prompt used when generating the transition video.
  • reverse - If true, this edge plays the reverse of another edge's video instead of generating a new one.

Use source: "*" (any state) for edges that can fire from any node - useful for global triggers like an error state or reset.

Conditions & Inputs

Each edge can have an array of conditions. A condition has three fields:

  • input - The name of the input to check (e.g. "behavior::interact", "node::nodeTime").
  • op - The comparison operator: "==", "!=", ">", "<", ">=", "<=".
  • value - The value to compare against.

The canvas supports these input types:

  • boolean - True/false values. Example: behavior::interact, behavior::working.
  • number - Numeric values. Example: node::nodeTime, node::loopCount.
  • trigger - Fire-once events. New desktop app-control graphs should normally use resolved behavior/action inputs instead.
  • string - Text values. Rare for desktop mascot graphs.

Create a Canvas

Create a canvas with an inline graph definition. The graph contains nodes, edges, viewport settings, and input declarations.

curl -X POST https://api.masko.ai/v1/collections/COLLECTION_ID/canvases \
  -H "Authorization: Bearer masko_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Main Canvas",
    "graph": {
      "nodes": [
        { "id": "ast_img_idle", "x": 0, "y": 0, "itemName": "Idle" },
        { "id": "ast_img_wave", "x": 300, "y": 0, "itemName": "Waving" }
      ],
      "edges": [
        {
          "id": "edge_001",
          "source": "ast_img_idle",
          "target": "ast_img_wave",
          "duration": 4,
          "description": "transitioning from idle to waving",
          "conditions": [
            { "input": "behavior::attention", "op": "==", "value": true }
          ]
        },
        {
          "id": "edge_002",
          "source": "ast_img_wave",
          "target": "ast_img_idle",
          "duration": 4,
          "description": "returning from wave to idle",
          "conditions": [
            { "input": "behavior::rest", "op": "==", "value": true }
          ],
          "reverse": true,
          "reverseOfEdgeId": "edge_001"
        }
      ],
      "viewport": { "x": 0, "y": 0, "zoom": 0.8 },
      "inputs": [
        { "name": "behavior::attention", "type": "boolean", "default": false },
        { "name": "behavior::rest", "type": "boolean", "default": true }
      ]
    }
  }'

Update the Graph

Update a canvas by sending a full replacement graph via PATCH. The entire graph (nodes, edges, inputs) is replaced - there is no partial update.

Delete a Canvas

Remove a canvas from a collection. This removes the graph definition; the underlying items and their generated assets stay in the collection.

Example: Sleep/Wake Canvas

A complete two-state canvas where the mascot sleeps by default and wakes up when the desktop runtime resolves direct interaction as behavior::interact.

curl -X POST https://api.masko.ai/v1/collections/COLLECTION_ID/canvases \
  -H "Authorization: Bearer masko_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Sleep Wake",
    "graph": {
      "nodes": [
        { "id": "ast_sleeping", "x": 0, "y": 0, "itemName": "Sleeping" },
        { "id": "ast_awake", "x": 400, "y": 0, "itemName": "Awake" }
      ],
      "edges": [
        {
          "id": "edge_wake",
          "source": "ast_sleeping",
          "target": "ast_awake",
          "duration": 3,
          "description": "waking up with a stretch and yawn",
          "conditions": [
            { "input": "behavior::interact", "op": "==", "value": true }
          ],
          "priority": 10
        },
        {
          "id": "edge_sleep",
          "source": "ast_awake",
          "target": "ast_sleeping",
          "duration": 4,
          "description": "slowly falling asleep, eyes drooping",
          "conditions": [
            { "input": "behavior::rest", "op": "==", "value": true }
          ],
          "priority": 10
        }
      ],
      "viewport": { "x": 0, "y": 0, "zoom": 0.8 },
      "inputs": [
        { "name": "behavior::interact", "type": "boolean", "default": false },
        { "name": "behavior::rest", "type": "boolean", "default": true }
      ]
    }
  }'
Tip

After creating a canvas with a graph, use the generate-all endpoint to generate all transition animations at once. Reverse transitions are created for free with auto_reverse.