Skip to content
maskostudioDocs

All pages

  • Overview/docs/use-cases
  • Mobile app/docs/use-cases#mobile
  • Website/docs/use-cases#website
  • Desktop/docs/use-cases#desktop
  • AI agents/docs/use-cases#agents
  • Videos/docs/use-cases#videos
  • Brand/docs/use-cases#brand
  • Start building/docs/ai-agents
  • Set up once/docs/ai-agents#set-up-once
  • Ask in plain words/docs/ai-agents#ask-in-plain-words
  • What your agent does/docs/ai-agents#what-your-agent-does
  • MCP server/docs/mcp
  • Masko for Cursor/docs/ai-tools/cursor
  • Masko in ChatGPT/docs/ai-tools/chatgpt
  • Start here/docs/tutorials#start-here
  • All tutorials/docs/tutorials
  • Create your first mascot/docs/tutorials/first-mascot
  • Create an animated character/docs/tutorials/first-animation
  • Make a character talk/docs/tutorials/make-it-talk
  • Put your mascot on your website/docs/tutorials/mascot-on-your-website
  • Guide onboarding with a talking mascot/docs/tutorials/web-onboarding-mascot
  • Add a mascot that celebrates progress and welcomes users back/docs/tutorials/web-progress-companion
  • Add a talking support mascot in the bottom right/docs/tutorials/web-support-mascot
  • Show a mascot while your AI thinks/docs/tutorials/ai-loading-mascot
  • Give your AI agent a face/docs/tutorials/ai-agent-face
  • Animate the mascot you already have/docs/tutorials/animate-your-mascot
  • How to make Telegram stickers/docs/tutorials/telegram-stickers
  • How to make Discord stickers/docs/tutorials/discord-stickers
  • Quickstart: your first animation/docs/quickstart
  • Terminal quickstart/docs/terminal
  • API keys and workspaces/docs/authentication
  • CLI browser login/docs/authentication/cli
  • How mascots work/docs/how-mascots-work
  • Credits and costs/docs/credits
  • From text/docs/create/from-text
  • From an image/docs/create/from-image
  • From a website/docs/create/from-website
  • From a brand book/docs/create/from-brand-book
  • Reference images/docs/manage/references
  • What you can generate/docs/generation
  • Pose-to-animation walkthrough/docs/generate/workflow
  • Images and poses/docs/generate/images
  • Animations/docs/generate/animations
  • Talking animations/docs/generate/talking
  • Logos, backgrounds and stickers/docs/generate/design-assets
  • Cursor follower/docs/generate/cursor-follower
  • Batch generation/docs/generate/batch
  • Image and video exports/docs/manage/media-exports
  • Canvases and releases/docs/canvas
  • Build a canvas/docs/canvas/build
  • Canvas templates/docs/canvas/templates
  • Generate canvas assets/docs/canvas/generate-all
  • Export a canvas/docs/canvas/export
  • Overview and demo/docs/player
  • Play one animation/docs/player/one-animation
  • Play a canvas/docs/player/canvas
  • Hosting and file formats/docs/generate/cdn
  • File sizes/docs/generate/file-sizes
  • Float a mascot on your Mac/docs/integrations/desktop
  • Jobs and polling/docs/manage/jobs
  • Webhooks/docs/manage/webhooks
  • Manage mascots and assets/docs/manage/collections
  • All endpoints/docs/reference
  • Requests and responses/docs/reference/conventions
  • Errors and recovery/docs/reference/errors
  • Command line reference/docs/terminal/commands
  • TypeScript SDK/docs/sdk/typescript
  • Changelog/docs/reference/changelog
  • Migrate to mascots and canvases/docs/manage/studio-migration
Get an API key
Documentation

Build a canvas

Define how your mascot reacts with a graph of poses, transitions and conditions.

What is a Canvas?

A state machine: each node is a pose (image or animation), and each edge is a video played when moving between nodes once its conditions match. Conditions usually check resolved inputs such as behavior::interact, behavior::attention or node::nodeTime. The Masko player plays a release of the canvas in your app.

Nodes & Edges

Nodes have a stable id, an assetId from a completed pose in this mascot (replace the example UUIDs), an itemName label and a position on the editor grid.

Edges connect a source node to a target node and can set:

  • conditions: rules that trigger the transition.
  • priority: the highest number wins when several edges can fire.
  • speed: playback multiplier, default 1 (1.5 is faster, 0.5 slower).
  • speedInput: the name of a number input in graph.inputs that the speed follows: the clip plays at speed times the value your app sets, kept between 0.2 and 4, so set('tempo', 1.3) speeds up the clips tied to it. Set it with PATCH /v1/canvases/CANVAS_ID/edges/EDGE_ID and { "speedInput": "tempo" }; null removes it.
  • duration: video length in seconds, used for generation.
  • description: the animation prompt for the transition video.

