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:
target | For | Files |
|---|---|---|
web | Browsers | Transparent WebM and HEVC |
apple | iPhone, iPad and Mac apps | HEVC with alpha |
android | Android apps | Stacked 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. ThereactionDoneevent tells you when it is done.- Names. A full name such as
behavior::workingis used as given. A bare name is the canvas input with that name, elsebehavior::for a behavior family (attention,working,focus,celebrate,error,waiting,ambient,rhythm) or abehavior::input the canvas declares, elseaction::. Sofire('boop')firesaction::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 andfalsewhen it leaves, andfire('action::click')on a click or tap. If your app lets people drag or throw the mascot, it setsheldContact,heldShakeandfallingthe 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 }, orinstant: trueon 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 withinstant, such as a let go, answers now too. - A
firenothing 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.
