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, scenes 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

Play a canvas

A released canvas holds your mascot's poses, the moves between them and the rules that pick them. The Masko player plays it: the right clip at the right moment, from pose to pose without a flicker. Your app only tells it what is happening, with set and fire. Install the player for your platform first.

1. Release the canvas

Release the canvas (checkpoints and releases). GET /v1/canvases/CANVAS_ID/history lists its releases with their IDs.

2. Fetch the delivery on your backend

The player reads a release delivery: the media links and the canvas rules of one release. Your backend fetches it with your API key, after checking that the person may see the mascot, and forwards data.delivery to your app. The player also accepts the whole response.

// GET /api/mascot-delivery, a route of your own server.
export async function GET() {
  const res = await fetch(
    `https://api.masko.ai/v1/canvases/${CANVAS_ID}/releases/${RELEASE_ID}/delivery?target=web`,
    { headers: { Authorization: `Bearer ${process.env.MASKO_API_KEY}` } }
  );
  const body = await res.json();
  if (!res.ok) throw new Error(body.error.message);
  return Response.json(body.data.delivery, {
    headers: { 'Cache-Control': 'no-store' }
  });
}

target picks the files for the platform:

targetForFiles
webBrowsersTransparent WebM and HEVC
appleiPhone, iPad and Mac appsHEVC with alpha
androidAndroid appsStacked MP4 (colour above, transparency below)

A React Native app asks for apple on iOS and android on Android.

Apps also add formats=av1, the value of the player's formats (MaskoPlayer.formats on Apple and Android, maskoFormats() in React Native). Each clip that has a stacked AV1 file then comes as videos.av1 in place of HEVC or the stacked MP4: the same picture, two to three times lighter at the same look. The Apple player decodes it on every iPhone, iPad and Mac; on Android, formats says av1 on phones with an AV1 decoder (Android 10 and later ship one), and older phones keep the stacked MP4. Animations made from October 27, 2026 have the AV1 file. Browsers ignore formats. One 5-second animation at 720 px is about 0.24 MB in AV1, against 2.2 MB in HEVC; animation file sizes has every format and a calculator.

The delivery holds signed links that expire, not a credential. Give the player getDelivery, a function that asks your backend for the same release again: the player calls it before the links expire and swaps them without stopping the mascot. release.id names what plays, so a fresh delivery with the same id only renews the links, and deliveryAccess.signedUrlExpiresAt says when they stop working. Never put your API key in a website or a shipped app. The JSON Schema describes the whole delivery.

3. Mount the player

import { mountMascot } from '@masko/player';

const getDelivery = () => fetch('/api/mascot-delivery').then((r) => r.json());

const mascot = mountMascot(document.getElementById('mascot'), {
  delivery: await getDelivery(),
  getDelivery
});

mascot.set('working', true);

In React Native, events are props: onPose, onClip, onReactionDone, onMovement and onError.

4. Drive it from your app

Your app tells the player what is happening. The canvas decides which clip plays.

  • set(name, value) holds a boolean or a number until you set that name again: set('working', true) when a request starts, set('working', false) when it ends. Several names can be on at once.
  • fire(name) sends one event: fire('celebrate') when a lesson ends. The reactionDone event tells you when it is done.
  • Names. A full name such as behavior::working is used as given. A bare name is the canvas input with that name, else behavior:: for a behavior family (attention, working, focus, celebrate, error, waiting, ambient, rhythm) or a behavior:: input the canvas declares, else action::. So fire('boop') fires action::boop.
  • The person. The player does not watch the pointer, clicks or taps. Your app sends them like any value: set('behavior::pointerHover', true) while the pointer is over the mascot and false when it leaves, and fire('action::click') on a click or tap. If your app lets people drag or throw the mascot, it sets heldContact, heldShake and falling the same way.

A delivery carries today's input names, so set and fire those: older names a canvas still holds arrive renamed. behavior::interact becomes behavior::attention; claudeCode::isWorking, isIdle, isAlert, hasPending and isCompacting (and the same agent:: names) become behavior::working, rest, attention, attention and waiting; bare loopCount and nodeTime become node::loopCount and node::nodeTime; and interactive::clicked becomes action::click.

When a move starts

  • Every move starts when the loop on screen ends, so the next clip starts from the pose's own frame and nothing jumps. That includes what the person does: a hover or a click can wait up to one loop.
  • Send a value with instant ({ instant: true }, or instant: true on Apple and Android) when it must answer now, such as a grab: the loop is cut, and when no transition leads to the new pose the mascot goes straight into it. A value set off with instant, such as a let go, answers now too.
  • A fire nothing in the canvas wants is dropped at once, and a newer instant reaction drops the one it cuts, so a reaction never plays late.
  • When several behaviors are on, the canvas's priorities decide which one shows, not the order of your calls.

Calls and events

The other calls are play(), pause(), reset(), position() (the clip on screen and its time), updateDelivery(delivery) (fresh links for the release that is playing; in React and React Native, pass the new delivery prop) and the sound setting. The events are pose, clip, reactionDone, movement (a move with a movement is on screen, for your app to move the mascot over its seconds; the player never moves it) and error. preview.showPose and preview.playMove are for editors; an app reaches a pose by setting values.

Check it

Check it in the running app: each event reaches its pose and the mascot comes back when the value goes off, over light and dark backgrounds. An error event names the problem; refresh_failed means getDelivery failed, and the player asks again 30 seconds later.