An edge whose source and target are the same node is an animation that starts and ends on that pose's picture. A node can have several. The first one without conditions is the pose's loop. Each time an animation ends, the player plays a move whose conditions hold, else the first of the pose's own animations whose conditions hold, else the loop, which gives way now and then to another of its animations without conditions. An input your app holds on (behavior::working and action::reading together) plays its animation again and again; one it sends once plays it once.

  • reverse: plays another edge's video backwards instead of generating a new one.

source: "*" (any state) fires from any node, for global triggers such as an error state or reset.

Conditions & Inputs

A condition has an input name (such as "behavior::interact"), an op ("==", "!=", ">", "<", ">=", "<=") and a value. Input types:

  • boolean, such as behavior::interact or behavior::working.
  • number, such as node::nodeTime or node::loopCount.
  • trigger: fire-once events. New desktop graphs should use resolved behavior and action inputs instead.

Create a Canvas

Create an empty canvas with POST /v1/canvases (project_id, mascot_id, name; see the canvas guide), then PATCH its graph with the returned hash:

curl -X PATCH https://api.masko.ai/v1/canvases/CANVAS_ID \
  -H "Authorization: Bearer masko_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "expected_graph_hash": "GRAPH_HASH_FROM_CREATE",
  "graph": {
    "nodes": [
      {
        "id": "idle",
        "x": 0,
        "y": 0,
        "itemName": "Idle",
        "assetId": "6aec5b1a-cb7f-5f10-8d6e-235ec16b4d97"
      },
      {
        "id": "waving",
        "x": 300,
        "y": 0,
        "itemName": "Waving",
        "assetId": "18d4fd5e-7193-57f1-8af9-f3bdff007783"
      }
    ],
    "edges": [
      {
        "id": "edge_001",
        "source": "idle",
        "target": "waving",
        "duration": 4,
        "description": "transitioning from idle to waving",
        "conditions": [
          {
            "input": "behavior::attention",
            "op": "==",
            "value": true
          }
        ]
      },
      {
        "id": "edge_002",
        "source": "waving",
        "target": "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
      }
    ],
    "initialNode": "idle"
  }
}'

Update the Graph

PATCH replaces the complete graph (nodes, edges, inputs, viewport). Fetch the canvas first and send its graph_content_hash as expected_graph_hash; a stale hash returns 409 and leaves the newer graph untouched.

Delete a Canvas

Removes the graph; its items and generated assets stay in the mascot. A canvas with checkpoints or releases returns 409 conflict.

Example: Sleep/Wake Canvas

Two states: the mascot sleeps by default and wakes on behavior::interact, which the desktop runtime resolves from direct interaction.

curl -X PATCH https://api.masko.ai/v1/canvases/CANVAS_ID \
  -H "Authorization: Bearer masko_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Sleep Wake",
  "graph": {
    "nodes": [
      {
        "id": "sleeping",
        "x": 0,
        "y": 0,
        "itemName": "Sleeping",
        "assetId": "d81e7218-4d80-548c-bcfc-968ccf1d5d80"
      },
      {
        "id": "awake",
        "x": 400,
        "y": 0,
        "itemName": "Awake",
        "assetId": "cf03e485-3d80-54a1-8e5f-4d038116c25c"
      }
    ],
    "edges": [
      {
        "id": "edge_wake",
        "source": "sleeping",
        "target": "awake",
        "duration": 4,
        "description": "waking up with a stretch and yawn",
        "conditions": [
          {
            "input": "behavior::interact",
            "op": "==",
            "value": true
          }
        ],
        "priority": 10
      },
      {
        "id": "edge_sleep",
        "source": "awake",
        "target": "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
      }
    ],
    "initialNode": "sleeping"
  }
}'

Validate before generating

curl https://api.masko.ai/v1/canvases/CANVAS_ID/validate \
  -H "Authorization: Bearer $MASKO_API_KEY"

The response has data.valid, data.issues (each with code, message, severity and optional node or edge ID) and data.summary.errors / warnings. HTTP 200 means the check ran, not that the graph is valid. Fix structural errors before paid generation; missing-video issues clear once generation finishes, so validate again before publishing.

Validation checks only the stored graph. Use media readiness to know whether the character can play.