# Masko > Create mascots for your product and bring them to life. Masko includes a web creation studio, a REST API, a Mac desktop companion, a mascot marketplace and Masko Life. Use Studio or the REST API to generate assets. Use exported files or hosted URLs in your own product, and connect its events to authored mascot behavior. Agent integrations, desktop notifications and the Mac runtime belong to Masko for Mac. Masko Life is the companion you raise and care for. ## What you can do with Masko Use cases by where the mascot lives. "Works today" means Masko makes it end to end. "You assemble it" means Masko makes the mascot, its clips and voice, and your own code joins them. Each use case links to its tutorial when one exists, otherwise to the most relevant guide. Overview with the results playing: https://masko.ai/docs/use-cases ### Mobile app - Add a coach that shows every exercise (works today): One animation per exercise, plus cheers between sets. Guide: https://masko.ai/docs/generate/animations.md - Show a mascot while your AI is thinking (works today): Thinking loops for long answers, then a small celebration. Tutorial: https://masko.ai/docs/tutorials/ai-loading-mascot.md - Guide your onboarding with a talking mascot (works today): One talking clip per step, in your mascot’s own voice. Tutorial: https://masko.ai/docs/tutorials/make-it-talk.md - Celebrate streaks and progress (works today): Clips for streaks, levels and finished lessons. Guide: https://masko.ai/docs/generate/animations.md - Grow a character that evolves with your users (works today): A state machine that hatches, grows and roars as they progress. Guide: https://masko.ai/docs/use-cases/evolving-character - Make a focus timer where the mascot falls if you leave (works today): It slips when you leave the app and falls after 10 seconds. Guide: https://masko.ai/docs/use-cases/focus-timer - Build a focus or Pomodoro app with a little buddy (works today): A focus loop, a break loop and a nudge when time is up. Guide: https://masko.ai/docs/generate/animations.md - Make a kids’ app with a character that talks to them (works today): A character that talks to kids in their language. Guide: https://masko.ai/docs/generate/talking.md - Put a mascot on empty screens and errors (works today): Poses and loops that make an empty list feel less cold. Guide: https://masko.ai/docs/generate/images.md - Use your mascot in your app icon and App Store screenshots (works today): An app icon and poses, ready for store images. Guide: https://masko.ai/docs/generate/design-assets.md - Ship your mascot as Lottie or transparent video on iOS and Android (works today): Lottie, dotLottie, transparent video, GIF and PNG frames. Guide: https://masko.ai/docs/manage/media-exports.md ### Website - Guide onboarding with a talking mascot (you assemble it): Welcome new users, explain each setup step, and celebrate their first completed action. Tutorial: https://masko.ai/docs/tutorials/web-onboarding-mascot.md - Add a mascot that celebrates progress and welcomes users back (you assemble it): React to completed habits and milestones, then welcome returning users to their next activity. Tutorial: https://masko.ai/docs/tutorials/web-progress-companion.md - Add a talking support mascot in the bottom right (you assemble it): Greet visitors when clicked, offer common help topics, and open the support chat. Tutorial: https://masko.ai/docs/tutorials/web-support-mascot.md ### Desktop - Get a cute desktop pet (works today): Masko for Mac puts a mascot on your screen. Guide: https://masko.ai/docs/integrations/desktop.md - Make your own mascot your desktop pet (works today): Make a mascot in Studio, then open it on your Mac. Guide: https://masko.ai/docs/integrations/desktop.md - Get a companion that reacts to your coding agents (works today): Reacts when Claude Code, Codex or Cursor works or needs you. Guide: https://masko.ai/docs/integrations/desktop.md - Run a Pomodoro with your pet (works today): The Focus and Break app times sessions with your pet. Guide: https://masko.ai/docs/integrations/desktop.md - Get water, break and breathing reminders from your pet (works today): Your pet acts out reminders on a timer, like a sip of water. Guide: https://masko.ai/docs/integrations/desktop.md - Make your pet react to your music (works today): Your pet reacts to the music you play. Guide: https://masko.ai/docs/integrations/desktop.md - Know when your Mac needs attention, through your pet (works today): Your pet reacts when something needs you, not a banner. Guide: https://masko.ai/docs/integrations/desktop.md - Make a desktop pet for your own app (works today): Every reaction animated, plus the state machine to play it. Guide: https://masko.ai/docs/use-cases/desktop-pet - Hear your pet say what your agent did (coming, needs a build first): Text bubbles today. Speaking them needs a build first. Guide: https://masko.ai/docs/integrations/desktop.md ### AI agents - Give your AI agent a face (works today): Idle, thinking and done clips your agent switches between. Tutorial: https://masko.ai/docs/tutorials/ai-agent-face.md - Let your AI agent create your mascot and its animations (works today): Through the API or MCP, it makes the mascot and its moves. Guide: https://masko.ai/docs/ai-agents.md ### Videos - Announce a new feature (works today): A short talking video in your mascot’s own voice. Tutorial: https://masko.ai/docs/tutorials/make-it-talk.md - Make a release video for each version (works today): One talking clip per release, scripted from your notes. Tutorial: https://masko.ai/docs/tutorials/make-it-talk.md - Present a product demo or explainer (works today): Your mascot talks through the product. Tutorial: https://masko.ai/docs/tutorials/make-it-talk.md - Make a video tutorial (works today): Step-by-step talking clips that show and tell. Tutorial: https://masko.ai/docs/tutorials/make-it-talk.md - Make videos for kids: lessons, stories, “repeat after me” (works today): Lessons and stories, with pauses for kids to repeat. Tutorial: https://masko.ai/docs/tutorials/make-it-talk.md - Teach a language with a mascot that speaks several (works today): One voice, many languages: Bonjour, Hola, Ciao. Tutorial: https://masko.ai/docs/tutorials/make-it-talk.md - Post skits on TikTok, Reels and X (works today): Short skits where your mascot whispers, shouts and laughs. Tutorial: https://masko.ai/docs/tutorials/make-it-talk.md - Send a personal video to each customer (works today): One talking clip per customer, with their name in it. Tutorial: https://masko.ai/docs/tutorials/make-it-talk.md - Send a birthday or holiday greeting with the person’s name (works today): A greeting your mascot says out loud, with their name. Tutorial: https://masko.ai/docs/tutorials/make-it-talk.md - Play your update in a video call instead of attending (works today): A talking clip of your update to play in the call. Tutorial: https://masko.ai/docs/tutorials/make-it-talk.md - Recap each meeting, or send a weekly team update (you assemble it): You turn the transcript into a line. Your mascot says it. Guide: https://masko.ai/docs/generate/talking.md - Summarize a podcast or an article (you assemble it): You write the summary. Your mascot reads it out. Guide: https://masko.ai/docs/generate/talking.md - Explain your numbers in a video (you assemble it): You bring the numbers. Your mascot presents them. Guide: https://masko.ai/docs/generate/talking.md - Host a YouTube series with your character (you assemble it): Masko makes each talking scene. You edit the episodes. Guide: https://masko.ai/docs/generate/talking.md ### Brand - Create a mascot that matches your logo and colors (works today): Start from text, your logo, your website or a brand book. Tutorial: https://masko.ai/docs/tutorials/first-mascot.md - Animate the mascot you already have (works today): Upload your character and animate that exact image. Tutorial: https://masko.ai/docs/tutorials/animate-your-mascot.md - Build a library of poses and expressions (works today): Poses and expressions of one character, in one place. Guide: https://masko.ai/docs/generate/images.md - Turn your pet, or yourself, into an illustrated character (works today): From a photo to an illustrated character you can animate. Guide: https://masko.ai/docs/create/from-image.md - Make a logo or app icon from your mascot (works today): Logos and app icons from your mascot, SVG when you need it. Guide: https://masko.ai/docs/generate/design-assets.md - Make stickers for WhatsApp, Telegram and Discord (works today): Image and animated stickers, with a Telegram pack ready. Tutorial: https://masko.ai/docs/tutorials/telegram-stickers.md - Make backgrounds for your app and site (works today): A place in your mascot’s style, where its animation plays. Guide: https://masko.ai/docs/generate/design-assets.md - Dress your mascot for seasons or campaigns (works today): Outfit variants: chef, rain gear, space suit. Guide: https://masko.ai/docs/manage/collections.md - Make mascots for your agency’s clients (works today): A project per client, with its own mascots and files. Guide: https://masko.ai/docs/how-mascots-work.md - Sell your mascot on the marketplace (works today): Submit it to the Masko marketplace for others to get. Guide: https://masko.ai/docs/manage/collections.md#publish-to-the-marketplace ## Tutorials Step-by-step guides that show the finished result first, then each step in Studio and with the REST API, with the credits before you spend them. Times are estimates. New to Masko? Follow the four Start here tutorials in order. All tutorials: https://masko.ai/docs/tutorials - [Create your first mascot](https://masko.ai/docs/tutorials/first-mascot.md): Start here, step 1. Describe a character in one sentence and get your first Masko mascot, in Studio or with the REST API. About 5 minutes and 1 credit. - [How to create an animated character](https://masko.ai/docs/tutorials/first-animation.md): Start here, step 2. Create an animated character step by step: make the character, then its first transparent five-second loop, in Studio or with the REST API. About 12 credits. (About 15 min, 12 credits) - [Lip sync animation: how to make a character talk](https://masko.ai/docs/tutorials/make-it-talk.md): Start here, step 3. How to lip sync an animation without drawing mouth shapes: give your character a voice, write a line, and get a transparent talking video with sound. (About 15 min, 13 credits) - [Put your mascot on your website](https://masko.ai/docs/tutorials/mascot-on-your-website.md): Start here, step 4. Play a transparent mascot animation on any web page with the Masko player, in plain HTML or React, using its hosted links. About 5 minutes and no credits. - [Guide onboarding with a talking mascot](https://masko.ai/docs/tutorials/web-onboarding-mascot.md): Build the Fold web demo with Nori: create the assets, connect real interactions, and download the complete React source. (About 45 min, from 42 credits) - [Add a mascot that celebrates progress and welcomes users back](https://masko.ai/docs/tutorials/web-progress-companion.md): Build the Little Wins web demo with Pip: create the assets, connect real interactions, and download the complete React source. (About 35 min, from 21 credits) - [Add a talking support mascot in the bottom right](https://masko.ai/docs/tutorials/web-support-mascot.md): Build the Leaf & Loom web demo with Fern: create the assets, connect real interactions, and download the complete React source. (About 35 min, from 42 credits) - [Show a mascot while your AI thinks](https://masko.ai/docs/tutorials/ai-loading-mascot.md): Make a thinking loop, a waiting loop and a celebration, then switch between them without a flicker while an AI request runs. About 33 credits. (About 25 min, 33 credits) - [Give your AI agent a face](https://masko.ai/docs/tutorials/ai-agent-face.md): Show what your AI agent is doing with your mascot: thinking, searching, needs approval, done or error, driven by Claude Code and Codex hooks. (About 20 min, 66 credits) - [Animate the mascot you already have](https://masko.ai/docs/tutorials/animate-your-mascot.md): Upload your character as a PNG, logo or drawing and get a five-second transparent loop that keeps its look, in Studio or with one REST call. (About 10 min, 10 credits) - [How to make Telegram stickers](https://masko.ai/docs/tutorials/telegram-stickers.md): Make Telegram stickers of your own character, image and animated, and add them as one pack from a QR code. No bot, no file conversion, no Telegram setup. (About 20 min, from 1 credit) - [How to make Discord stickers](https://masko.ai/docs/tutorials/discord-stickers.md): Make Discord stickers of your own character, animated and image, in the 320 by 320 format Discord asks for, then upload them to your server. (About 20 min, from 1 credit) ## Products and website ### Masko Studio - [Masko Studio](https://masko.ai/studio): The web studio for mascot images, animations, logos, backgrounds, stickers and interactive canvases. Start from a description, website or existing artwork. - [App mascot examples](https://masko.ai/studio/app-mascots): Explore how mascots support apps and websites, then create your own character with Masko Studio. - [Mascot guide](https://masko.ai/mascot-101): Introduction to mascots and their uses. - [Masko Studio for devices](https://masko.ai/studio/devices): ESP32 character animation - [Muse Gadgets alternative](https://masko.ai/studio/devices/muse-gadgets): Muse Gadgets alternative - [Masko Studio API](https://masko.ai/studio/api): mascot generation API - [Masko guides](https://masko.ai/guides): mascot guides - [How to Animate a Mascot from an Image](https://masko.ai/guides/how-to-animate-a-mascot): Learn how to animate an existing mascot in Masko Studio or through the API: upload your image, describe the movement, and export a transparent video for your app. - [How to Make an AI Explainer Video with a Talking Mascot](https://masko.ai/guides/how-to-make-an-ai-explainer-video): Make an AI explainer video with your own talking mascot as presenter. Pick a voice, direct its gestures, and place it over your visuals. Full example included. - [Fitness app mascots](https://masko.ai/studio/app-mascots/fitness): fitness app mascot - [Learning app mascots](https://masko.ai/studio/app-mascots/learning): learning app mascot - [Meditation app mascots](https://masko.ai/studio/app-mascots/meditation): meditation app mascot - [Savings app mascots](https://masko.ai/studio/app-mascots/savings): savings app mascot - [Cooking app mascots](https://masko.ai/studio/app-mascots/cooking): cooking app mascot - [Reading app mascots](https://masko.ai/studio/app-mascots/reading): reading app mascot - [Sleep app mascots](https://masko.ai/studio/app-mascots/sleep): sleep app mascot - [Gardening app mascots](https://masko.ai/studio/app-mascots/gardening): gardening app mascot - [Focus app mascots](https://masko.ai/studio/app-mascots/focus): focus app mascot - [Travel app mascots](https://masko.ai/studio/app-mascots/travel): travel app mascot - [Company mascots](https://masko.ai/studio/company-mascot): company mascot generator - [Brand book to mascot](https://masko.ai/studio/brand-book): mascot brand book - [Cursor follower](https://masko.ai/studio/cursor-follower): cursor follower - [AI agent mascots](https://masko.ai/studio/ai-agent-mascot): AI agent mascot generator - [Animation](https://masko.ai/studio/animation): Create an animated character from one picture. The AI animated character generator makes loops, reactions and animated mascots as transparent video. - [Talking characters](https://masko.ai/studio/talking): Make a talking cartoon of your mascot: give it a character voice, write the line, and get a talking animation with AI lip sync on a transparent background. - [Stickers](https://masko.ai/studio/stickers): Make image and animated stickers of your own character. Animated Discord stickers come as 320 by 320 APNG; Telegram gets a full pack from one QR code. - [Logos](https://masko.ai/studio/logos): Turn your mascot into a custom logo. Explore graphic styles, download app icons and favicons, and generate an editable SVG with Masko Studio. - [Mascot state machine](https://masko.ai/studio/mascot-state-machine): Build a mascot state machine. Connect your mascot's poses, let Masko generate every transition, and let your app decide how your mascot reacts. ### Masko for Mac - [Masko for Mac](https://masko.ai/desktop): A desktop companion for macOS 14 or newer. Start free with Ko, choose or create a mascot, and connect it to supported AI agents. Integration capabilities vary by agent; see the integration guides. - [Developers: the masko command and SDKs](https://masko.ai/studio/desktop): Make and download mascots from the terminal, scripts, Swift, Node.js or Electron with the masko command, signed in with your browser. Requires an Apple Silicon Mac with macOS 14 or newer. It does not show mascots on screen: Masko for Mac does. - [Download Masko for Mac](https://masko.ai/download): Mac download and installation information. - [Companions](https://masko.ai/companions): Choose an AI desktop companion or playful desktop pet for Mac. Start with Ko, find a finished character, or create a mascot that reacts to your work. - [AI desktop companion](https://masko.ai/companions/ai-desktop-companion): Meet an AI desktop companion that lives beside your work on Mac, shows when supported AI agents need you, and performs useful companion apps. - [Desktop pet for Mac](https://masko.ai/companions/desktop-pet-for-mac): Get an interactive desktop pet for Mac that rests, reacts, reminds you, and follows AI-agent activity. Start free with Ko or create your own. - [Integrations](https://masko.ai/integrations): Connect Claude Code, Codex, Cursor, Grok Build, Kimi Code, and Hermes Agent to one Masko desktop mascot with visible status on Mac. - [Claude Code](https://masko.ai/integrations/claude-code): Claude Code’s own mascot is Clawd. Masko gives Claude Code your own animated mascot on your Mac that shows when it works, asks, or finishes. - [Codex](https://masko.ai/integrations/codex): Give Codex a custom desktop pet on Mac. See working, waiting, approvals, completion, errors, and subagent activity, then return to Codex when needed. - [Cursor](https://masko.ai/integrations/cursor): Connect Cursor hooks to a Masko desktop pet on Mac. See agent sessions, tool use, and file edits, then return to the native Cursor agent prompt. - [Grok Build](https://masko.ai/integrations/grok-build): Connect Grok Build to a Masko desktop pet on Mac. See agent activity, questions, completion, and errors, with supported responses and native handoff. - [Kimi Code](https://masko.ai/integrations/kimi-code): Connect Kimi Code to a Masko desktop pet on Mac. See working, questions, completion, and errors, then return to the native Kimi Code prompt. - [Hermes Agent](https://masko.ai/integrations/hermes-agent): Connect Hermes Agent to a Masko desktop pet on Mac. See agent activity, clarification questions, completion, errors, and supported response paths. - [Features](https://masko.ai/features): See AI agent notifications, permission requests, supported approvals, and terminal return through one animated Masko desktop mascot on your Mac. - [AI agent notifications](https://masko.ai/features/ai-agent-notifications): Get visible AI agent notifications for working, waiting, questions, completion, interruption, and errors through an animated Masko mascot on Mac. - [Permissions and approvals](https://masko.ai/features/permissions-and-approvals): See AI agent permission requests through a Masko desktop mascot, answer directly where supported, or return safely to the exact native prompt. - [Terminal focus](https://masko.ai/features/terminal-focus): Use a Masko desktop mascot notification to return to the exact terminal, tab, or agent surface that needs your response without hunting for it. - [Apps](https://masko.ai/apps): Give your Masko desktop companion useful apps for focus, breaks, breathing, water, music reactions, and Mac system health through one character. ### Masko Life - [Masko Life](https://masko.ai/life): Raise a little companion, feed it, share photos and keep memories as it grows. See the product page for current Android and iOS availability. - [Masko Life privacy](https://masko.ai/life/privacy): Privacy policy for Masko Life. - [Masko Life terms](https://masko.ai/life/terms): Terms for Masko Life. - [Masko Life account deletion](https://masko.ai/life/delete-account): How to request account deletion. ### Marketplace and general information - [Masko](https://masko.ai/): Create mascots from text or reference artwork, generate consistent poses and transparent animations, and use the assets in apps, agents and websites. - [Marketplace](https://masko.ai/marketplace): Sign in to browse mascot listings. Check each listing for included assets, licensing, price and compatible uses; Studio libraries and existing Desktop playable purchases have different content. - [Affiliate program](https://masko.ai/affiliates): Masko affiliate information. - [Free mascot tools](https://masko.ai/tools): free mascot tools - [Discord sticker and emoji maker](https://masko.ai/tools/discord-emoji-maker): discord emoji maker - [Telegram sticker maker](https://masko.ai/tools/telegram-sticker-maker): telegram sticker maker - [WhatsApp sticker maker](https://masko.ai/tools/whatsapp-sticker-maker): whatsapp sticker maker - [Slack emoji maker](https://masko.ai/tools/slack-emoji-maker): slack emoji maker - [Twitch emote maker](https://masko.ai/tools/twitch-emote-maker): twitch emote maker - [Transparent GIF maker](https://masko.ai/tools/transparent-gif-maker): transparent gif maker - [Sprite sheet generator](https://masko.ai/tools/sprite-sheet-generator): sprite sheet generator - [Video file size calculator](https://masko.ai/tools/video-file-size-calculator): video file size calculator - [Character expression sheet](https://masko.ai/tools/character-expression-sheet): character expression sheet - [Mascot name generator](https://masko.ai/tools/mascot-name-generator): mascot name generator - [Brand archetype quiz](https://masko.ai/tools/brand-archetype-quiz): brand archetype quiz - [Virtual pet: Dumple](https://masko.ai/tools/virtual-pet): virtual pet online ## Pricing, licensing and support - [Credits and generation costs](https://masko.ai/docs/credits.md): Current generation rates, balance, estimates and failure handling. Studio and API generation use credits; some Desktop interfaces call them Masks. - [Terms](https://masko.ai/terms): Terms for generated outputs, source material and commercial use. Marketplace listings specify their available licenses. - [Privacy](https://masko.ai/privacy): Masko privacy policy. - [Support](mailto:paul@masko.ai): Product, account and billing questions. ## Studio REST API v1 and programmatic access The REST API lets applications and AI agents create and manage mascots, assets and canvases. Masko for Mac agent integrations connect agent activity to the Mac companion; see the Masko for Mac section above. - Production base URL: https://api.masko.ai/v1. Documented operation paths already include /v1; do not add it twice. - Protected requests use Authorization: Bearer . Keep secret credentials on your server or in your local environment. - [MCP server](https://masko.ai/docs/mcp.md): https://masko.ai/api/mcp exposes mascot, animation, canvas and job tools. Claude Code, Codex and VS Code connect with an API key in the Authorization header; Claude, ChatGPT and Cursor sign in with OAuth. Voices and talking use the REST API. - Current canonical contract: 2026-09-26. Use /v1/mascots and /v1/canvases. Send Masko-API-Version: 2026-09-26 for canonical fields on shared paths such as jobs and assets. Legacy /v1/collections routes remain compatible and deprecated. - Read each operation's schema, review generation costs, retain returned IDs and follow the accepted job's poll_url. Use Idempotency-Key on supported POST requests for safe retries. - [OpenAPI specification](https://api.masko.ai/v1/openapi.json): Authoritative deployed paths, request fields, response schemas and API version. Check this for rollout availability. - [Documentation query](https://api.masko.ai/v1/docs): Public text index; use ?q=animation to search or ?slug=generate/animations for a complete guide. Documentation and OpenAPI require no authentication. - [Full documentation](https://masko.ai/llms-full.txt): Product overview, all public guides and an operation index generated from OpenAPI. - [Feedback](https://masko.ai/docs/ai-agents.md#send-feedback-to-masko): When something in Masko is confusing, broken or missing, or works well, tell the Masko team with POST /v1/feedback or the MCP tool send_feedback, then carry on with the task. Only summary is required, read-only keys work, and each account can send 5 a minute and 30 a day. This index is generated from the documentation and public website page registry shipped with this deployment. Documentation changes appear after deployment; these text endpoints may be cached for one hour. This is not a live catalog of marketplace inventory, account balances or subscription offers. ## Documentation ### Use cases - [Overview](https://masko.ai/docs/use-cases): Everything you can do with a Masko mascot, by platform, with each result playing and its status. ### Build with your AI agent - [Build with your AI agent: Claude Code, Codex, Cursor](https://masko.ai/docs/ai-agents.md): Your coding agent can create your mascot, animate it and put it in your app. Give it a Masko API key and the MCP server, then ask in plain words. - [Masko MCP server: Claude Code, Codex, Cursor, Claude](https://masko.ai/docs/mcp.md): Connect Claude Code, Codex, Cursor, Claude, ChatGPT or VS Code to Masko's MCP server, then create, animate and ship mascots from a chat. - [Masko for Cursor: mascots in your code editor](https://masko.ai/docs/ai-tools/cursor.md): Create mascots, animate your own artwork, and integrate transparent animations into your app with Masko's Cursor plugin. - [Masko in ChatGPT: create and animate mascots](https://masko.ai/docs/ai-tools/chatgpt.md): Connect your Masko account to create mascots, animate uploaded characters, build interactive canvases, and integrate completed assets into your app. ### Tutorials - [All tutorials](https://masko.ai/docs/tutorials): Every tutorial by platform: the four Start here steps, the ones that are written and the ones coming soon. - [Create your first mascot](https://masko.ai/docs/tutorials/first-mascot.md): Describe a character in one sentence and get your first Masko mascot, in Studio or with the REST API. About 5 minutes and 1 credit. - [How to create an animated character](https://masko.ai/docs/tutorials/first-animation.md): Create an animated character step by step: make the character, then its first transparent five-second loop, in Studio or with the REST API. About 12 credits. - [Lip sync animation: how to make a character talk](https://masko.ai/docs/tutorials/make-it-talk.md): How to lip sync an animation without drawing mouth shapes: give your character a voice, write a line, and get a transparent talking video with sound. - [Put your mascot on your website](https://masko.ai/docs/tutorials/mascot-on-your-website.md): Play a transparent mascot animation on any web page with the Masko player, in plain HTML or React, using its hosted links. About 5 minutes and no credits. - [Guide onboarding with a talking mascot](https://masko.ai/docs/tutorials/web-onboarding-mascot.md): Build the Fold web demo with Nori: create the assets, connect real interactions, and download the complete React source. - [Add a mascot that celebrates progress and welcomes users back](https://masko.ai/docs/tutorials/web-progress-companion.md): Build the Little Wins web demo with Pip: create the assets, connect real interactions, and download the complete React source. - [Add a talking support mascot in the bottom right](https://masko.ai/docs/tutorials/web-support-mascot.md): Build the Leaf & Loom web demo with Fern: create the assets, connect real interactions, and download the complete React source. - [Show a mascot while your AI thinks](https://masko.ai/docs/tutorials/ai-loading-mascot.md): Make a thinking loop, a waiting loop and a celebration, then switch between them without a flicker while an AI request runs. About 33 credits. - [Give your AI agent a face](https://masko.ai/docs/tutorials/ai-agent-face.md): Show what your AI agent is doing with your mascot: thinking, searching, needs approval, done or error, driven by Claude Code and Codex hooks. - [Animate the mascot you already have](https://masko.ai/docs/tutorials/animate-your-mascot.md): Upload your character as a PNG, logo or drawing and get a five-second transparent loop that keeps its look, in Studio or with one REST call. - [How to make Telegram stickers](https://masko.ai/docs/tutorials/telegram-stickers.md): Make Telegram stickers of your own character, image and animated, and add them as one pack from a QR code. No bot, no file conversion, no Telegram setup. - [How to make Discord stickers](https://masko.ai/docs/tutorials/discord-stickers.md): Make Discord stickers of your own character, animated and image, in the 320 by 320 format Discord asks for, then upload them to your server. ### Start - [API quickstart: your first mascot animation](https://masko.ai/docs/quickstart.md): Create a mascot and get a five-second transparent animation of it waving, hosted and ready for your app or website, with four REST calls and 12 credits. - [Masko terminal quickstart](https://masko.ai/docs/terminal.md): Install the masko command, sign in with your browser, then make, animate and download mascots with the Masko API from your terminal or scripts. - [API keys and workspaces](https://masko.ai/docs/authentication.md): Create a Masko API key, authenticate REST requests, and choose the workspace that owns your resources and credits. - [Masko CLI login: browser and device sign-in](https://masko.ai/docs/authentication/cli.md): Connect the Masko CLI through browser approval or device login, select a workspace, and manage sessions. - [How mascots work: the Masko data model](https://masko.ai/docs/how-mascots-work.md): The Masko data model. Projects own mascots and canvases, mascots hold items and assets, and every generated file links back to the file it came from. - [Credits and generation costs](https://masko.ai/docs/credits.md): What each generation costs in credits, how to estimate work before paying, check your balance, buy credits, and handle partial failures. ### Create - [Create a mascot from text](https://masko.ai/docs/create/from-text.md): Describe your character in natural language, pick a style preset, and generate a mascot via API. Browse available presets from the styles endpoint. - [Create a mascot from an image](https://masko.ai/docs/create/from-image.md): Upload your character as a reference image. AI vision extracts a prompt that keeps future generations on model. Up to 6 references. - [Create a mascot from a website](https://masko.ai/docs/create/from-website.md): Analyze any website URL to extract page metadata, brand context, and optional mascot suggestions that can seed a text-to-mascot flow. - [Create a mascot from a brand book](https://masko.ai/docs/create/from-brand-book.md): Turn a brand book PDF or its page images into a ready mascot in one API call, with every view as a reference and its colors, style, don'ts and props filled in. - [Reference images and style consistency](https://masko.ai/docs/manage/references.md): The three kinds of reference images in the Masko API: mascot references that persist, preview hints that do not, and the source assets you animate. ### Make it move - [What you can generate and what it costs](https://masko.ai/docs/generation.md): Every asset Masko generates from a mascot, with its guide and cost: images, animations, talking animations, logos, SVGs, backgrounds and stickers. - [Pose-to-animation walkthrough](https://masko.ai/docs/generate/workflow.md): Create a mascot with reference images, generate a pose, check it, then animate that exact pose. Every REST call with its response. - [Mascot images and poses](https://masko.ai/docs/generate/images.md): Generate mascot pose images and transparent PNGs, edit existing poses, and keep the style consistent across your mascot. 1 credit per image. - [Mascot animation API: loops and transitions](https://masko.ai/docs/generate/animations.md): Generate transparent mascot animations as WebM, HEVC with alpha and MP4: loops, transitions and motion from a video. Standard costs 2 credits a second. - [Talking mascot animations with voice](https://masko.ai/docs/generate/talking.md): Give your mascot a voice and generate transparent talking animations with sound, a timed transcript and captions. 2 credits per second. - [Logos, backgrounds and stickers](https://masko.ai/docs/generate/design-assets.md): Turn an existing mascot into a logo or app icon, an editable SVG, a background for your app, or a sticker with a white border. - [Cursor follower: a mascot that follows the mouse](https://masko.ai/docs/generate/cursor-follower.md): Turn one transparent mascot image into nine gaze directions so your mascot follows the cursor on a website. 9 credits, made in Studio or with the API. - [Batch generation and AI suggestions](https://masko.ai/docs/generate/batch.md): Generate multiple mascot images and animations in a single request. AI-powered action suggestions for poses. Parallel processing with individual job tracking. - [Image and video exports](https://masko.ai/docs/manage/media-exports.md): Resize mascot images and export animations through the app or v1 API, with saved mascot settings and project or folder bulk actions. ### State machine - [Canvases: mascot state machines and releases](https://masko.ai/docs/canvas.md): Create a canvas, the state machine that decides how your mascot reacts, edit its graph safely, generate its media, save checkpoints and ship playable releases. - [Build a canvas: nodes, edges and inputs](https://masko.ai/docs/canvas/build.md): Build a mascot canvas: nodes for poses, edges for transitions, conditions on inputs, and full graph replacement with PATCH. - [Canvas templates: start from a ready graph](https://masko.ai/docs/canvas/templates.md): Start a canvas from a ready-made graph of poses, transitions and inputs. List built-in and saved templates, apply one to a new canvas, and save your own. - [Generate canvas assets](https://masko.ai/docs/canvas/generate-all.md): Generate pending node images and transition videos for a canvas with one API call. Includes explicit generated, skipped, reverse-free, and cost accounting. - [Export a canvas graph as JSON](https://masko.ai/docs/canvas/export.md): Read a completed canvas as JSON, with its nodes, edges, inputs and media URLs, or as a runtime delivery envelope. Apps play a release with the Masko player. ### Masko player - [Masko player: play your mascot on the web, iPhone, Mac, React Native, Flutter and Android](https://masko.ai/docs/player.md): The Masko player is the open-source player for Masko mascots. Install it with npm, Swift Package Manager, pub.dev or Maven Central, loop one animation, or play a canvas that reacts to your app with set and fire. - [Play one animation with the Masko player](https://masko.ai/docs/player/one-animation.md): Loop one transparent Masko animation in a website or an app with the Masko player, from its hosted WebM and HEVC files, with no backend and no API key. - [Play a canvas with the Masko player](https://masko.ai/docs/player/canvas.md): Play a released Masko canvas in your website or app with the Masko player. Your backend fetches the release delivery, the player runs every canvas rule, and your app drives it with set and fire. - [Mascot hosting, CDN and file formats](https://masko.ai/docs/generate/cdn.md): Which file to play on each platform, how to put a transparent animation on a web page, and how hosted CDN URLs and size variants work. - [Mascot animation file sizes](https://masko.ai/docs/generate/file-sizes.md): How heavy mascot animations are on a website, iPhone or Android: WebM, HEVC, stacked MP4 and stacked AV1 sizes, with a calculator for your own clips. - [Float a mascot on your Mac](https://masko.ai/docs/integrations/desktop.md): How Masko Desktop floats a mascot above your windows, how to install one from the marketplace, and what the API controls. ### Track - [Job polling API: track each generation](https://masko.ai/docs/manage/jobs.md): Track generation jobs with polling or long-polling, check a job's status, and get asset URLs when it completes. Webhooks are the alternative. - [Webhooks: job events, signatures and retries](https://masko.ai/docs/manage/webhooks.md): Get notified when Masko jobs complete or fail: timestamped HMAC-SHA256 signatures, stable event IDs for deduplication, retries over about 21 hours. - [Manage mascots and assets](https://masko.ai/docs/manage/collections.md): List projects, mascots, items and assets, export hosted links, create variants, and publish a mascot to the Marketplace with the REST API. ### Reference - [All endpoints](https://masko.ai/docs/reference): Every REST endpoint with its request fields and responses, generated from OpenAPI. - [Requests and responses](https://masko.ai/docs/reference/conventions.md): Base URLs, JSON envelopes, pagination, async generation receipts, and safe retries shared by the Masko REST API. - [Masko API error codes and recovery](https://masko.ai/docs/reference/errors.md): Complete reference for API error codes. Covers 400, 401, 402, 403, 404, 409, 429, and 500 errors with explanations, common causes, and recovery strategies. - [Command line reference](https://masko.ai/docs/terminal/commands.md): Every masko command and option, its output and exit codes: sign in, check credits, call the Masko API, retry safely, upload and download files. - [TypeScript SDK](https://masko.ai/docs/sdk/typescript.md): Call the Masko API from Node.js or Electron main on macOS with your saved Masko login, with pagination, safe retries and the API's error fields. - [Masko API changelog: every v1 update](https://masko.ai/docs/reference/changelog.md): Every update to the Masko v1 API with release dates, breaking changes, new endpoints, and migration notes for each version. - [Migrate to mascots and canvases](https://masko.ai/docs/manage/studio-migration.md): Move an integration from /v1/collections to /v1/mascots and /v1/canvases, keep current field names on shared routes, and set the animation price explicitly. ### Additional guides - [Masko in Lovable: add animated mascots to your app](https://masko.ai/docs/ai-tools/lovable.md): Connect Lovable to Masko with an API key, create a mascot and its animations from a chat, and use the finished files in your app. - [Masko for Muse: integration guide](https://masko.ai/docs/ai-tools/muse.md): Masko's Muse integration guide for creating mascots, animating your own artwork, building interactive canvases, and integrating assets into apps and websites. - [Masko skill for coding agents: setup and prompts](https://masko.ai/docs/ai-tools/skills.md): Use Masko from Claude Code, Cursor, Copilot, Codex, Gemini, Windsurf and other coding agents with an API key and a plain prompt. - [Guide onboarding with a talking mascot](https://masko.ai/docs/use-cases/web-onboarding.md): Build Fold with Nori: reference prompts, state flow, Masko v1 requests and copyable React integration. - [Add a mascot that celebrates progress and welcomes users back](https://masko.ai/docs/use-cases/web-progress-companion.md): Build Little Wins with Pip: reference prompts, state flow, Masko v1 requests and copyable React integration. - [Add a talking support mascot in the bottom right](https://masko.ai/docs/use-cases/web-support-mascot.md): Build Leaf & Loom with Fern: reference prompts, state flow, Masko v1 requests and copyable React integration. ## Complete guides ---url: /docs/ai-agents--- Source: https://masko.ai/docs/ai-agents # Start building with your AI agent Your coding agent can make your mascot for you, from the first drawing to the code that plays it in your app. You ask in plain words. The agent uses Masko's tools, shows you the cost before anything paid, and puts the finished animations in your app. It works with Claude Code, Codex, Cursor, Claude, ChatGPT and any agent that can run commands. This is Spike. A coding agent made him from one prompt and put him in a plant app. Play with him: %% one-prompt-demo %% ## Set up once Open [API keys](/api-keys), choose **Create API key**, give it a name and keep **Read + Write**. Copy the key: it is shown once. ![The Create API Key window, named "My coding agent", with Read + Write selected](https://assets.masko.ai/marketing/docs/ai-agents/api-key-dialog-ddc925fd.png "screenshot") Then set it in the terminal where you start your agent: ```bash export MASKO_API_KEY=masko_YOUR_KEY ``` Keep the key on your machine or your server. Never put it in browser code or commit it. Then add the [Masko MCP server](/docs/mcp), so your agent gets Masko's tools. In Claude Code: ```bash claude mcp add --transport http --scope user masko https://masko.ai/api/mcp \ --header "Authorization: Bearer $MASKO_API_KEY" ``` In Codex: ```bash codex mcp add masko --url https://masko.ai/api/mcp \ --bearer-token-env-var MASKO_API_KEY ``` Cursor, Claude and ChatGPT connect by signing in on masko.ai, with no key: see [the setup for each app](/docs/mcp#connect-your-app). An agent without MCP still works. With `MASKO_API_KEY` set, it calls the REST API and reads the docs at [masko.ai/llms.txt](https://masko.ai/llms.txt). Voices and talking always go through the REST API, so keep the key set even when MCP is connected. ## Ask in plain words Name Masko (masko.ai) in your first prompt so your agent knows where to look. Add "Tell me the cost first", and it shows you the credits before any paid request. Then keep asking in the same session: each step builds on the last. ### Animate your mascot If your app already has a mascot image: ```text Animate the mascot in public/mascot.png with Masko (masko.ai): a gentle idle loop, shown on the home screen instead of the still image. Tell me the cost first. ``` No mascot yet? Ask for one: ```text Create a friendly mascot for this app with Masko (masko.ai) and make it wave hello. Tell me the cost first. ``` You get a five-second loop with a transparent background, so it sits on any page. Animating your own image costs 10 credits. A new mascot adds 2 credits: one for its first drawing and one for the pose. ### Make it react ```text When someone finishes a lesson, make the mascot celebrate with a happy jump, then go back to its idle loop. ``` The agent makes one more animation, for 10 credits, and finds the place in your code where a lesson ends to play it. ### Make it talk ```text Give our mascot a calm voice, and let me hear a few options first. Then make her say: "Hi, I'm Nori. Let's make a little space for your next big idea." ``` The agent plays you three voices for 1 credit, keeps the one you pick and makes a [talking animation](/docs/generate/talking) with the sound inside, at 2 credits per second of speech: ### Make it fully interactive ```text Now make the mascot fully interactive, with smooth moves between its moods: it reacts when I tap it, cheers when I finish a task, and dozes off when I leave the app alone. ``` The agent plans one mood per moment, generates a loop for each and the moves between them, and releases the set as a [canvas](/docs/canvas). The price depends on how many moods and moves you ask for, and the agent can reuse the animations you already made. Spike, at the top of this page, has 7 moods and 14 moves on the Premium model, for 343 credits. ## What your agent does Whatever you ask for, the agent works the same way: 1. **It finds the right tool.** With the [MCP server](/docs/mcp), it calls Masko's tools. Without it, it starts at [masko.ai/llms.txt](https://masko.ai/llms.txt), which lists every guide, and takes the exact requests from the [OpenAPI specification](https://api.masko.ai/v1/openapi.json). 2. **It looks at your account first.** Checking your credits, projects and mascots is free, so it reuses the mascot you already have instead of making a copy. 3. **It tells you the cost and waits.** Estimates are free, and nothing paid starts until you say go. 4. **It generates and waits.** Each animation is a job on Masko. The agent checks the job until the files are ready. 5. **It brings the files into your app.** Transparent WebM for Chrome, Firefox and Edge, and HEVC for Safari, at the size your app shows them. 6. **It wires the mascot in and tests it.** It installs the [Masko player](/docs/player) for your platform: for a single loop, it gives the player the animation's files; for a mascot that changes clips, the canvas release, and connects your app's events to it with `set` and `fire`. Then it checks the result in the running app. The files, or the player and the code that drives it, end up in your project. Your app only plays files, and your backend fetches any private links, so the app never needs your API key. ## Use Masko in every session To stop naming masko.ai in each prompt, paste these lines into your agent's instructions file, such as `CLAUDE.md` or `AGENTS.md`. Keep what is already there. ```text ## Masko Masko (masko.ai) makes our mascot images and animations. - Use the Masko MCP tools when they are connected. - Otherwise use the REST API at https://api.masko.ai/v1 with MASKO_API_KEY from the environment. Voices and talking always use the REST API. - Docs: https://masko.ai/llms.txt lists every guide, and https://api.masko.ai/v1/openapi.json has the exact requests. - Never print or commit MASKO_API_KEY, and never put it in browser code. - Tell me the credits before any paid generation and wait for my OK. - Keep the IDs you get back. Check existing jobs before generating again. - When something in Masko is confusing, broken or missing, or works well, tell the Masko team with the send_feedback tool or POST /v1/feedback. ``` Spike's agent had lines like these, which is why his prompt does not name masko.ai. ## Good to know - Generation spends credits from your Masko account, not from your agent's subscription. Reading, estimates and checking jobs are free. See [credits and costs](/docs/credits). - If a run stops halfway, ask the agent to check its existing jobs before it starts again, so you never pay twice. - Disconnect a signed-in app under Connected apps in [Developers → MCP](/api-keys?tab=mcp). Manage keys in [API keys](/api-keys). ## Send feedback to Masko Your agent can tell the Masko team when something is confusing, broken or missing, or when it works well. The team reads every message and may reply to your account email. With the MCP server, the agent uses the `send_feedback` tool. You can also ask it: "Send Masko feedback about this." Without MCP, it calls the REST API: ```bash curl -X POST https://api.masko.ai/v1/feedback \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "summary": "The animation job failed without saying why.", "feedback_type": "api", "sentiment": "negative", "suggested_improvement": "Name the input that was wrong in the error." }' ``` Only `summary` is required. Read-only keys can send feedback too. Each account can send 5 a minute and 30 a day. Every field is in the [API reference](/docs/reference). From the [terminal](/docs/terminal), signed in with `masko login`, it is one command; the first line is the summary: ```bash masko feedback "The animation job failed without saying why." ``` ## What your agent reads | Resource | URL | | --- | --- | | MCP server, setup in [MCP server](/docs/mcp) | `https://masko.ai/api/mcp` | | Product and documentation index | [masko.ai/llms.txt](https://masko.ai/llms.txt) | | Every guide and the API operation index | [masko.ai/llms-full.txt](https://masko.ai/llms-full.txt) | | Documentation search | `GET https://api.masko.ai/v1/docs?q=animation` | | OpenAPI specification | [api.masko.ai/v1/openapi.json](https://api.masko.ai/v1/openapi.json) | --- ---url: /docs/mcp--- Source: https://masko.ai/docs/mcp # Masko MCP server The Masko MCP server gives your AI app tools to create a mascot, animate it, build an interactive canvas and bring the files into your project. You ask in plain words, and the app calls the tools. One server works for every app: ```text https://masko.ai/api/mcp ``` ## Connect your app Your app connects in one of two ways: - **Sign in**, in Claude Code, Cursor, Claude, ChatGPT and opencode. Your app opens masko.ai. You sign in, choose your personal or team workspace and approve. You never paste a key. A team connection needs an owner or admin. - **API key**, in Codex, VS Code, Claude Code run by scripts, and other apps. Open [Developers → API keys](/api-keys), choose **Create API key** and keep **Read + Write**. Copy the key: it is shown once. It belongs to the workspace you are in when you create it. Then set it in your terminal: ```bash export MASKO_API_KEY=masko_YOUR_KEY ``` Add that line to your shell profile, such as `~/.zshrc`, so every new terminal has it. Never commit the key or put it in browser code. Pick your app for its setup. The [MCP tab of the Developers page](/api-keys?tab=mcp) shows the same steps with copy buttons. ### Claude Code **Sign in, no key.** Run this in your terminal: ```bash claude mcp add --transport http --scope user masko https://masko.ai/api/mcp ``` Start Claude Code, run `/mcp`, choose masko and **Authenticate**. Your browser opens masko.ai: sign in, choose your workspace and approve. `--scope user` adds Masko to all your projects. Leave it out to add Masko to this project only. From the terminal, `claude mcp list` checks it. **Or use your API key**, for scripts and machines without a browser. Run this in the terminal where `MASKO_API_KEY` is set: ```bash claude mcp add --transport http --scope user masko https://masko.ai/api/mcp \ --header "Authorization: Bearer $MASKO_API_KEY" ``` The command saves the key in your Claude Code settings on this computer. To share the setup with your team in a repository, put this in `.mcp.json` instead. Each person keeps their own `MASKO_API_KEY`: ```json { "mcpServers": { "masko": { "type": "http", "url": "https://masko.ai/api/mcp", "headers": { "Authorization": "Bearer ${MASKO_API_KEY}" } } } } ``` ### Codex **Uses your API key.** Run this in the terminal where `MASKO_API_KEY` is set: ```bash codex mcp add masko --url https://masko.ai/api/mcp \ --bearer-token-env-var MASKO_API_KEY ``` Codex reads the key from `MASKO_API_KEY` each time it starts, so start it from a terminal where the key is set. Run `/mcp` in Codex to see Masko's tools, or `codex mcp list` from the terminal. The same setup in `~/.codex/config.toml`, which the Codex IDE extension also reads: ```toml [mcp_servers.masko] url = "https://masko.ai/api/mcp" bearer_token_env_var = "MASKO_API_KEY" ``` Codex asks before each tool that spends credits. For a script that runs `codex exec` without you, add `default_tools_approval_mode = "approve"` under `[mcp_servers.masko]`. ### Cursor **Signs in, no key.** Click to add Masko to Cursor: [Add Masko to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=masko&config=eyJ1cmwiOiJodHRwczovL21hc2tvLmFpL2FwaS9tY3AiLCJhdXRoIjp7IkNMSUVOVF9JRCI6Imh0dHBzOi8vbWFza28uYWkvb2F1dGgvY2xpZW50cy9jdXJzb3IiLCJzY29wZXMiOlsibWFza286cmVhZCIsIm1hc2tvOndyaXRlIl19fQ%3D%3D) Then open **Customize** in Cursor's sidebar, turn on masko and connect it. Sign in on masko.ai, choose a workspace and approve. To add it by hand, put this in `~/.cursor/mcp.json` for all your projects, or in `.cursor/mcp.json` for one. The client ID is public configuration, not a secret: ```json { "mcpServers": { "masko": { "url": "https://masko.ai/api/mcp", "auth": { "CLIENT_ID": "https://masko.ai/oauth/clients/cursor", "scopes": ["masko:read", "masko:write"] } } } } ``` For four ready-made workflow skills on top of the server, install [Masko for Cursor](/docs/ai-tools/cursor). ### Claude (web and desktop) **Signs in, no key.** 1. In Claude, open **Customize → Connectors**, click **+ Add**, then **Add custom connector**. 2. Name it Masko, paste `https://masko.ai/api/mcp` and click **Continue**. 3. Under Authentication choose **Sign in now**, and under OAuth client choose **Use Claude's published identity**. Leave request headers empty, then click **Add**. 4. Sign in on masko.ai, choose a workspace and approve. **Prefer an API key?** In step 3, choose **No sign in** and add a request header named `Authorization` with the value `Bearer masko_YOUR_KEY`. Everyone who uses that connector then acts with your key, so on a team plan, sign in instead. A connector added on claude.ai also works in the Claude desktop app. On Team and Enterprise plans, an owner adds it in **Organization settings → Connectors**, and each member then clicks **Connect** in **Customize → Connectors**. ### ChatGPT **Signs in, no key.** 1. Open [chatgpt.com/plugins](https://chatgpt.com/plugins), click **+**, then **Add custom MCP server**. 2. Name it Masko. Under **Connection**, enter the server URL `https://masko.ai/api/mcp` and choose **OAuth**. 3. Confirm with **I understand and want to continue**, click **Create as a plugin**, then install it. 4. Sign in on masko.ai, choose a workspace and approve. ChatGPT asks you to confirm each tool that changes something, such as a paid generation. See [Masko in ChatGPT](/docs/ai-tools/chatgpt) for uploads, canvases and buying credits. ### VS Code **Uses your API key.** Click to add Masko to VS Code. It asks for your key the first time Masko starts and keeps it out of your files: [Install Masko in VS Code](vscode:mcp/install?%7B%22name%22%3A%22masko%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fmasko.ai%2Fapi%2Fmcp%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24%7Binput%3Amasko-api-key%7D%22%7D%2C%22inputs%22%3A%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22masko-api-key%22%2C%22description%22%3A%22Masko%20API%20key%22%2C%22password%22%3Atrue%7D%5D%7D) To add it by hand for one project, put this in `.vscode/mcp.json`: ```json { "inputs": [ { "type": "promptString", "id": "masko-api-key", "description": "Masko API key", "password": true } ], "servers": { "masko": { "type": "http", "url": "https://masko.ai/api/mcp", "headers": { "Authorization": "Bearer ${input:masko-api-key}" } } } } ``` Then use Copilot Chat in agent mode. ### Other apps **Uses your API key.** Any app that connects to remote MCP servers over HTTP and can send a header works. Use: URL: `https://masko.ai/api/mcp` Header: `Authorization: Bearer masko_YOUR_KEY` Sign-in works only with Claude, ChatGPT and Cursor for now. If your app can only sign in, email [paul@masko.ai](mailto:paul@masko.ai) with its name. ## Check the connection Ask your agent: ```text Show my Masko projects, mascots and credit balance. Don't generate anything. ``` Reading is free. If something is off: | What you see | What to do | | --- | --- | | The app shows no Masko tools | Run `/mcp` (Claude Code, Codex) or open the app's MCP settings, check that masko is connected, then restart the app. | | Codex says Masko isn't available, but `/mcp` lists it | Ask it to "use the masko MCP tools". Codex can keep a server's tools out of sight until it searches for them. | | A browser sign-in opens in Claude Code | Approve it on masko.ai: Claude Code connects without a key. To use a key instead, add Masko with the `--header` command above. | | A browser sign-in opens and fails in Codex | The key is missing, revoked, or not a **Read** or **Read + Write** API key. Check `echo $MASKO_API_KEY` in the terminal that starts the app, then add Masko again. | | "This connection is read-only" | Use a **Read + Write** key, or reconnect and approve write access. | | Other projects or credits than you expected | The key or connection belongs to another workspace. Create the key in the right workspace, or reconnect and choose it. | ## Then ask in plain words ```text Create a friendly mascot for this app with Masko and make it wave hello. Tell me the cost first. ``` ```text When someone finishes a lesson, make the mascot celebrate, then go back to its idle loop. ``` ```text Add these animations to this React app, with no flicker between clips. ``` The agent shows you the cost before anything paid, follows each job until the files are ready, installs the [Masko player](/docs/player) and connects your app's events to it. More prompts are in [Build with your AI agent](/docs/ai-agents#ask-in-plain-words). ## What the tools do | Area | Tools | | --- | --- | | Account and credits | `get_account`, `get_credits`, `create_credit_checkout`, `get_credit_checkout` (the two checkout tools are not offered in ChatGPT) | | Projects and mascots | `list_projects`, `create_project`, `list_mascots`, `get_mascot`, `create_mascot`, `update_mascot` (name, context and room for movement), `import_mascot_image`, `upload_mascot_image` | | Images, backgrounds and animations | `generate_mascot_image`, `suggest_backgrounds`, `make_background`, `animate_mascot` | | Voices and talking | `get_mascot_voice`, `suggest_mascot_voices`, `sample_mascot_voices`, `keep_mascot_voice`, `make_talking_animation` | | Jobs and files | `get_job`, `list_jobs`, `get_asset`, `list_assets`, `export_asset`, `publish_mascot_assets`, `get_mascot_cdn` | | Interactive canvases | `list_canvases`, `create_canvas`, `get_canvas`, `save_canvas`, `validate_canvas`, `estimate_canvas_generation`, `generate_canvas`, `get_canvas_status`, `repair_canvas_media`, `list_canvas_history`, `release_canvas`, `get_release_delivery` | | Guides | `get_integration_guide` | | Feedback | `send_feedback` | `get_release_delivery` returns a release's playback files for the web, iPhone and iPad, Android or Masko for Mac; with `formats: av1`, apps get the lighter AV1 files the [Masko player](/docs/player) plays. For [talking animations](/docs/generate/talking), `make_talking_animation` first runs free with `dry_run` to show the script and cost. ## Good to know - **Credits.** Generation spends the connected workspace's Masko credits, not your AI app's subscription. Reading, estimates and job checks are free. See [credits and costs](/docs/credits). - **Your own image.** The server runs on masko.ai and cannot read files on your computer. Give it an HTTPS image URL, upload the image on masko.ai first, or let your agent upload it from the terminal with `masko studio POST upload --file image.png` ([terminal quickstart](/docs/terminal)) or [`POST /v1/upload`](/docs/create/from-image). - **Hosting.** New mascots made through the MCP server start with public hosting off, and their file links expire. Ask the agent to publish the assets before you use their links in a public app. - **A stopped run.** Ask the agent to check its existing jobs before it generates again, so you never pay twice. - **Limits.** 120 requests per minute per connection. - **Feedback.** Your agent can tell the Masko team what is confusing, broken or missing, or what works well, with `send_feedback`. It works on read-only connections too. See [Send feedback to Masko](/docs/ai-agents#send-feedback-to-masko). ## Pages you will use | Page | What you do there | | --- | --- | | [Developers → MCP](/api-keys?tab=mcp) | Copy the setup for your app, and disconnect the apps you signed in with | | [Developers → API keys](/api-keys) | Create or revoke the key for Claude Code, Codex, VS Code and other apps | | [Billing](/billing) | Buy credits | Disconnecting an app or revoking a key stops its future requests. It does not delete your mascots or cancel a generation that already started. ## Without MCP Any coding agent that runs commands can use the REST API with `MASKO_API_KEY` and the docs at [masko.ai/llms.txt](https://masko.ai/llms.txt): see [Build with your AI agent](/docs/ai-agents#set-up-once). To work from the terminal yourself, see the [terminal quickstart](/docs/terminal). --- ---url: /docs/ai-tools/cursor--- Source: https://masko.ai/docs/ai-tools/cursor # Masko for Cursor Create a mascot, animate your own artwork and add the finished assets to your codebase from Cursor. ## Connect your account The plugin has four workflow skills and a connection to Masko's hosted MCP server. Install the skills in your project: ```bash npx skills add masko-ai/masko-plugins --agent cursor --skill '*' --yes ``` Add `--global` for all your Cursor projects, or replace `'*'` with `create-mascot`, `animate-mascot`, `build-interactive-mascot` or `integrate-mascot`. Skills alone do not connect your account: enable Masko in Cursor's **Customize** page, connect the MCP server, sign in on masko.ai, choose a personal or team workspace and approve. No API key is needed. To configure only the MCP connection, add this server to Cursor's MCP settings: ```json { "mcpServers": { "masko": { "url": "https://masko.ai/api/mcp", "auth": { "CLIENT_ID": "https://masko.ai/oauth/clients/cursor", "scopes": ["masko:read", "masko:write"] } } } } ``` The client ID is public configuration (a JSON client metadata document), not a credential. It is registered for Cursor only; other apps connect as shown in [MCP server](/docs/mcp). Team connections require an owner or admin, and you can disconnect in **Settings → Connected apps**. ## Start with your existing work Try: **“Show my Masko projects, mascots and credit balance. Don't generate anything.”** Then choose a workflow: | Skill | Example prompt | | --- | --- | | Create a mascot | “Create a friendly fox for my fitness app. Show me the cost first.” | | Animate a mascot | “Animate my existing mascot waving hello.” | | Build interactions | “Make it celebrate a completed lesson, then return to idle.” | | Integrate assets | “Add these animations to this React project without flickering between clips.” | Generation spends the connected workspace's Masko credits, which Cursor's subscription does not include, so review the cost first. The plugin can return a Stripe checkout link to buy more; team purchases require the team owner. ## Use your own character Reuse a saved Masko image, attach a downloadable file if the host supports it, or give an HTTPS image URL (PNG, JPEG, WebP or GIF up to 10 MB). The hosted server cannot fetch private network addresses or local paths, and a Cursor chat attachment may not give Masko a download URL. For a local-only image, upload it on masko.ai first, then ask Cursor to find the saved asset. ## Put the result into your product The agent follows each job until the assets are ready. For a canvas release, the integration skill installs the [Masko player](/docs/player) for your platform and connects your app's events to it with `set` and `fire`; a single looping clip plays in the same player from its hosted files ([play one animation](/docs/player/one-animation)). New mascots are private and preview URLs expire, so a public website needs public asset hosting or private delivery through your backend. The plugin does not deploy your app or put a mascot on your Mac. ## Support If tools are missing, check that the Masko MCP server is enabled and authorized, and reconnect through OAuth instead of pasting credentials into chat. Email **paul@masko.ai** with the request or job ID if an operation fails. [Privacy policy](/privacy) · [Terms of service](/terms) --- ---url: /docs/ai-tools/chatgpt--- Source: https://masko.ai/docs/ai-tools/chatgpt # Masko in ChatGPT The Masko plugin lets ChatGPT create mascots, animate a character you upload, build interactive canvases and integrate the assets into your app. It is in developer preview; availability depends on deployment and your ChatGPT workspace settings. ## Connect your account Choose Connect in ChatGPT, sign in on Masko, select your personal or team workspace, and approve; team authorization requires an owner or admin. Masko issues OAuth tokens, so you never paste an API key and ChatGPT never sees your password. The connection stays tied to that workspace, even if you switch workspaces on the website; reconnect to choose another. Generation spends its Masko credits, separate from your ChatGPT subscription, so ask for the estimated cost first. ChatGPT cannot buy credits. When the workspace runs out, it tells you what the job needs and what is left; add credits to that workspace in [Billing](/billing) on masko.ai. Only the team owner manages team billing. To add it yourself, create a custom MCP server at [chatgpt.com/plugins](https://chatgpt.com/plugins) with the URL `https://masko.ai/api/mcp` and OAuth; the [step-by-step setup](/docs/mcp#chatgpt) lists each button. ChatGPT's published metadata identifies the client, so no client secret is needed. The same server works in Claude Code, Codex, Cursor, Claude and VS Code: see [MCP server](/docs/mcp). ## Try a workflow - “Create a friendly mascot for my reading app.” - “Animate this uploaded mascot waving hello.” - “Make my mascot idle, celebrate when a lesson finishes, then return to idle.” - “Help me integrate this animation into my React website.” Uploads accept PNG, JPEG, WebP or GIF up to 10 MB and are imported into your Masko workspace. Generation returns a job first, and the plugin checks it until the asset is ready; a pending job never needs a new generation. ## Interactive canvases The plugin can create and edit a [canvas](/docs/canvas), validate it, estimate cost, generate missing media within an approved credit ceiling, and create an immutable playable release. A release does not publish a Marketplace listing or activate a mascot on your computer. App events must map to inputs the target runtime supports; see [build a canvas](/docs/canvas/build). ## Platform-specific integration The plugin installs the [Masko player](/docs/player) for your platform. A single looping clip plays from its hosted files ([play one animation](/docs/player/one-animation)); for a canvas release, the plugin connects your app's events to the player with `set` and `fire` ([play a canvas](/docs/player/canvas)). New mascots from the plugin start with public CDN hosting off, and the plugin explains what becomes public before publishing. Private preview and release URLs expire, so a production app needs your backend to fetch fresh delivery. Standalone uploads belong to your account and can be reused when creating a mascot in any selected workspace. ## Manage access Disconnect under Connected apps in [Developers → MCP](/api-keys?tab=mcp). This stops future requests from that connection but does not delete mascots or cancel accepted generation. API keys and signed-in computers are managed separately. ## Support Email [paul@masko.ai](mailto:paul@masko.ai) with the job or request ID, never a password, API key, authorization code or token. For an interrupted generation, check your existing jobs before requesting another paid output. --- ---url: /docs/tutorials/first-mascot--- Source: https://masko.ai/docs/tutorials/first-mascot # Create your first mascot One sentence becomes your mascot's first **reference image**: the picture Masko reads before every new pose and animation, so the character keeps the same look. **5 min · 1 credit · Studio or API** ## Do it in Studio 1. Open [Create a mascot](/mascots/new) on the **Create with AI** tab. 2. Write one sentence in **Describe your mascot**, such as "A friendly orange fox with a blue scarf, for a cooking app". Stuck? Press **Give me ideas**. 3. Optional: pick a **Style** card and a few **Design touches**. 4. Press **Generate · 1 credit**. Each try appears under **Your tries**, and **Generate another** draws one more for 1 credit. 5. Click the try you like. To fix a detail, write it in **Change something** and press **Refine · 1 credit**. 6. Fill in **Name your mascot** and press **Create mascot**. Already have a character, a logo or a drawing? Use the **Upload yours** tab, or follow [Animate the mascot you already have](/docs/tutorials/animate-your-mascot). ## Do it with the API Create a key with write access in [API keys](/api-keys). Run these calls on your server or in a terminal, never in browser code. ```bash export MASKO_API_KEY=masko_YOUR_KEY ``` ### 1. Create a project A project groups your mascots. It is free. POST /v1/projects ```bash curl -X POST https://api.masko.ai/v1/projects \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "My App" }' ``` ```javascript const res = await fetch('https://api.masko.ai/v1/projects', { method: 'POST', headers: { Authorization: `Bearer ${process.env.MASKO_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'My App' }) }); const body = await res.json(); if (!res.ok) throw new Error(body.error?.message || `HTTP ${res.status}`); const projectId = body.data.id; ``` Save `data.id` as `PROJECT_ID`. ### 2. Create the mascot from a sentence Send a `prompt` with no reference images, and Masko draws the first reference for 1 credit, refunded if drawing or saving fails. A retry with the same `Idempotency-Key` and body is never charged twice. POST /v1/mascots ```bash curl -X POST https://api.masko.ai/v1/mascots \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: first-mascot-001" \ -d '{ "name": "Felix the Fox", "project_id": "PROJECT_ID", "prompt": "A friendly orange fox with a blue scarf, for a cooking app" }' ``` ```javascript const res = await fetch('https://api.masko.ai/v1/mascots', { method: 'POST', headers: { Authorization: `Bearer ${process.env.MASKO_API_KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': 'first-mascot-001' }, body: JSON.stringify({ name: 'Felix the Fox', project_id: projectId, prompt: 'A friendly orange fox with a blue scarf, for a cooking app' }) }); const body = await res.json(); if (!res.ok) throw new Error(body.error?.message || `HTTP ${res.status}`); const mascot = body.data; console.log(mascot.id, mascot.reference_asset_ids); ``` ```json { "data": { "id": "MASCOT_ID", "name": "Felix the Fox", "slug": "felix-the-fox-a1b2c3d4", "type": "mascot", "reference_asset_ids": ["REFERENCE_ASSET_ID"], "settings": { "cdn_enabled": true, "animation_sizes": [] } } } ``` Save `data.id` as `MASCOT_ID`. Optional fields: `style` takes a preset ID from `GET /v1/styles`, and `design_touches` takes ideas such as `["proportions", "eyes-big", "mood-cozy"]` (see [create from text](/docs/create/from-text)). ### 3. Look at the reference The asset's `file_url` is a signed link that expires, so keep the asset ID and read it again for a fresh link. ```bash curl https://api.masko.ai/v1/assets/REFERENCE_ASSET_ID \ -H "Authorization: Bearer $MASKO_API_KEY" ``` ### Optional: compare drafts before you choose Preview several takes at 1 credit each, then create the mascot from the one you like for no extra credits. Preview links expire after an hour (`expires_in: 3600`). POST /v1/generate/preview ```bash curl -X POST https://api.masko.ai/v1/generate/preview \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "A friendly orange fox with a blue scarf, for a cooking app", "count": 4 }' ``` Then send the chosen `url` to `POST /v1/mascots` as `"reference_image_urls": ["URL_FROM_PREVIEW"]`, with `name`, `project_id` and the same `prompt`. ## Is there a job to wait for? No. Creating a mascot answers once the reference is drawn, so the call takes a little while, but there is nothing to poll. Poses and animations return a `job_id`; [the next tutorial](/docs/tutorials/first-animation) shows how to wait for one. ## What you get - **A mascot** with an `id`, a `name` and a `slug`. The slug becomes part of its hosted links, such as `https://assets.masko.ai/{prefix}/felix-the-fox/...`. - **One reference image**, a PNG. A mascot holds up to 6 references, and more angles give steadier results. See [reference images](/docs/manage/references). - **Hosting on by default** for mascots created with the API (`settings.cdn_enabled: true`), so your next files get permanent links. Asset hosting is billed separately from credits. ## Using an AI agent? Paste this into an agent connected to the Masko MCP server ([setup for Claude Code, Codex, Cursor and more](/docs/mcp)), or into one with the [Masko skill](/docs/ai-tools/skills) and an API key. ```text Create my first Masko mascot by following https://masko.ai/docs/tutorials/first-mascot My product: . Use the Masko MCP tools if they are connected. Otherwise use the REST API at https://api.masko.ai/v1 with MASKO_API_KEY from my environment. Never print the key or put it in browser code. Before any paid call, tell me the credits and wait for my OK. It should be 1 credit. When it is done, show me the reference image and give me the mascot ID. ``` ## Next - [Create an animated character](/docs/tutorials/first-animation) - Next tutorial: your mascot's first transparent animation, for 11 credits. - [Create from text](/docs/create/from-text) - Styles, previews and design touches. - [Create from an image](/docs/create/from-image) - Start from a character you already drew. - [Credits](/docs/credits) - What each image, animation and talking second costs. --- ---url: /docs/tutorials/first-animation--- Source: https://masko.ai/docs/tutorials/first-animation # How to create an animated character An animated character is one character drawn the same way every time, plus short videos of it moving. Masko makes both: you describe the character or upload a picture of it, then Masko draws a new pose and animates it into a five-second loop with a transparent background. **15 min · 12 credits · Studio or API** ## Make the character Open [Create a mascot](/mascots/new), describe the character in one sentence, such as "A friendly orange fox with a blue scarf", and press **Generate · 1 credit**. Already have a character, a logo or a drawing? Use the **Upload yours** tab instead. [Create your first mascot](/docs/tutorials/first-mascot) covers every option and the API call. If you made one there, skip to the next step. ## Animate it in Studio 1. Open your mascot in [Studio](/mascots), go to the **Animations** tab, press **Create Animation** and choose **New image + animation**. 2. Fill in **Name** ("Waving hello"), **Starting image** ("standing, smiling, one paw raised") and **Movement** ("waves hello, then lowers the paw back to the start"). Or type the name and press **Fill prompts**. 3. Keep **Loop** on, and under **Quality** keep **Standard** and a **Duration** of 5s. 4. Press **Generate · 11 credits**. When it is ready, open it in the **Animations** tab for **Available Formats**, **Export** and **Get Links**. Already have a pose you like? Press **Animate** on its image card: no new drawing, 10 credits for five seconds. ## Animate it with the API You need a write key from [API keys](/api-keys) in `MASKO_API_KEY` and your `MASCOT_ID`. Run these calls on your server or in a terminal, never in browser code. ### 1. Ask for the animation One request draws the pose from `image_prompt` and animates it with `animation_prompt`, and `loop: true` makes a clip that repeats without a seam. Always set `animation_model` to `standard`: leaving it out keeps an older, pricier rate of 5 credits a second. POST /v1/mascots/:id/generate ```bash curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/generate \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "animation", "animation_model": "standard", "name": "Waving hello", "image_prompt": "standing, smiling, one paw raised", "animation_prompt": "waves hello, then lowers the paw back to the start", "duration": 5, "loop": true }' ``` ```javascript const res = await fetch( `https://api.masko.ai/v1/mascots/${mascotId}/generate`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.MASKO_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ type: 'animation', animation_model: 'standard', name: 'Waving hello', image_prompt: 'standing, smiling, one paw raised', animation_prompt: 'waves hello, then lowers the paw back to the start', duration: 5, loop: true }) } ); const body = await res.json(); if (!res.ok) throw new Error(body.error?.message || `HTTP ${res.status}`); const jobId = body.data.job_id; // Save the job ID. Do not send the request again to check progress. ``` ```json { "data": { "job_id": "JOB_ID", "status": "pending", "type": "animation", "animation_model": "standard", "item_id": "ITEM_ID", "item_name": "Waving hello", "estimated_cost": 11, "asset_ids": { "image": "IMAGE_ASSET_ID", "transparent_image": "TRANSPARENT_IMAGE_ASSET_ID", "video": "VIDEO_ASSET_ID", "webm": "WEBM_ASSET_ID", "hevc": "HEVC_ASSET_ID" }, "urls": { "video": "https://assets.masko.ai/.../waving-hello-e5f6.mp4", "webm": "https://assets.masko.ai/.../waving-hello-g7h8.webm", "hevc": "https://assets.masko.ai/.../waving-hello-i9j0.mov" }, "poll_url": "/api/v1/jobs/JOB_ID" } } ``` It answers `202 Accepted` at once, before the work is done. Save `job_id` and `item_id`. | `animation_model` | Five seconds with a new pose | | ----------------- | ---------------------------------------- | | `standard` | 11 credits: 1 for the pose, 2 per second | | `premium` | 31 credits, higher quality | | left out | Older rate of 5 credits per second | End the movement where it started, such as "lowers the paw back to the start", so the clip loops without a jump. ### 2. Wait for the job A job goes `pending`, `processing`, then `completed` or `failed`. With `wait=true`, the server holds the request until the job finishes or the timeout passes; then ask again. GET /v1/jobs/:id?wait=true&timeout=120 ```bash # Holds the request open until the job finishes, up to 120 seconds. # If the timeout comes first, you get the current status: ask again. curl "https://api.masko.ai/v1/jobs/JOB_ID?wait=true&timeout=120" \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Masko-API-Version: 2026-09-26" ``` ```javascript async function waitForJob(jobId, timeoutMs = 10 * 60 * 1000) { const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { const res = await fetch( `https://api.masko.ai/v1/jobs/${jobId}?wait=true&timeout=30`, { headers: { Authorization: `Bearer ${process.env.MASKO_API_KEY}`, 'Masko-API-Version': '2026-09-26' }, signal: AbortSignal.timeout(Math.min(35000, deadline - Date.now())) } ); const body = await res.json(); if (!res.ok) throw new Error(body.error?.message || `HTTP ${res.status}`); const job = body.data; if (job.status === 'completed') return job; if (job.status === 'failed') throw new Error(JSON.stringify(job.error)); } throw new Error('Stopped waiting. Keep the job ID and check again later.'); } const job = await waitForJob(jobId); console.log(job.urls.webm, job.urls.hevc); ``` ```bash # Register once. Masko then POSTs each finished job to your URL. curl -X POST https://api.masko.ai/v1/webhooks \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-app.com/api/masko-webhook", "events": ["job.completed", "job.failed"] }' # Save the "secret" from the response: it is shown only once. # Every event carries "job_id". Read the job for its files if the body # does not list them. ``` In production, use a [webhook](/docs/manage/webhooks): your server gets a signed POST when each job finishes. Check its `Masko-Signature` header on every call. The URLs in the first response are reserved in advance and serve a placeholder until the job completes. Wait for `completed` before you show them. ## What you get One **item**, "Waving hello", holds these files: | Asset | File | Use | | ------------------- | ---------------------------- | ------------------------------------ | | `image` | PNG | The pose, with its background | | `transparent_image` | PNG | The pose, background removed | | `webm` | WebM, VP9 with alpha | Transparent in Chrome, Firefox, Edge | | `hevc` | MOV, HEVC (H.265) with alpha | Transparent in Safari, iOS, macOS | | `video` | MP4, H.264 | Plays everywhere, solid background | | `stacked_video` | MP4, color and alpha stacked | Transparent on Android, in the Masko player | A page lists both transparent files and each browser plays the one it can; [Put it on your website](/docs/tutorials/mascot-on-your-website) has the markup. Other formats are free: `POST /v1/assets/:id/exports`, or **Export** in Studio, makes GIF, Lottie, dotLottie, ProRes 4444 with alpha, PNG sequences and resized versions. See [image and video exports](/docs/manage/media-exports). ## Using an AI agent? Paste this into an agent connected to the Masko MCP server ([setup for Claude Code, Codex, Cursor and more](/docs/mcp)), or into one with the [Masko skill](/docs/ai-tools/skills) and an API key. ```text Animate my Masko mascot by following https://masko.ai/docs/tutorials/first-animation Use my mascot "MASCOT_NAME". Ask me which one if I have several. Make a five-second Standard looping animation called "Waving hello": a new pose, then a wave that ends where it started. Use the Masko MCP tools if they are connected. Otherwise use the REST API at https://api.masko.ai/v1 with MASKO_API_KEY from my environment. Never print the key. Tell me the cost first and wait for my OK. It should be 11 credits. Wait for the job without starting it again, then give me the WebM and HEVC URLs. ``` ## Next - [Make a character talk](/docs/tutorials/make-it-talk) - Next tutorial: a voice and a talking animation. - [Animations](/docs/generate/animations) - Transitions, Premium, durations and size variants. - [Jobs and polling](/docs/manage/jobs) - Every job field and filter. - [Webhooks](/docs/manage/webhooks) - Signatures, retries and delivery. --- ---url: /docs/tutorials/make-it-talk--- Source: https://masko.ai/docs/tutorials/make-it-talk # How to lip sync an animation Give your mascot a voice, write a line with its movements in brackets, and get a transparent talking video with the sound inside. You never draw a mouth shape: Masko makes the lip sync animation from the voice. ## How the lip sync works Masko records the line first, then animates your character to that voice. The mouth moves while it speaks and rests in the pauses; it follows the speech and the silences, not each syllable. At the same time the body acts out the moves you wrote in brackets, such as `[waves hello, excited]`. **15 min · 1 credit for voices, 2 credits per second of talk · Studio or API** **Marketplace mascots speak with their creator's voice**, so they skip the voice step. ## Do it in Studio 1. Open your mascot in [Studio](/mascots), then **Context & References**. In the **Voice** row, press **Add a voice**. 2. Describe the voice (age, energy, pitch, accent) in the field named after your mascot, such as **Describe Felix's voice**. Press **Hear 3 voices · 1 credit**, then **Keep** on the one you like. 3. In the **Animations** tab, press **Create Animation**, then **Talking animation**. 4. Write the line in the **What Felix says** field, with movements in brackets where they start: `[waves hello, excited] Hi! I'm Felix.` **Add movements** writes them for you. 5. Pick the pose it **Starts from** and, optionally, the one it **Ends on**. 6. Check **Estimated cost** and press **Generate · up to N credits**. Unused seconds come back once the voice is recorded. ## Do it with the API You need a write key from [API keys](/api-keys) in `MASKO_API_KEY`, your `MASCOT_ID` and an `ITEM_ID` to start from, such as "Waving hello" from [Create an animated character](/docs/tutorials/first-animation). ### 1. Get voice ideas Free voice descriptions written from your mascot's references; `refresh=true` gives others. GET /v1/mascots/:id/voice/suggestions ```bash curl https://api.masko.ai/v1/mascots/MASCOT_ID/voice/suggestions \ -H "Authorization: Bearer $MASKO_API_KEY" ``` ### 2. Hear three samples POST /v1/mascots/:id/voice/samples Three voices from one `description`, each reading the same optional `sample_line`. Samples expire after 24 hours (`expires_at`). ```bash curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/voice/samples \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "description": "A cheerful young fox. Young male voice, warm and bouncy, quick and friendly. American accent." }' ``` ```javascript const res = await fetch( `https://api.masko.ai/v1/mascots/${mascotId}/voice/samples`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.MASKO_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ description: 'A cheerful young fox. Young male voice, warm and bouncy, quick and friendly. American accent.' }) } ); const body = await res.json(); if (!res.ok) throw new Error(body.error?.message || `HTTP ${res.status}`); for (const sample of body.data.samples) console.log(sample.id, sample.url); ``` Listen to each `url` and note the `id` you like. ### 3. Keep one PUT /v1/mascots/:id/voice ```bash curl -X PUT https://api.masko.ai/v1/mascots/MASCOT_ID/voice \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "sample_id": "SAMPLE_ID" }' ``` It replaces the mascot's voice, and variants without their own voice use it too. `GET /v1/mascots/:id/voice` shows it: `source` is `original`, or `creator` for a Marketplace mascot. ### 4. Write the line Bracketed words are never spoken, and a feeling after a comma steers the voice. ```text [waves hello, excited] Hi! I'm Felix. [points to the right] Your recipes are right here! ``` Or send only the words as `speech.text`, with an optional `speech.direction`, and Masko writes the movements. Set `speech.language` (such as `fr`), or leave it out to detect it. ### 5. Get the free estimate `dry_run: true` returns the length and price without charging. In shell, `'\''` writes an apostrophe inside single quotes. ```bash curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/generate \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "animation", "item_id": "ITEM_ID", "speech": { "script": "[waves hello, excited] Hi! I'\''m Felix. [points to the right] Your recipes are right here!" }, "dry_run": true }' ``` ```json { "data": { "dry_run": true, "script": "[waves hello, excited] Hi! I'm Felix. [points to the right] Your recipes are right here!", "estimate": { "speech_seconds": 4.3, "duration": 6, "max_duration": 6, "credits": 12, "too_long": false } } } ``` ### 6. Generate it POST /v1/mascots/:id/generate The same request without `dry_run`, plus an `Idempotency-Key` so a retry is never charged twice. ```bash curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/generate \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: felix-hello-001" \ -d '{ "type": "animation", "item_id": "ITEM_ID", "speech": { "script": "[waves hello, excited] Hi! I'\''m Felix. [points to the right] Your recipes are right here!" } }' ``` ```javascript const res = await fetch( `https://api.masko.ai/v1/mascots/${mascotId}/generate`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.MASKO_API_KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': 'felix-hello-001' }, body: JSON.stringify({ type: 'animation', item_id: itemId, speech: { script: "[waves hello, excited] Hi! I'm Felix. [points to the right] Your recipes are right here!" } }) } ); const body = await res.json(); if (!res.ok) throw new Error(body.error?.message || `HTTP ${res.status}`); const { job_id, estimated_cost, asset_ids } = body.data; ``` You pay for `estimate.max_duration`, minus the unused seconds once the voice is recorded; the job's `cost_credits` shows the final price. A mascot without a voice returns `409` with `details.reason: "voice_required"`, before any charge. ## Wait for the job Long-poll until `completed` or `failed`, or use a [webhook](/docs/manage/webhooks). ```bash curl "https://api.masko.ai/v1/jobs/JOB_ID?wait=true&timeout=120" \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Masko-API-Version: 2026-09-26" ``` ```javascript // waitForJob is the helper from the "Make it move" tutorial. const job = await waitForJob(job_id); console.log(job.cost_credits, job.urls); ``` Never resend the generate request to check progress: without the same `Idempotency-Key`, it charges a second talk. ## What you get | Asset | What it is | | ----------------- | ------------------------------------------------------------- | | Talking animation | MP4, transparent WebM and transparent HEVC `.mov`, with sound | | `audio` | The voice alone, MP3 | | `transcript` | JSON timing of every word, line and movement, in seconds | Read the transcript with `GET /v1/assets/{id}`, then its `file_url`. Each line ends at `.`, `!` or `?`, ready for captions. ```json { "text": "Hi! I'm Felix. Your recipes are right here!", "language": "en", "duration": 6, "words": [{ "text": "Hi!", "start": 0.26, "end": 0.6 }], "lines": [{ "text": "Hi! I'm Felix.", "start": 0.26, "end": 1.5 }], "movements": [{ "text": "waves hello, excited", "start": 0.26, "end": 2.04 }] } ``` Browsers block autoplay with sound: start the clip `muted` and unmute it on a tap. **Limits.** A talk lasts 5 seconds to 5 minutes, following the speech, and uses the Standard model. It cannot go through `generate-batch` or be reversed or edited: send the script again for a new take. [Long talks](/docs/generate/talking#limits) are cut at pauses into takes. ## Using an AI agent? Voice and talking run through the REST API, so use an agent with the [Masko skill](/docs/ai-tools/skills) and `MASKO_API_KEY`. ```text Make my Masko mascot talk by following https://masko.ai/docs/tutorials/make-it-talk Use the REST API at https://api.masko.ai/v1 with MASKO_API_KEY from my environment. Never print the key or put it in browser code. Use my mascot "MASCOT_NAME". If it has no voice, suggest voices, get three samples (1 credit) and let me pick one before you keep it. The line: "". Write the movements in brackets. Run a dry_run first, tell me the credits and wait for my OK. Wait for the job without sending the request again, then give me the WebM, HEVC and transcript links. ``` ## Next - [Put it on your website](/docs/tutorials/mascot-on-your-website) - Next tutorial: play your mascot on any page, for no credits. - [Talking animations](/docs/generate/talking) - Variants, long talks, the transcript and every limit. --- ---url: /docs/tutorials/mascot-on-your-website--- Source: https://masko.ai/docs/tutorials/mascot-on-your-website # Put your mascot on your website Play a transparent animation on any page with the Masko player and two hosted files. You need a finished animation from [Create an animated character](/docs/tutorials/first-animation). **5 min · No credits · HTML or React** ## Get the links in Studio 1. Open your mascot in [Studio](/mascots) and press **Get Links**. 2. Turn on **Enable asset hosting** (the button then reads **Links**). Made animations before that? Press **Update links** to publish them. 3. Under **Export JSON**, press **Copy** or **Download JSON**. The platform filter (**Web**, **Mobile**, **Safari / iOS**, **Android** and more) narrows the list, and the line under it gives what the files weigh. 4. Keep two links per animation: `transparent_video_mov` (HEVC) and `transparent_video_webm` (WebM). ## Get the links with the API Mascots created with the API have hosting on, so a completed animation job already carries `urls.hevc` and `urls.webm`. For every hosted link of a mascot at once, read its free export. GET /v1/mascots/:id/cdn-export ```bash curl https://api.masko.ai/v1/mascots/MASCOT_ID/cdn-export \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Masko-API-Version: 2026-09-26" ``` ```javascript // Run on your server. Never ship your API key to a browser. const res = await fetch( `https://api.masko.ai/v1/mascots/${mascotId}/cdn-export`, { headers: { Authorization: `Bearer ${process.env.MASKO_API_KEY}`, 'Masko-API-Version': '2026-09-26' } } ); const body = await res.json(); if (!res.ok) throw new Error(body.error?.message || `HTTP ${res.status}`); const wave = body.items.find((item) => item.name === 'Waving hello'); const { transparent_video_mov, transparent_video_webm } = wave.animations[0]; ``` ```json { "mascot": "Felix the Fox", "items": [ { "name": "Waving hello", "image": "https://assets.masko.ai/.../waving-hello-a1b2.png", "transparent_image": "https://assets.masko.ai/.../waving-hello-c3d4.png", "animations": [ { "video": "https://assets.masko.ai/.../waving-hello-e5f6.mp4", "transparent_video_webm": "https://assets.masko.ai/.../waving-hello-g7h8.webm", "transparent_video_mov": "https://assets.masko.ai/.../waving-hello-i9j0.mov", "transparent_video_android": "https://assets.masko.ai/.../waving-hello-k1l2.mp4" } ] } ] } ``` The export lists only ready, hosted files. If hosting is off or nothing is hosted yet, it returns `409` with the code `cdn_export_not_ready`. A mascot made in Studio starts with hosting off: turn it on in **Get Links**, or with `POST /v1/mascots/MASCOT_ID/publish` ([turn on hosting](/docs/generate/cdn#turn-on-hosting)), then ask again. Hosting makes the files public, so ask the mascot's owner first. A generating animation's URLs serve a placeholder until the job is `completed`. [Wait for the job](/docs/tutorials/first-animation#2-wait-for-the-job) first. ## Add it to your page Give the [Masko player](/docs/player/one-animation) your two URLs. ```html
``` ```tsx // npm install @masko/player import { MaskoPlayer } from '@masko/player/react'; export function Mascot({ webm, hevc, size = 240 }: { webm: string; hevc: string; size?: number }) { return ; } // ``` - **The right file everywhere.** The player plays the HEVC `.mov` on Safari, on iPhone and iPad and in Mac apps' web views, and the WebM elsewhere, so the mascot stays transparent, also in Chrome setups that decode HEVC but draw a box around it. - **It loops without a flash**, plays the animation's own voice if it has one (browsers allow sound after the person's first click or key press; `sound: false` mutes it), shows a play button when the browser blocks autoplay and pauses while the tab is hidden. To switch between animations, or make the mascot react to your app, put its animations in a [canvas](/docs/canvas) and play the release: [Play a canvas](/docs/player/canvas). ## Make the files lighter For a mascot shown at 240 px, a 360 px version loads much faster than full size: about 0.5 MB instead of 0.9 MB for a 5-second WebM ([file sizes](/docs/generate/file-sizes)). Size variants are free, in WebM, HEVC and Android formats. - **In Studio**: **Get Links**, **Settings** tab, turn on **360 px** under **Sizes**, press **Sync**. - **With the API**: turn the sizes on once, then fill in your existing animations. ```bash curl -X PATCH https://api.masko.ai/v1/mascots/MASCOT_ID/settings \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "publish_params": { "animation_sizes": { "enabled": true, "sizes": [360] } } }' curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/size-variants \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` The export then also lists `transparent_video_mov_360` and `transparent_video_webm_360`, used like the full-size pair. For visitors who turn off animations, pass `autoPlay: !matchMedia('(prefers-reduced-motion: reduce)').matches`: the player then shows the first frame and waits. ## What you get - **Permanent links** on `https://assets.masko.ai`, not signed links that expire. - **Every format** in one JSON: `video` (MP4 on a solid background), `transparent_video_webm`, `transparent_video_mov` and `transparent_video_android` (color and alpha stacked in one MP4), plus pose images, stickers and cursor followers. - **No credits** for links and size variants. Asset hosting is billed separately; see [Billing](/billing). ## Using an AI agent? Paste this into your coding agent, in your website's project, connected to the Masko MCP server ([setup for Claude Code, Codex, Cursor and more](/docs/mcp)) or with the [Masko skill](/docs/ai-tools/skills) and an API key. ```text Put my Masko mascot on this website by following https://masko.ai/docs/tutorials/mascot-on-your-website Use my mascot "MASCOT_NAME" and its animation "ANIMATION_NAME". Use the Masko MCP tools if they are connected. Otherwise use the REST API at https://api.masko.ai/v1 with MASKO_API_KEY from my environment. Never put the key in browser code. If hosting is off, ask me before you turn it on. Play the hosted .webm and .mov (HEVC) links with the Masko player's clip option (@masko/player 0.1.9 or later). Prefer the 360 px versions if they exist. Show me the page where the mascot plays when you are done. ``` ## Next - [Show a mascot while your AI thinks](/docs/tutorials/ai-loading-mascot) - Next tutorial: three clips switched by your app, without a flicker. - [Cursor follower](/docs/generate/cursor-follower) - Nine gaze directions so your mascot looks at the pointer, for 9 credits. - [State machine](/docs/canvas/build) - Poses and transitions that react to clicks and app events. - [Masko player](/docs/player) - The player for the web, iPhone, Mac, React Native and Android. --- ---url: /docs/tutorials/web-onboarding-mascot--- Source: https://masko.ai/docs/tutorials/web-onboarding-mascot # Guide onboarding with a talking mascot Build Fold’s three-step setup with Nori, a paper manta ray. The visitor names a workspace, creates a project, and sees the finished result. Nori speaks one short, captioned line at each stage. [Reference prompts and Masko v1 recipe](/docs/use-cases/web-onboarding) · [Download the full React examples](/examples/website-mascot-demos.zip) ## What you will build A responsive web interface with a new brand character, working interactions and transparent video playback. This tutorial uses **Fold**, a fictional product. The demo is free to try; generating your own assets spends your Masko credits. **What you need:** a React app, a Masko account, and a mascot’s published files or local downloads. Use Studio for generation, or keep a write API key on your server. Never ship the key in browser code. ## Create Nori for your brand ![Nori: approved character reference and animation recipe](/website-use-cases/nori-recipe.svg) Copy this reference prompt: ```text A friendly origami manta ray made of periwinkle and ivory folded paper, large dark eyes, a small apricot badge, soft tactile paper, calm smile, full character centered, no text or extra objects. ``` A little paper manta ray with a calm voice and an apricot badge. Nori makes a blank workspace feel less intimidating. Create the reference in [Studio](/mascots/new), or follow [Create a mascot from text](/docs/create/from-text). Keep the approved image as the mascot’s reference so future poses share its identity. The downloaded examples include the completed demonstration media; replace the files when you make your own character. ## Map the interaction states ![Nori: website states and clips](/website-use-cases/nori-flow.svg) Keep the form step separate from the clip. Failed validation or a failed save leaves the visitor on the current screen. In this demo, a talking clip plays once and holds its last frame until the next step; **Hear Nori** replays the current clip with sound. The idle loop is available in the source for a quieter resting state. These are website interaction states. Masko v1 creates the media; your app chooses which clip to show. [Copy the v1 requests](/docs/use-cases/web-onboarding#1-create-the-reference) for character creation, generation and file delivery. ## Make the animation set | Clip or moment | Performance or spoken line | Trigger | | -------------- | ------------------------------------------------------------------------- | ----------------------- | | Welcome | “Hi, I’m Nori. Let’s make a little space for your next big idea.” | Enter the first screen | | Guide | “Give your first project a name. One small step, and you’re ready to go.” | Workspace name accepted | | Done | “Your first project is ready. You’ve got this!” | First project created | Create a five-second idle loop from your approved image. Keep the camera still and the full character inside the frame. Then make the three talking clips below. ```text Gentle breathing, one friendly blink and a tiny paper-wing sway. Keep the full character centered and the camera still. End in the starting pose for a seamless loop. ``` [Animate an existing image](/docs/generate/animations#animate-an-existing-image) to avoid paying for a new pose when you already have the right one. Inspect the results before putting them in your interface. ## Give Nori a voice and record the lines In Studio, open **Context & References → Voice → Add a voice**. Try this description: ```text A calm, clear adult voice. Warm, lightly playful, unhurried conversational English. ``` Hear the samples and keep one. Each voice sample set costs 1 credit. For each line in the recipe table, create a **Talking animation** from the welcome pose. Add movements in brackets, such as `[waves gently, warm]`, and request a cost estimate before generating. For an API or coding agent, use the [talking tutorial](/docs/tutorials/make-it-talk) for voice samples, voice selection and job polling. Once the mascot has a voice, this request estimates one clip: ```typescript // Run on your server. Keep this helper out of browser code. async function masko(path: string, body: object, operationId: string) { const response = await fetch(`https://api.masko.ai/v1${path}`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.MASKO_API_KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': operationId }, body: JSON.stringify(body) }); const result = await response.json(); if (!response.ok) throw new Error(result.error?.message ?? 'Request failed'); return result.data; } const request = { type: 'animation', item_id: welcomeItemId, speech: { script: '[waves gently, warm] Hi, I’m Nori. Let’s make a little space for your next big idea.' } }; const estimate = await masko( `/mascots/${mascotId}/generate`, { ...request, dry_run: true }, crypto.randomUUID() ); // Review estimate.estimate before sending the paid request. // Use a unique Idempotency-Key and the identical body on retries. ``` Generate your actual scripted lines from the table, not the example greeting above. Wait for the job and its WebM and HEVC files to complete before adding the result to the app. ## Get files that are ready to ship Download the transparent PNG, WebM and HEVC files into your project, or publish them to get stable CDN URLs. A completed MP4 alone is not proof that the transparent versions are ready: check the assets you intend to serve. Signed private URLs expire and should not be hardcoded into a shipped app. The source download uses `/website-use-cases/` for media. Copy that directory to your app’s `public/` folder, or change the URLs in `website-mascots.ts` to your published files. WebM serves Chrome, Firefox and Edge; HEVC with alpha serves Safari. ## Connect it to your web app Keep setup state in the app. Advance only after validation succeeds. In production, wait for the backend to create the workspace or project before changing the step; if it fails, keep the form open with a useful error. Put the clips in a [canvas](/docs/canvas/build) with one pose: its loop is the idle clip, and each line is an animation of that pose that starts on `action::` and the line's name. Release it, and your backend fetches its delivery ([Play a canvas](/docs/player/canvas)). The page then fires each line once, by name; the [Masko player](/docs/player) plays it when the loop on screen ends and goes back to idle. This small component shows the event wiring with local state. The source download includes the complete layout and production integration points. ```tsx import { useEffect, useRef, useState, type FormEvent } from 'react'; import { MaskoPlayer, type MaskoHandle } from '@masko/player/react'; const lines = ['welcome', 'guide', 'done']; const captions = [ 'Hi, I’m Nori. Let’s make a little space for your next big idea.', 'Give your first project a name. One small step, and you’re ready to go.', 'Your first project is ready. You’ve got this!' ]; // delivery: Nori's canvas release, fetched by your backend. export function SetupGuide({ delivery }) { const nori = useRef(null); const [step, setStep] = useState(0); const [workspace, setWorkspace] = useState(''); const [project, setProject] = useState(''); // Nori says the step's line once, when the loop on screen ends. useEffect(() => { nori.current?.fire(`action::${lines[step]}`); }, [step]); function submit(event: FormEvent) { event.preventDefault(); const name = step === 0 ? workspace : project; if (name.trim()) setStep((previous) => previous + 1); } return (

{captions[step]}

{step < 2 ? (
step === 0 ? setWorkspace(event.target.value) : setProject(event.target.value) } />
) : (

{workspace}: {project} is ready.

)}
); } ``` The downloaded demo contains the project form, back navigation, completion view and reset control. Put the spoken text next to the mascot, and keep the instruction beside the field. A screen reader can follow the flow without hearing the video. ## Use the full design [Download the React source and generated media](/examples/website-mascot-demos.zip). It contains all three independent designs. Choose **onboarding** for this tutorial: ```tsx import { WebsiteMascotDemo } from './WebsiteMascotDemo'; export default function Example() { return ; } ``` The archive includes the components, CSS module, media configuration and a README. Import the CSS module beside the components and keep your app’s existing React setup. The example plays each mascot with the Masko player, from a small canvas it builds from these clips in `website-mascot-delivery.ts`. In your app, play your own canvas release instead. Keep spoken lines visible as text. ## Check the finished experience Submit an empty or whitespace-only name: the step must stay put. Create a workspace, go back, and check that the name is preserved. Create the project and check that both names appear in the result. Replay each spoken line, mute it, then complete setup using only the keyboard. Test muted, with sound, with reduced motion, and with a keyboard. Check a narrow mobile viewport and both WebM and Safari’s HEVC playback. A failed media request should leave the still image or previous frame visible and offer a retry. ## Connect your real product Save setup to the signed-in account and restore the correct step after a reload. Keep essential instructions in text. Let returning users skip a guide they have already completed. ## Costs and what to measure Budget **1 credit for the reference image**, **10 for a five-second Standard idle loop from that image**, **1 for a voice sample set**, and **2 credits per second of talking**. Three talking clips have a five-second minimum each, so this recipe starts at about **42 credits**; the longer scripts in this example cost more. Use the talking estimate for your own lines. Exports and playback of downloaded or published files add no generation cost. The tutorial catalog shows estimates, not the cost of a visitor’s session. Compare setup completion, time to first project and next-day return rate with and without the guide. This demo illustrates the experience; it does not report a conversion lift. ## Ask Lovable to build your version ```text Build a project planning web app called Fold with a calm blue design. Use Masko to create Nori, a friendly paper manta ray. Add a three-step onboarding guide with captioned talking clips for welcome, the next setup step and completion. Let users name their workspace and create their first project. Keep sound optional and never cover the form. Follow https://masko.ai/docs/tutorials/web-onboarding-mascot. Use Masko v1 at https://api.masko.ai/v1 and inspect https://api.masko.ai/v1/openapi.json. Use real generated files, keep generation credentials on the server, and download or publish assets before shipping the app. ``` [Reference prompts and Masko v1 recipe](/docs/use-cases/web-onboarding) · [All website use cases](/docs/use-cases#website) --- ---url: /docs/tutorials/web-progress-companion--- Source: https://masko.ai/docs/tutorials/web-progress-companion # Add a mascot that celebrates progress and welcomes users back Build Little Wins with Pip, a lilac axolotl. Complete three habits, celebrate the daily milestone, then preview coming back for the next day. The demo saves its history in this browser. [Reference prompts and Masko v1 recipe](/docs/use-cases/web-progress-companion) · [Download the full React examples](/examples/website-mascot-demos.zip) ## What you will build A responsive web interface with a new brand character, working interactions and transparent video playback. This tutorial uses **Little Wins**, a fictional product. The demo is free to try; generating your own assets spends your Masko credits. **What you need:** a React app, a Masko account, and a mascot’s published files or local downloads. Use Studio for generation, or keep a write API key on your server. Never ship the key in browser code. ## Create Pip for your brand ![Pip: approved character reference and animation recipe](/website-use-cases/pip-recipe.svg) Copy this reference prompt: ```text A round lilac axolotl with coral gills, a mint scarf and a small yellow star badge, large dark eyes, short rounded limbs, soft clay-like 3D material, warm smile, full body centered, no text or extra props. ``` A round lilac axolotl with coral gills and a mint scarf. Pip cheers for effort, and never makes a missed day feel like a failure. Create the reference in [Studio](/mascots/new), or follow [Create a mascot from text](/docs/create/from-text). Keep the approved image as the mascot’s reference so future poses share its identity. The downloaded examples include the completed demonstration media; replace the files when you make your own character. ## Map the interaction states ![Pip: website states and clips](/website-use-cases/pip-flow.svg) Use just two media states: `idle` and `celebrate`. When the celebration ends, return to the same idle clip. On a later visit, reuse idle with a new written greeting. Save each habit once per calendar day, then replay the celebration for a new completion. Increment a daily milestone only after all three distinct habits are complete. Returning visits preserve the history and change the text. These are website interaction states. Masko v1 creates the media; your app chooses which clip to show. [Copy the v1 requests](/docs/use-cases/web-progress-companion#1-create-the-reference) for character creation, generation and file delivery. ## Make the animation set | Clip or moment | Performance or spoken line | Trigger | | -------------- | ------------------------------------------ | ------------------------ | | Idle | Gentle breathing and a friendly blink | Waiting for an activity | | Celebrate | Two small joyful hops, then settle | A new habit is completed | | Welcome back | Idle animation with a new written greeting | A later visit | Create a five-second idle loop from your approved image. Keep the camera still and the full character inside the frame. For Pip, also create the celebration as a one-shot animation that settles naturally at the end. ```text Idle: subtle breathing, a friendly blink and a tiny relaxed sway. Keep the character centered, preserve its design, and make a seamless loop. Celebration: two small joyful hops, arms raised, then settle with a proud smile. No camera movement, text or extra props. Keep the full body in frame. ``` [Animate an existing image](/docs/generate/animations#animate-an-existing-image) to avoid paying for a new pose when you already have the right one. Inspect the results before putting them in your interface. ## Get files that are ready to ship Download the transparent PNG, WebM and HEVC files into your project, or publish them to get stable CDN URLs. A completed MP4 alone is not proof that the transparent versions are ready: check the assets you intend to serve. Signed private URLs expire and should not be hardcoded into a shipped app. The source download uses `/website-use-cases/` for media. Copy that directory to your app’s `public/` folder, or change the URLs in `website-mascots.ts` to your published files. WebM serves Chrome, Firefox and Edge; HEVC with alpha serves Safari. ## Connect it to your web app Keep progress separate from animation state. The app records each habit once per day and fires the celebration; the player returns to idle when it ends. Put the clips in a [canvas](/docs/canvas/build) with one pose: its loop is the idle clip, and each line is an animation of that pose that starts on `action::` and the line's name. Release it, and your backend fetches its delivery ([Play a canvas](/docs/player/canvas)). The page then fires each line once, by name; the [Masko player](/docs/player) plays it when the loop on screen ends and goes back to idle. This small component shows the event wiring with local state. The source download includes the complete layout and production integration points. ```tsx import { useRef, useState } from 'react'; import { MaskoPlayer, type MaskoHandle } from '@masko/player/react'; const habits = ['read', 'walk', 'pause']; // delivery: Pip's canvas release, fetched by your backend. export function HabitCompanion({ delivery }) { const pip = useRef(null); const [completed, setCompleted] = useState([]); function completeHabit(id: string) { if (completed.includes(id)) return; setCompleted((previous) => [...previous, id]); pip.current?.fire('action::celebrate'); } return (

{completed.length} of 3 habits complete

{habits.map((id) => ( ))}
); } ``` The source download includes validated browser storage, calendar days, disabled completed habits and the next-visit preview. The “Come back tomorrow” control deliberately simulates a later date. In a real app, use the user’s calendar day and visit history, rather than a simulation button. Count a daily milestone only when all three distinct habits are complete. A reload or another click on an already completed habit must not award it again. Save completed dates as well as the current day so returning users keep their history. ## Use the full design [Download the React source and generated media](/examples/website-mascot-demos.zip). It contains all three independent designs. Choose **progress** for this tutorial: ```tsx import { WebsiteMascotDemo } from './WebsiteMascotDemo'; export default function Example() { return ; } ``` The archive includes the components, CSS module, media configuration and a README. Import the CSS module beside the components and keep your app’s existing React setup. The example plays each mascot with the Masko player, from a small canvas it builds from these clips in `website-mascot-delivery.ts`. In your app, play your own canvas release instead. Keep spoken lines visible as text. ## Check the finished experience Finish one habit and reload: its checkmark must remain. Click a completed habit again: the count must not increase. Finish all three, preview tomorrow, and check that the next day starts at zero while the previous milestone remains. Reload again and check the welcome-back message. Reset the demo and confirm the history clears. Test muted, with sound, with reduced motion, and with a keyboard. Check a narrow mobile viewport and both WebM and Safari’s HEVC playback. A failed media request should leave the still image or previous frame visible and offer a retry. ## Connect your real product Save progress in the signed-in user’s account. Use their timezone for daily boundaries, validate event ownership on the server, and make writes idempotent. Keep celebration brief and avoid guilt for missed days. ## Costs and what to measure A reference image and two five-second Standard animations from that image cost about **21 credits** (1 + 10 + 10). The welcome-back message uses the same idle clip, so no new generation is needed for each visit. Exports and playback of downloaded or published files add no generation cost. The tutorial catalog shows estimates, not the cost of a visitor’s session. Measure return visits, completed activities and continued use over several weeks. Check whether the companion helps the habit itself, rather than only increasing clicks on the mascot. ## Ask Lovable to build your version ```text Build a daily habit web app called Little Wins with a calm cream, sage and lilac design with landscape photography. Use Masko to create Pip, a lilac axolotl with coral gills and a mint scarf. Give Pip an idle loop and a celebration animation. Celebrate completed habits and daily milestones, save progress, and welcome returning users to their next activity without guilt or punishment. Follow https://masko.ai/docs/tutorials/web-progress-companion. Use Masko v1 at https://api.masko.ai/v1 and inspect https://api.masko.ai/v1/openapi.json. Use real generated files, keep generation credentials on the server, and download or publish assets before shipping the app. ``` [Reference prompts and Masko v1 recipe](/docs/use-cases/web-progress-companion) · [All website use cases](/docs/use-cases#website) --- ---url: /docs/tutorials/web-support-mascot--- Source: https://masko.ai/docs/tutorials/web-support-mascot # Add a talking support mascot in the bottom right Build Leaf & Loom’s bottom-right support widget with Fern, a terracotta plant pot. A click opens help, the visitor chooses care or delivery, and a human handoff remains available. [Reference prompts and Masko v1 recipe](/docs/use-cases/web-support-mascot) · [Download the full React examples](/examples/website-mascot-demos.zip) ## What you will build A responsive web interface with a new brand character, working interactions and transparent video playback. This tutorial uses **Leaf & Loom**, a fictional product. The demo is free to try; generating your own assets spends your Masko credits. **What you need:** a React app, a Masko account, and a mascot’s published files or local downloads. Use Studio for generation, or keep a write API key on your server. Never ship the key in browser code. ## Create Fern for your brand ![Fern: approved character reference and animation recipe](/website-use-cases/fern-recipe.svg) Copy this reference prompt: ```text A friendly little terracotta plant pot with two jade leaves, large dark eyes, a small smiling mouth, short rounded arms and feet, warm clay texture, full character centered, soft 3D lighting, no text or extra props. ``` A little terracotta pot with jade leaves and a warm voice. Fern knows when to explain plant care and when to hand the conversation to a person. Create the reference in [Studio](/mascots/new), or follow [Create a mascot from text](/docs/create/from-text). Keep the approved image as the mascot’s reference so future poses share its identity. The downloaded examples include the completed demonstration media; replace the files when you make your own character. ## Map the interaction states ![Fern: website states and clips](/website-use-cases/fern-flow.svg) Keep panel visibility, selected topic and team contact as website state. **All help topics** returns to the menu. Closing or Escape returns focus to the launcher. Topic clips play once; the demo holds the last frame, and **Hear Fern** replays the line. Team contact uses the idle clip and a message form. These are website interaction states. Masko v1 creates the media; your app chooses which clip to show. [Copy the v1 requests](/docs/use-cases/web-support-mascot#1-create-the-reference) for character creation, generation and file delivery. ## Make the animation set | Clip or moment | Performance or spoken line | Trigger | | -------------- | -------------------------------------------------------------------------------------------------------------------------- | ---------------------- | | Greeting | “Hi, I’m Fern. Looking for plant care or delivery help?” | The visitor opens help | | Plant care | “Give your plant bright, indirect light. Check the soil before watering. Each plant comes with its own care card.” | Care selected | | Delivery | “Our example shop dispatches plants in two working days, packed with care. Your confirmation email has the tracking link.” | Delivery selected | Create a five-second idle loop from your approved image. Keep the camera still and the full character inside the frame. Then make the three talking clips below. ```text A tiny relaxed sway and a friendly blink; the jade leaves move gently. Keep the pot, arms and feet consistent. Full character in frame, still camera, seamless loop. ``` [Animate an existing image](/docs/generate/animations#animate-an-existing-image) to avoid paying for a new pose when you already have the right one. Inspect the results before putting them in your interface. ## Give Fern a voice and record the lines In Studio, open **Context & References → Voice → Add a voice**. Try this description: ```text A warm, practical adult voice with a gentle British accent, relaxed conversational pace. ``` Hear the samples and keep one. Each voice sample set costs 1 credit. For each line in the recipe table, create a **Talking animation** from the welcome pose. Add movements in brackets, such as `[waves gently, warm]`, and request a cost estimate before generating. For an API or coding agent, use the [talking tutorial](/docs/tutorials/make-it-talk) for voice samples, voice selection and job polling. Once the mascot has a voice, this request estimates one clip: ```typescript // Run on your server. Keep this helper out of browser code. async function masko(path: string, body: object, operationId: string) { const response = await fetch(`https://api.masko.ai/v1${path}`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.MASKO_API_KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': operationId }, body: JSON.stringify(body) }); const result = await response.json(); if (!response.ok) throw new Error(result.error?.message ?? 'Request failed'); return result.data; } const request = { type: 'animation', item_id: welcomeItemId, speech: { script: '[waves gently, warm] Hi, I’m Fern. Looking for plant care or delivery help?' } }; const estimate = await masko( `/mascots/${mascotId}/generate`, { ...request, dry_run: true }, crypto.randomUUID() ); // Review estimate.estimate before sending the paid request. // Use a unique Idempotency-Key and the identical body on retries. ``` Generate your actual scripted lines from the table, not the example greeting above. Wait for the job and its WebM and HEVC files to complete before adding the result to the app. ## Get files that are ready to ship Download the transparent PNG, WebM and HEVC files into your project, or publish them to get stable CDN URLs. A completed MP4 alone is not proof that the transparent versions are ready: check the assets you intend to serve. Signed private URLs expire and should not be hardcoded into a shipped app. The source download uses `/website-use-cases/` for media. Copy that directory to your app’s `public/` folder, or change the URLs in `website-mascots.ts` to your published files. WebM serves Chrome, Firefox and Edge; HEVC with alpha serves Safari. ## Connect it to your web app Use a button as the launcher and keep the panel closed until the visitor chooses help. Fern appears beside the launcher when closed and inside the panel when opened. The widget stays inside the demo’s shop; on a real website, position the widget relative to the viewport with room for mobile safe areas. Put the clips in a [canvas](/docs/canvas/build) with one pose: its loop is the idle clip, and each line is an animation of that pose that starts on `action::` and the line's name. Release it, and your backend fetches its delivery ([Play a canvas](/docs/player/canvas)). The page then fires each line once, by name; the [Masko player](/docs/player) plays it when the loop on screen ends and goes back to idle. This small component shows the event wiring with local state. The source download includes the complete layout and production integration points. ```tsx import { useEffect, useRef, useState } from 'react'; import { MaskoPlayer, type MaskoHandle } from '@masko/player/react'; const answers = { welcome: 'Hi, I’m Fern. Looking for plant care or delivery help?', care: 'Give your plant bright, indirect light. Check the soil before watering. Each plant comes with its own care card.', delivery: 'Our example shop dispatches plants in two working days, packed with care. Your confirmation email has the tracking link.' }; // delivery: Fern's canvas release, fetched by your backend. export function ShopHelp({ delivery }) { const [open, setOpen] = useState(false); const [topic, setTopic] = useState('welcome'); const launcher = useRef(null); const fern = useRef(null); // Fern says the topic's line once, when the loop on screen ends. useEffect(() => { if (open) fern.current?.fire(`action::${topic}`); }, [open, topic]); function close() { setOpen(false); launcher.current?.focus(); } return (
{ if (event.key === 'Escape') close(); }} > {open && (

{answers[topic]}

)}
); } ``` The downloaded example includes a close control, Escape handling, focus returning to the launcher, topic buttons, captions and a local message preview. It never sends a customer message. Replace that preview with your support provider’s SDK or a server endpoint, and confirm successful delivery before telling the visitor the message was sent. The written answer is the source of truth. These talking clips contain prewritten lines. Your existing chatbot or help service supplies dynamic answers; Masko’s build-time connection does not turn those answers into live lip-synced speech. ## Use the full design [Download the React source and generated media](/examples/website-mascot-demos.zip). It contains all three independent designs. Choose **support** for this tutorial: ```tsx import { WebsiteMascotDemo } from './WebsiteMascotDemo'; export default function Example() { return ; } ``` The archive includes the components, CSS module, media configuration and a README. Import the CSS module beside the components and keep your app’s existing React setup. The example plays each mascot with the Masko player, from a small canvas it builds from these clips in `website-mascot-delivery.ts`. In your app, play your own canvas release instead. Keep spoken lines visible as text. ## Check the finished experience Open help with the keyboard, choose both topics, and check that the text and clip match. Close with Escape and confirm focus returns to the launcher. Try it on a narrow screen and verify product controls remain reachable. Preview a message: the page must clearly say it was not sent. Test muted, with sound, with reduced motion, and with a keyboard. Check a narrow mobile viewport and both WebM and Safari’s HEVC playback. A failed media request should leave the still image or previous frame visible and offer a retry. ## Connect your real product Replace the example care and delivery policies with your actual policies before publishing. Connect your support service, provide a human handoff, and keep a text fallback if video or sound cannot play. ## Costs and what to measure Budget **1 credit for the reference image**, **10 for a five-second Standard idle loop from that image**, **1 for a voice sample set**, and **2 credits per second of talking**. Three talking clips have a five-second minimum each, so this recipe starts at about **42 credits**; the longer scripts in this example cost more. Use the talking estimate for your own lines. Exports and playback of downloaded or published files add no generation cost. The tutorial catalog shows estimates, not the cost of a visitor’s session. Compare answered questions, successful support handoffs and purchase completion. A friendly mascot can support trust, while accurate answers and clear service policies do the work. ## Ask Lovable to build your version ```text Build a botanical ecommerce website called Leaf and Loom with forest green, cream and terracotta colors. Use Masko to create Fern, a friendly terracotta plant pot with jade leaves. Put Fern in the bottom right as a button that opens help. Add care and delivery topics with written answers and optional talking clips, plus a human support handoff. Keep checkout clear and sound off until clicked. Follow https://masko.ai/docs/tutorials/web-support-mascot. Use Masko v1 at https://api.masko.ai/v1 and inspect https://api.masko.ai/v1/openapi.json. Use real generated files, keep generation credentials on the server, and download or publish assets before shipping the app. ``` [Reference prompts and Masko v1 recipe](/docs/use-cases/web-support-mascot) · [All website use cases](/docs/use-cases#website) --- ---url: /docs/tutorials/ai-loading-mascot--- Source: https://masko.ai/docs/tutorials/ai-loading-mascot # Show a mascot while your AI thinks Your mascot thinks while your AI request runs, stays patient on long answers and celebrates when the result lands, with no flicker between clips. You need a mascot: [Create your first mascot](/docs/tutorials/first-mascot). **25 min · 33 credits · Web, iOS, Android** ## Do it in Studio 1. Open your mascot in [Studio](/mascots), go to the **Animations** tab, press **Create Animation** and choose **New image + animation**. 2. Fill in one row of the table below, keep **Loop** on, **Standard** and 5s, and press **Generate · 11 credits**. Repeat for the other two. 3. When all three are ready, press **Get Links**, turn on **Enable asset hosting** and copy each animation's `transparent_video_mov` and `transparent_video_webm` from **Export JSON**. Then go to [step 4](#4-play-the-thinking-loop). | Name | Starting image | Movement | | -------- | ------------------------------------- | ----------------------------------------------------- | | Thinking | hand on chin, looking up thoughtfully | thinking, tapping its chin, eyes drifting up and back | | Waiting | standing relaxed, patient and calm | gentle breathing, a small sway, a slow blink | | Done | arms raised in celebration, big smile | one happy hop with arms up, landing back in place | ## Do it with the API You need a write key from [API keys](/api-keys) in `MASKO_API_KEY` and 33 credits (`GET /v1/credits` shows your balance). Never put the key in browser or app code. ### 1. Find your mascot Copy your mascot's `id`. ```bash curl https://api.masko.ai/v1/mascots \ -H "Authorization: Bearer $MASKO_API_KEY" ``` ### 2. Generate the three animations One batch call runs all three in parallel. Each request draws a pose from `image_prompt` and animates it with `animation_prompt`; set `animation_model` to `standard` and keep `loop: true`. POST /v1/mascots/:id/generate-batch ```bash curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/generate-batch \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "requests": [ { "type": "animation", "animation_model": "standard", "name": "Thinking", "image_prompt": "hand on chin, looking up thoughtfully", "animation_prompt": "thinking, tapping its chin, eyes drifting up and back", "duration": 5, "loop": true }, { "type": "animation", "animation_model": "standard", "name": "Waiting", "image_prompt": "standing relaxed, patient and calm", "animation_prompt": "gentle breathing, a small sway, a slow blink", "duration": 5, "loop": true }, { "type": "animation", "animation_model": "standard", "name": "Done", "image_prompt": "arms raised in celebration, big smile", "animation_prompt": "one happy hop with arms up, landing back in place", "duration": 5, "loop": true } ] }' ``` ```javascript // Run on your server. Never ship your API key to a browser. const clip = (name, image_prompt, animation_prompt) => ({ type: 'animation', animation_model: 'standard', name, image_prompt, animation_prompt, duration: 5, loop: true }); const res = await fetch( `https://api.masko.ai/v1/mascots/${mascotId}/generate-batch`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.MASKO_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ requests: [ clip( 'Thinking', 'hand on chin, looking up thoughtfully', 'thinking, tapping its chin, eyes drifting up and back' ), clip( 'Waiting', 'standing relaxed, patient and calm', 'gentle breathing, a small sway, a slow blink' ), clip( 'Done', 'arms raised in celebration, big smile', 'one happy hop with arms up, landing back in place' ) ] }) } ); const body = await res.json(); if (!res.ok) throw new Error(body.error?.message || `HTTP ${res.status}`); console.log(body.data.total_cost); // 33 const jobs = body.data.jobs; // one job per animation, in order ``` ```json { "data": { "jobs": [ { "job_id": "JOB_ID_THINKING", "item_id": "ITEM_ID_THINKING", "item_name": "Thinking", "status": "pending", "estimated_cost": 11, "asset_ids": { "image": "...", "video": "...", "webm": "...", "hevc": "..." }, "urls": { "webm": "https://assets.masko.ai/.../thinking-a1b2.webm", "hevc": "https://assets.masko.ai/.../thinking-c3d4.mov" } }, { "job_id": "JOB_ID_WAITING", "item_name": "Waiting", "estimated_cost": 11 }, { "job_id": "JOB_ID_DONE", "item_name": "Done", "estimated_cost": 11 } ], "total_cost": 33 } } ``` It returns `202 Accepted` with one job per animation. A batch is not all-or-nothing: check that every entry in `data.jobs` has a `job_id`, and never resend the whole batch after an uncertain response. `premium` costs 31 credits per clip instead of 11. Clips that end on their first pose loop and switch cleanly. Already have the poses? Pass `source_image_asset_id` instead of `image_prompt` for 10 credits each. ### 3. Wait for the jobs Long-poll each job until `completed` or `failed`, or use a webhook in production. A completed job carries `urls.webm` and `urls.hevc`. ```bash # Holds the request open until the job finishes, up to 120 seconds. # If the timeout comes first, you get the current status: ask again. curl "https://api.masko.ai/v1/jobs/JOB_ID?wait=true&timeout=120" \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Masko-API-Version: 2026-09-26" ``` ```javascript async function waitForJob(jobId, timeoutMs = 10 * 60 * 1000) { const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { const res = await fetch( `https://api.masko.ai/v1/jobs/${jobId}?wait=true&timeout=30`, { headers: { Authorization: `Bearer ${process.env.MASKO_API_KEY}`, 'Masko-API-Version': '2026-09-26' }, signal: AbortSignal.timeout(Math.min(35000, deadline - Date.now())) } ); const body = await res.json(); if (!res.ok) throw new Error(body.error?.message || `HTTP ${res.status}`); const job = body.data; if (job.status === 'completed') return job; if (job.status === 'failed') throw new Error(JSON.stringify(job.error)); } throw new Error(`Stopped waiting. Check job ${jobId} again later.`); } const [thinking, waiting, done] = await Promise.all( jobs.map((job) => waitForJob(job.job_id)) ); console.log(thinking.urls.webm, thinking.urls.hevc); ``` ```bash # Register once. Masko then POSTs each finished job to your URL. curl -X POST https://api.masko.ai/v1/webhooks \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-app.com/api/masko-webhook", "events": ["job.completed", "job.failed"] }' # Save the "secret" from the response: it is shown only once. ``` The URLs in the batch response serve a placeholder until each job is `completed`, so switch to a clip only after that. With a [webhook](/docs/manage/webhooks), check the `Masko-Signature` header on every call. ### 4. Play the thinking loop Give the [Masko player](/docs/player) the Thinking animation's files ([install it](/docs/player#install) first). It loops it with its transparency on every platform. ```html
``` ```tsx import { MaskoPlayer } from '@masko/player/react'; // ``` ```swift MaskoView(player: MaskoPlayer(clip: thinkingURL)) // the job's urls.hevc .frame(width: 160, height: 160) ``` ```kotlin val player = view.loadClip(thinkingStackedUrl) // a MaskoPlayerView in your layout ``` The Android file is the `stacked_video` asset (`GET /v1/mascots/:id/assets`), or `transparent_video_android` in the [hosted links](/docs/tutorials/mascot-on-your-website). ### 5. Switch between thinking, waiting and done The Masko player switches clips without a flicker when they are in a [canvas](/docs/canvas). Put the three animations in a canvas as three poses, with [Any State](/docs/canvas/build) moves on `working`, `waiting` and `celebrate` (an Any State move has no video of its own), release it and play the release as in [Play a canvas](/docs/player/canvas). Your app then only says what is happening: ```javascript // mascot is the Masko player playing your canvas release. async function ask(prompt) { mascot.set('working', true); const slow = setTimeout(() => mascot.set('waiting', true), 8000); try { const answer = await runYourModel(prompt); mascot.fire('celebrate'); return answer; } finally { clearTimeout(slow); mascot.set('working', false); mascot.set('waiting', false); } } ``` Thinking starts at the end of the loop on screen, Waiting takes over after 8 seconds, and Done plays when the answer lands. Give the Waiting move a higher priority than Thinking, so it shows while both are on. ## What you get - **Three animations**, Thinking, Waiting and Done: five-second Standard clips with their pose images. - **Every format**: transparent WebM and HEVC `.mov`, `stacked_video` for Android and a plain MP4. [Exports](/docs/manage/media-exports) to Lottie, GIF and more are free. - **Hosted links** on `https://assets.masko.ai`. For phones, turn on free [360 px size variants](/docs/tutorials/mascot-on-your-website#make-the-files-lighter). ## Using an AI agent? Paste this into your coding agent, in your app's project, connected to the Masko MCP server ([setup for Claude Code, Codex, Cursor and more](/docs/mcp)) or with the [Masko skill](/docs/ai-tools/skills) and an API key. ```text Add a loading mascot to this app with Masko, following https://masko.ai/docs/tutorials/ai-loading-mascot Use my mascot "MASCOT_NAME". Ask me if I have several. Generate three Standard 5-second looping animations: Thinking, Waiting and Done. Show me the cost first and wait for my OK. It should be 33 credits. Never put my Masko API key in browser or app code. Wait for the jobs, then use each job's urls.webm and urls.hevc. Show Thinking while my AI request runs, Waiting after 8 seconds, and Done when it returns: put the three in a canvas and play its release with the Masko player (https://masko.ai/docs/player/canvas). ``` ## Next - [Animate the mascot you already have](/docs/tutorials/animate-your-mascot) - Next tutorial: start from your own character. - [Make a character talk](/docs/tutorials/make-it-talk) - Your mascot says a line in its own voice. - [Masko player](/docs/player) - Install it on every platform and play a canvas. - [State machine](/docs/canvas/build) - More states, or reactions to taps and clicks. --- ---url: /docs/tutorials/ai-agent-face--- Source: https://masko.ai/docs/tutorials/ai-agent-face # Give your AI agent a face Your AI agent runs in the background. Your mascot shows what it is doing and tells you when it needs you. Each step of the agent sends an event, and each event moves the mascot to a new pose. **20 min · 66 credits · Web, Mac, any agent** ## 1. Make the clips Start with one five-second loop per state: Idle, Thinking, Working, Needs approval, Done and Error. Send the six requests in one batch call, 11 credits each. POST /v1/mascots/:id/generate-batch ```json { "requests": [ { "type": "animation", "animation_model": "standard", "name": "Needs approval", "image_prompt": "waving one hand to get attention", "animation_prompt": "waves twice, then waits", "duration": 5, "loop": true } ] } ``` Add the five other states to `requests`, then [wait for the jobs](/docs/manage/jobs) and keep each clip's `urls.webm` and `urls.hevc`. Fun extras like Searching, Found it, Running tests and Deploying are one more request each. ## 2. Get the events ### From Claude Code or Codex A hook is a command your agent runs at each event, with the event as JSON. Point every event at your app: ```json { "hooks": { "UserPromptSubmit": [ { "hooks": [ { "type": "command", "command": "curl -s -m 1 -X POST localhost:4545/event --data-binary @- || true" } ] } ] } } ``` Add the same entry for `PreToolUse`, `PostToolUse`, `PermissionRequest`, `Notification`, `Stop`, `StopFailure` and `SessionEnd`. Codex reads the same format from `~/.codex/hooks.json`. Your app reads `hook_event_name`, and `tool_name` for tools, and picks the state: | Event | Tool | State | | ----------------------------------- | ----------------------- | -------------- | | `UserPromptSubmit` | | Thinking | | `PreToolUse` | `Grep`, `Glob`, `Read` | Searching | | `PreToolUse` | `Edit`, `Write` | Working | | `PreToolUse` | `Bash` running tests | Running tests | | `PreToolUse` | `Bash` running a deploy | Deploying | | `PermissionRequest`, `Notification` | | Needs approval | | `Stop` | | Done | | `StopFailure` | | Error | | `SessionEnd` | | Idle | ### Let a small model catch the moments The hook says which tool runs, not what the agent found. For moments like "found the bug" or "the build failed", send the agent's last words to a small OpenAI model and let it pick. Claude Code puts them in `tool_response` and `last_assistant_message`. With no hooks at all, like a chatbot or your own agent loop, the same call drives every state. ```javascript const decision = await openai.decisions.create({ model: 'gpt-6-luna', input: lastMessages, questions: [ { type: 'choice', name: 'state', instructions: 'What is the AI agent doing now?', choices: [ { value: 'thinking', description: 'Reads, plans or writes.' }, { value: 'found_it', description: 'Just found the cause of a bug.' }, { value: 'needs_approval', description: 'Asks the user for a yes.' }, { value: 'done', description: 'Finished and reports back.' }, { value: 'error', description: 'Something failed.' } ] } ] }); ``` It answers in about a third of a second. If it is unsure or slow, keep the current state. ## 3. Show the right clip Put the clips in a [canvas](/docs/canvas/build), one pose per state with a move on each state's input, release it and play the release with the [Masko player](/docs/player/canvas). It switches clips without a blink. Your app only names the state: ```javascript // mascot is the Masko player playing your canvas release. const states = ['thinking', 'working', 'done']; function show(state) { for (const name of states) mascot.set(name, name === state); } ``` **Masko for Mac does all of this for you.** Connect Claude Code, Codex or Cursor and your mascot reacts on your desktop, and with Claude Code you can approve from the mascot. [Download Masko for Mac](/download). ## Next - [Show a mascot while your AI thinks](/docs/tutorials/ai-loading-mascot) - The same switching, around one AI request. - [Build a canvas](/docs/canvas/build) - Move between poses with transitions. - [Build with your AI agent](/docs/ai-agents) - Let your agent make the clips. --- ---url: /docs/tutorials/animate-your-mascot--- Source: https://masko.ai/docs/tutorials/animate-your-mascot # Animate the mascot you already have Upload your own character, logo or drawing, and Masko animates that exact image with no redraw. The upload also becomes the mascot's reference, so later poses and animations keep its look. **10 min · 10 credits · Studio or API** ## Do it in Studio 1. Open [Create a mascot](/mascots/new) and choose the **Upload yours** tab. 2. Press **Choose an image** (PNG, JPG, WebP or SVG), name it and press **Create mascot**. 3. In the **Animations** tab, press **Create Animation** and choose **Animate an image**. 4. Press **Upload image** and pick the same file (PNG, JPEG or WebP, up to 10 MB), or **Choose existing**. 5. Describe the motion in **Movement (optional)**, such as "Wave gently, keeping the feet planted." Keep **Loop** on, **Standard** and 5s, and press **Generate · 10 credits**. ## Do it with the API You need a write key from [API keys](/api-keys) in `MASKO_API_KEY` and a `PROJECT_ID` ([create a project](/docs/tutorials/first-mascot#1-create-a-project)). Run these calls on your server or in a terminal, never in browser code. ### 1. Upload your image Send the file as a multipart form, or the URL of a public image. Uploads are free. URL imports take PNG, JPEG, WebP, GIF and SVG up to 10 MB, and SVG becomes PNG. POST /v1/upload ```bash curl -X POST https://api.masko.ai/v1/upload \ -H "Authorization: Bearer $MASKO_API_KEY" \ -F "file=@my-mascot.png" ``` ```javascript import { readFile } from 'node:fs/promises'; const form = new FormData(); form.append( 'file', new Blob([await readFile('my-mascot.png')], { type: 'image/png' }), 'my-mascot.png' ); // Let fetch set the multipart boundary: do not set Content-Type yourself. const res = await fetch('https://api.masko.ai/v1/upload', { method: 'POST', headers: { Authorization: `Bearer ${process.env.MASKO_API_KEY}` }, body: form }); const body = await res.json(); if (!res.ok) throw new Error(body.error?.message || `HTTP ${res.status}`); const assetId = body.data.asset_id; ``` ```bash curl -X POST https://api.masko.ai/v1/upload \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/my-mascot.png" }' ``` Save `data.asset_id` as `UPLOADED_IMAGE_ID`. ### 2. Create the mascot and animate it in one request `create_mascot` makes a new mascot whose first reference is your upload, used as it is, and starts the animation. Leave out `create_mascot.name` and Masko picks a name from the image. POST /v1/animations ```bash curl -X POST https://api.masko.ai/v1/animations \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: first-wave-001" \ -d '{ "source_image_asset_id": "UPLOADED_IMAGE_ID", "create_mascot": { "project_id": "PROJECT_ID" }, "name": "Wave", "animation_prompt": "Wave gently, keeping the feet planted.", "animation_model": "standard", "duration": 5 }' ``` ```javascript const res = await fetch('https://api.masko.ai/v1/animations', { method: 'POST', headers: { Authorization: `Bearer ${process.env.MASKO_API_KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': 'first-wave-001' }, body: JSON.stringify({ source_image_asset_id: assetId, create_mascot: { project_id: projectId }, name: 'Wave', animation_prompt: 'Wave gently, keeping the feet planted.', animation_model: 'standard', duration: 5 }) }); const body = await res.json(); if (!res.ok) throw new Error(body.error?.message || `HTTP ${res.status}`); const { mascot_id, mascot_name, job_id, estimated_cost } = body.data; ``` The `202` answer carries `mascot_id`, `mascot_name`, `job_id`, `item_id`, `asset_ids`, `estimated_cost` and `poll_url`. Defaults are Standard, 5 seconds and looping, for 10 credits; creating the mascot, naming and cropping are free. A retry with the same `Idempotency-Key` and body is never charged twice. If the animation fails after the mascot was created, `error.details.mascot_id` gives its ID: reuse it with a new key. ### 3. Wait for the job ```bash curl "https://api.masko.ai/v1/jobs/JOB_ID?wait=true&timeout=120" \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Masko-API-Version: 2026-09-26" ``` ```javascript // waitForJob is the long-polling helper from the "Make it move" tutorial. const job = await waitForJob(job_id); console.log(job.urls.webm, job.urls.hevc); ``` The server holds the request until the job is `completed` or `failed`; if the timeout passes first, ask again. Never resend the animation request to check progress. In production, use a [webhook](/docs/manage/webhooks). ### 4. Make more animations of the same character Send the returned `mascot_id` instead of `create_mascot`: each `create_mascot` request makes a separate mascot. ```json { "mascot_id": "RETURNED_MASCOT_ID", "source_image_asset_id": "UPLOADED_IMAGE_ID", "name": "Bounce", "animation_prompt": "Bounce gently in place." } ``` Or create the mascot first, for free: `POST /v1/mascots` with `"reference_asset_ids": ["UPLOADED_IMAGE_ID"]`. Then animate with `POST /v1/mascots/MASCOT_ID/generate`, `type: "animation"` and `source_image_asset_id`, for the same 10 credits. ## Keep its identity - **Square, clean image.** Use a white or transparent background, at 1024 × 1024 or larger. A wide or tall image is center-cropped to a separate square source, and `source_image_crop` (`x`, `y`, `width`, `height` in pixels) picks the square yourself ([image framing](/docs/generate/animations#image-framing)). - **Describe motion, not looks.** Say what moves and what stays still, such as "keeping the feet planted". - **Write down the rules.** `PATCH /v1/mascots/MASCOT_ID` with a `context` such as "always wears the red hat" saves them as hints for later generations. - **Add more angles.** Up to 6 references; three or four views in the same style give steadier poses. `POST /v1/mascots/MASCOT_ID/references` with an `asset_id` from `/v1/upload`. Video models can still change small details, so check the result before you ship. A new take is paid again. ## What you get - **An animation** in every format: transparent WebM and HEVC `.mov`, a stacked MP4 for Android and a plain MP4. - **Hosted links** when hosting is on, ready for [your website](/docs/tutorials/mascot-on-your-website). [Exports](/docs/manage/media-exports) to GIF, Lottie, ProRes and more are free. ## Using an AI agent? Paste this into an agent connected to the Masko MCP server ([setup for Claude Code, Codex, Cursor and more](/docs/mcp)), or into one with the [Masko skill](/docs/ai-tools/skills) and an API key. ```text Animate the mascot I already have with Masko, following https://masko.ai/docs/tutorials/animate-your-mascot My character image: . Use the Masko MCP tools if they are connected. Otherwise use the REST API at https://api.masko.ai/v1 with MASKO_API_KEY from my environment. Never print the key. If you cannot upload a local file, ask me to upload it in Masko Studio first. Never send my image anywhere else. Animate it as it is, without drawing a new image: a five-second Standard loop where it waves gently and keeps its feet planted. Tell me the cost first and wait for my OK. It should be 10 credits. Wait for the job, then give me the mascot ID and the WebM and HEVC links. ``` ## Next - [Put it on your website](/docs/tutorials/mascot-on-your-website) - Next tutorial: play it on any page with the Masko player. - [Make a character talk](/docs/tutorials/make-it-talk) - A voice and a talking animation. - [Create from an image](/docs/create/from-image) - References, auto-analysis and best practices. - [Animations](/docs/generate/animations) - Transitions, Premium, crops and size variants. --- ---url: /docs/tutorials/telegram-stickers--- Source: https://masko.ai/docs/tutorials/telegram-stickers # How to make Telegram stickers | Rolling belly laugh | Startled recoil | Excited star-jump | | --- | --- | --- | | ![Dumple laughing so hard it rocks back and forth, an animated Telegram sticker](https://assets.masko.ai/547bbb8e/dumple-vsa8qdhb/dumple-everyday-h3-loop---01-lol-60ba9937-512.webp) | ![Dumple jumping back in surprise, an animated Telegram sticker](https://assets.masko.ai/547bbb8e/dumple-vsa8qdhb/dumple-everyday-h3-loop---02-wait-what-db7711d9-512.webp) | ![Dumple bouncing with stars in its eyes, an animated Telegram sticker](https://assets.masko.ai/547bbb8e/dumple-vsa8qdhb/dumple-everyday-h3-loop---23-so-excited-a0449d6f-512.webp) | Make a character once, give it as many reactions as you like, and add them all to Telegram as one pack: image stickers and animated stickers together. Masko's [AI sticker maker](/studio/stickers) removes the background from every pose and animation, sizes each file for Telegram and builds the pack, so you never touch the @Stickers bot or a file converter. **20 min · 1 credit per image sticker, animated stickers free from an animation · Studio** Every sticker below was made this way: the whole Dumple Everyday pack. Tap one to send it in the little chat, or [add the pack to Telegram](https://t.me/addstickers/dumple_everyday_by_MaskoStickersBot). ## 1. Make your character Describe it in one sentence on [Create a mascot](/mascots/new), upload a drawing or your logo, or send your whole [brand book](/docs/create/from-brand-book) and get a mascot with its colors, style and don'ts already filled in. Every pose and animation after that starts from the same reference, so all your stickers show the same character. ## 2. Give it big reactions A sticker is a reaction, so go big: a belly laugh, a startled jump, a heart, a proud "nailed it", a sleepy yawn. - **For an image sticker**, make a pose: in the **Images** tab, press **Create Image** and describe it, such as "laughing so hard it holds its belly". - **For an animated sticker**, make an animation: in the **Animations** tab, press **Create Animation** and describe the movement, such as "bursts out laughing and rocks back and forth". Keep it short and looping. [Create an animated character](/docs/tutorials/first-animation) walks through it. Poses and animations come out with a transparent background, so every sticker has clean edges and no box around the character. ## 3. Turn them into image and animated stickers Press **Sticker** on the card: - **On a pose**, you get an image sticker: a transparent PNG with a white border, for 1 credit. - **On an animation**, you get an animated sticker in four versions at once, for no credits. The Telegram one is a 512 × 512 WebM. Both land in the **Stickers** tab. | Image sticker | Image sticker | | --- | --- | | ![Dumple sighing with relief, an image sticker with a white border](https://assets.masko.ai/547bbb8e/dumple-vsa8qdhb/sticker-expression-study---relief-a1ffa323.bin) | ![Dumple sulking under a little rain cloud, an image sticker with a white border](https://assets.masko.ai/547bbb8e/dumple-vsa8qdhb/sticker-expression-study---sulk-31ddcfa5.bin) | ## 4. Add the sticker pack to Telegram 1. Open **Get Links → Stickers → Telegram** and click **Prepare for Telegram**. 2. Click **Open in Telegram**, or scan the QR code with your phone. 3. Tap **Add Stickers**. Masko keeps one pack per mascot with all its ready image and animated stickers, up to Telegram's limit of 120. **Add to Telegram** on an animated sticker opens the same pack. - Press **Prepare** again after you make new stickers: it adds them without duplicates. - Deleting a sticker in Masko does not remove it from Telegram. - Anyone with the pack link can use the stickers. The pack needs no public hosting. ## Telegram sticker size and format | Sticker | File Masko makes | Size | Limit | | --- | --- | --- | --- | | Image | WebP | 512 pixels on its longest side | under 512 KB | | Animated | VP9 WebM, no sound | 512 × 512 | 3 seconds, 230,400 bytes | A longer animation is retimed to fit its whole loop into 3 seconds. Already have a GIF, video or picture? The free [Telegram sticker maker](/tools/telegram-sticker-maker) turns it into a video sticker or emoji the @Stickers bot accepts, in your browser. ## Do it with the API Your code can make the stickers; adding the Telegram pack is done in Studio. You need a write key from [API keys](/api-keys) in `MASKO_API_KEY`. POST /v1/assets/:id/sticker ```bash curl -X POST https://api.masko.ai/v1/assets/ASSET_ID/sticker \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` With an image ID, it returns the image sticker. With a completed animation that has its background removed, it starts the animated sticker: poll `GET /v1/assets/STICKER_ASSET_ID/sticker` until `formats.telegram` is completed, then use its `file_url`, or its permanent `public_url` when asset hosting is on. See [animated sticker creation](/docs/manage/media-exports#create-an-animated-sticker). ## Put one on the Wall [The Masko Wall](/wall) is one shared wall of mascot stickers, every one made with Masko. Press **Put on the wall** on a finished image to pin your character next to everyone else's. ## Questions ### Do I need a Telegram bot? No. Masko creates and fills the pack for you. You only tap **Add Stickers** in Telegram. ### Can one pack mix image and animated stickers? Yes. The pack holds every ready image sticker and animated sticker of the mascot, up to 120. ### Can I make a pack for a brand? Yes. Start the mascot from your logo or [brand book](/docs/create/from-brand-book), and every sticker keeps its colors and style. ### Does the same sticker work in other apps? Each animated sticker also comes as an animated WebP for WhatsApp, a GIF for WeChat and an APNG for Discord. See [How to make Discord stickers](/docs/tutorials/discord-stickers). ## Next - [How to make Discord stickers](/docs/tutorials/discord-stickers) - The same reactions, uploaded to your server. - [Free sticker makers](/tools) - Turn any GIF or video into a Telegram, WhatsApp, Discord, Slack or Twitch file. - [Create an animated character](/docs/tutorials/first-animation) - The animations your stickers start from. - [Create a mascot from a brand book](/docs/create/from-brand-book) - A character that already matches your brand. - [Image and video exports](/docs/manage/media-exports) - Every sticker format and its limits. - [The Masko Wall](/wall) - Stickers from every Masko mascot, on one wall. --- ---url: /docs/tutorials/discord-stickers--- Source: https://masko.ai/docs/tutorials/discord-stickers # How to make Discord stickers | Affectionate squeeze | Proud landing | Giant sleepy yawn | | --- | --- | --- | | ![Dumple hugging itself with heart eyes, an animated Discord sticker](https://assets.masko.ai/547bbb8e/dumple-vsa8qdhb/dumple-everyday-h3-loop---03-love-you-f7dc0e9b-512.webp) | ![Dumple landing proudly with a little bounce, an animated Discord sticker](https://assets.masko.ai/547bbb8e/dumple-vsa8qdhb/dumple-everyday-h3-loop---06-nailed-it-c6c1db74-512.webp) | ![Dumple stretching into a huge yawn, an animated Discord sticker](https://assets.masko.ai/547bbb8e/dumple-vsa8qdhb/dumple-everyday-h3-loop---21-good-night-e2ce7998-512.webp) | Give your server its own character with stickers nobody else has. Masko's [AI sticker maker](/studio/stickers) draws the character, animates each reaction with a transparent background and writes the animated sticker in the exact format Discord asks for. You upload it to your server yourself. **20 min · 1 credit per image sticker, animated stickers free from an animation · Studio, then Discord** Every sticker below was made this way: the whole Dumple Everyday pack. Tap one to send it in the little chat; each comes with the Discord APNG Masko made. ## Discord sticker size and format From Discord's [sticker rules](https://support.discord.com/hc/en-us/articles/4402687377815-Tips-for-Sticker-Creators-FAQ): | Sticker | File | Size | Limit | | --- | --- | --- | --- | | Animated | APNG | exactly 320 × 320 | 512 KB | | Image | PNG | exactly 320 × 320 | 512 KB | Masko's animated Discord file is a looping APNG at 320 × 320, under 471,859 bytes and up to 5 seconds, so it uploads as it is. The image sticker PNG is larger than that: resize it to 320 × 320 in any image editor first. Already have a GIF, video or picture? The free [Discord sticker maker](/tools/discord-emoji-maker) turns it into a sticker or emoji at the right size, in your browser. ## 1. Make your character Describe it in one sentence on [Create a mascot](/mascots/new), upload a drawing or your server's logo, or start from a full [brand book](/docs/create/from-brand-book). Every pose and animation starts from that reference, so the whole set looks like one character. ## 2. Give it reactions your server will use Think of what people type all day: "lol", "gg", "wait what", "good night". Make one reaction for each. - **For an animated sticker**, in the **Animations** tab press **Create Animation** and describe the movement, such as "jumps back in surprise, eyes wide". [Create an animated character](/docs/tutorials/first-animation) walks through it. - **For an image sticker**, in the **Images** tab press **Create Image** and describe the pose, such as "sulking with arms crossed". Every pose and animation comes out with a transparent background, so the sticker sits cleanly on Discord's light and dark themes. ## 3. Turn them into animated or image stickers Press **Sticker** on the card. An animation becomes an animated sticker in four versions for no credits, including the Discord APNG. A pose becomes an image sticker for 1 credit. Open the sticker in the **Stickers** tab and download the Discord version, or the PNG for an image sticker. | Image sticker | Image sticker | | --- | --- | | ![Dumple sighing with relief, an image sticker with a white border](https://assets.masko.ai/547bbb8e/dumple-vsa8qdhb/sticker-expression-study---relief-a1ffa323.bin) | ![Dumple sulking under a little rain cloud, an image sticker with a white border](https://assets.masko.ai/547bbb8e/dumple-vsa8qdhb/sticker-expression-study---sulk-31ddcfa5.bin) | Resize an image sticker to 320 × 320 before you upload it; the animated Discord file is already that size. ## 4. Upload to your server You need to own the server or have the **Create Expressions** permission. Uploading works in the Discord desktop or web app, not on phones. 1. Open **Server Settings → Stickers**. 2. Press **Upload Sticker** and choose the file. 3. Enter a name, such as `dumple_lol`, and pick a related emoji so the sticker shows up in suggestions. 4. Add a short description for screen readers, then save. Discord previews APNG animation in the upload window. Dropped into a chat as a plain file, an APNG does not animate. ## How many stickers a server can hold | Server | Sticker slots | | --- | --- | | Every server | 5 | | Boost Level 1 | 15 | | Boost Level 2 | 30 | | Boost Level 3 | 60 | Everyone in your server can send its stickers there. Members with Nitro can use them in other servers and DMs too. ## Do it with the API POST /v1/assets/:id/sticker ```bash curl -X POST https://api.masko.ai/v1/assets/ANIMATION_ASSET_ID/sticker \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` Poll `GET /v1/assets/STICKER_ASSET_ID/sticker` until `formats.discord` is completed, then download its `file_url`, or use its permanent `public_url` when asset hosting is on. Masko does not upload to Discord, so you add the file in your server settings. See [animated sticker creation](/docs/manage/media-exports#create-an-animated-sticker). ## Put one on the Wall [The Masko Wall](/wall) is one shared wall of mascot stickers, every one made with Masko. Press **Put on the wall** on a finished image to pin your character next to everyone else's. ## Questions ### Can Discord stickers be animated? Yes. Masko's animated Discord sticker is a looping APNG at exactly 320 × 320, under Discord's 512 KB limit, and making it from an animation costs no credits. ### Why does my APNG not move in chat? Discord animates stickers, not APNG files posted as attachments. Upload it under **Server Settings → Stickers** and send it from the sticker picker. ### Can I make emoji too? Masko makes stickers. A pose with a transparent background is a good start for an emoji, but Discord emoji have their own sizes and upload screen. ### Can I make stickers for my brand's community server? Yes. Start the mascot from your logo or [brand book](/docs/create/from-brand-book), and every sticker keeps its colors and style. ### Do the same stickers work on Telegram? Yes. Each animated sticker also has a Telegram version, and Masko can add the whole set as one Telegram pack. See [How to make Telegram stickers](/docs/tutorials/telegram-stickers). ## Next - [How to make Telegram stickers](/docs/tutorials/telegram-stickers) - The same reactions as one Telegram pack, from a QR code. - [Free sticker makers](/tools) - Turn any GIF or video into a Discord, Telegram, WhatsApp, Slack or Twitch file. - [Create an animated character](/docs/tutorials/first-animation) - The animations your stickers start from. - [Create a mascot from a brand book](/docs/create/from-brand-book) - A character that already matches your brand. - [Image and video exports](/docs/manage/media-exports) - Every sticker format and its limits. - [The Masko Wall](/wall) - Stickers from every Masko mascot, on one wall. --- ---url: /docs/quickstart--- Source: https://masko.ai/docs/quickstart # Quickstart: your first animation Create a mascot from a description and get a five-second transparent animation of it waving, in four calls. It uses **12 credits**: 1 for the reference image, 1 for the pose and 10 for the Standard video. Ko, in a transparent animation like yours. To check the pose before paying for video, follow the [pose-to-animation walkthrough](/docs/generate/workflow) instead. Using an AI coding agent? It can make these calls for you: [build with your AI agent](/docs/ai-agents). You need a write key from [API keys](/api-keys) and enough [credits](/docs/credits). Run requests on a server or in a terminal, never with the key in browser code, and replace uppercase IDs with values from earlier steps. ## Step 1: Create a project Projects group related mascots. ```bash curl -X POST https://api.masko.ai/v1/projects \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "My App" }' ``` ```javascript const res = await fetch('https://api.masko.ai/v1/projects', { method: 'POST', headers: { Authorization: `Bearer ${process.env.MASKO_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'My App' }), }); const body = await res.json(); if (!res.ok) throw new Error(body.error?.message || `HTTP ${res.status}`); const project = body.data; // Save project.id as PROJECT_ID. ``` ## Step 2: Create a mascot Without reference images, Masko draws the first one from your `prompt` for 1 credit. ```bash curl -X POST https://api.masko.ai/v1/mascots \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Felix the Fox", "project_id": "PROJECT_ID", "prompt": "A friendly orange fox mascot wearing a blue scarf" }' ``` ```javascript const res = await fetch('https://api.masko.ai/v1/mascots', { method: 'POST', headers: { Authorization: `Bearer ${process.env.MASKO_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Felix the Fox', project_id: 'PROJECT_ID', prompt: 'A friendly orange fox mascot wearing a blue scarf', }), }); const body = await res.json(); if (!res.ok) throw new Error(body.error?.message || `HTTP ${res.status}`); const mascot = body.data; // Save mascot.id for the next steps ``` ## Step 3: Generate an animation This five-second Standard request costs 11 credits, including its new pose image (31 with `"premium"`), and returns a job ID. ```bash curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/generate \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "animation", "name": "Waving hello", "image_prompt": "waving hello", "animation_prompt": "waving hello with a friendly hand wave", "animation_model": "standard", "duration": 5 }' ``` ```javascript const res = await fetch( 'https://api.masko.ai/v1/mascots/MASCOT_ID/generate', { method: 'POST', headers: { Authorization: `Bearer ${process.env.MASKO_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ type: 'animation', name: 'Waving hello', image_prompt: 'waving hello', animation_prompt: 'waving hello with a friendly hand wave', animation_model: 'standard', duration: 5, }), } ); const body = await res.json(); if (!res.ok) throw new Error(body.error?.message || `HTTP ${res.status}`); const job_id = body.data.job_id; // Save this ID before polling. Do not resubmit to check progress. ``` ## Step 4: Get the result Poll the job until `completed` or `failed`, stopping on HTTP errors. A client timeout does not cancel accepted work. Keep `?api_version=2026-09-26` on `/v1/jobs` (or send the header `Masko-API-Version: 2026-09-26`). Without it, this shared route returns older field names, such as `collection_id` instead of `mascot_id`. `data.poll_url` is a masko.ai path (`/api/v1/jobs/...`); with `https://api.masko.ai`, build the URL from the job ID: ```bash curl https://api.masko.ai/v1/jobs/JOB_ID?api_version=2026-09-26 \ -H "Authorization: Bearer $MASKO_API_KEY" # Response: # { # "data": { # "id": "JOB_ID", # "status": "completed", # "type": "item_generation", # "urls": { # "video": "https://...", # "webm": "https://..." # } # } # } ``` ```javascript async function pollJob(jobId) { const deadline = Date.now() + 10 * 60 * 1000; while (Date.now() < deadline) { const res = await fetch(`https://api.masko.ai/v1/jobs/${jobId}?api_version=2026-09-26`, { headers: { Authorization: `Bearer ${process.env.MASKO_API_KEY}` }, signal: AbortSignal.timeout(Math.min(30000, deadline - Date.now())) }); const body = await res.json(); if (!res.ok) throw new Error(body.error?.message || `HTTP ${res.status}`); const job = body.data; if (job.status === 'completed') return job.urls; if (job.status === 'failed') throw new Error(JSON.stringify(job.error)); await new Promise(resolve => setTimeout(resolve, 2000)); } throw new Error('Stopped waiting. Save the job ID and check its status later.'); } const urls = await pollJob(job_id); console.log(urls); ``` Play `webm` in Chrome, Firefox and Edge and the HEVC `.mov` on Apple devices ([hosting and file formats](/docs/generate/cdn)). URLs can exist before their file, so wait for `completed`. ## Next steps - [Choose a workflow](/docs/generation) - Find the guide for images, animations, video references, or design assets. - [Hosting and formats](/docs/generate/cdn) - Use completed files in your website or application. - [Webhooks](/docs/manage/webhooks) - Receive job completion events on your server. - [Canvas](/docs/canvas/build) - Build interactive state machines with your mascot. - [Build with your AI agent](/docs/ai-agents) - Let your coding agent make these calls for you. --- ---url: /docs/terminal--- Source: https://masko.ai/docs/terminal # Terminal quickstart The `masko` command signs in to your Masko account and calls the [Masko API](/docs/reference) for you: the same requests and responses as the REST API, with your browser login instead of an API key. It runs on Apple Silicon Macs with macOS 14 or later. It does not show mascots on screen; Masko for Mac does, see [Float a mascot on your Mac](/docs/integrations/desktop). Coding agents such as Claude Code, Codex and Cursor can also connect to the [Masko MCP server](/docs/mcp), which needs no install. ## Install ```bash curl -fsSL https://releases.masko.ai/cli/install.sh | sh ``` This installs the signed, notarized CLI at `~/.local/bin/masko`, adds that folder to your PATH in `~/.zshrc` (or `~/.bash_profile` for bash), and opens your browser to sign you in. Over SSH it shows a code to enter on any device instead, and Ctrl+C skips sign-in. Then **open a new terminal** before you run `masko`. Running the installer again updates the CLI and keeps your sign-in; in scripts and CI it never waits for a browser. To leave your shell profile alone, put `MASKO_NO_MODIFY_PATH=1` on the right side of the pipe and add `export PATH="$HOME/.local/bin:$PATH"` yourself. No Xcode, Node.js or checkout is needed. Intel Macs, Linux and Windows can use the [REST API](/docs/quickstart) or the [MCP server](/docs/mcp). The installer sends anonymous install reports, never account data. To turn them off, put the variable on the right side of the pipe: ```bash curl -fsSL https://releases.masko.ai/cli/install.sh | MASKO_TELEMETRY_DISABLED=1 sh ``` That choice is kept for later installs. `DO_NOT_TRACK=1` works too. The `masko` command itself sends no analytics. ## Sign in and check your account ```bash masko login masko whoami masko credits ``` The installer already signed you in. `masko login` signs in again, for example to switch account or workspace: it prints a link, opens your browser and returns after you approve; choose your account and workspace there. Over SSH or on another computer, use `masko login --device --no-browser`. See [CLI login](/docs/authentication/cli). `masko logout` signs out on this Mac. ## Make a mascot `masko studio METHOD PATH` calls any Masko API path. `--file` sends a JSON body, or an image or video to `upload`. ```bash masko studio GET projects masko studio POST upload --file character.png masko studio POST mascots --file mascot.json masko studio POST mascots/MASCOT_ID/generate --file pose.json masko studio GET 'jobs/JOB_ID?wait=true' masko assets download ASSET_ID --output mascot.webm ``` `mascot.json` can be `{"project_id": "PROJECT_ID", "name": "Ko", "reference_asset_ids": ["ASSET_ID"]}`, with a project from the first command and the `asset_id` that upload returned. `pose.json` can be `{"type": "image", "name": "Wave", "prompt": "waving hello"}`, or `{"type": "animation", "item_id": "ITEM_ID", "animation_model": "standard", "animation_prompt": "waves hello"}` to animate a pose. The [quickstart](/docs/quickstart) lists every field. Generation spends [credits](/docs/credits): an image is 1 credit and a Standard animation 2 credits a second. Name the model: without `animation_model`, the older price of 5 credits a second applies. A mascot made from a `prompt` draws its first image for 1 credit and is hosted on the public CDN unless `settings.cdn_enabled` is `false`. The output is the API's own response: `data`, plus `meta.pagination` for lists. When `has_more` is true, ask for the next page with `next_cursor`: ```bash masko studio GET 'assets?mascot_id=MASCOT_ID&limit=100' masko studio GET 'assets?mascot_id=MASCOT_ID&limit=100&cursor=NEXT_CURSOR' ``` Paths are the API's `/v1` paths, such as `mascots`, `canvases/CANVAS_ID` or `credit-checkouts`; a job's `poll_url` works as it is. Sign-in, admin and the old `collections` paths are not available here. Requests use API version `2026-09-26`, like the MCP server. ## Retry without paying twice Every POST sends an idempotency key. When a POST fails without an answer, or you press Control+C, `masko` prints its key. Run the same command with that key, and Masko returns the first result instead of running or charging it again: ```bash masko studio POST mascots/MASCOT_ID/generate --file pose.json --idempotency-key KEY ``` ## Errors Errors print the API's code and message, the field that was wrong and a request ID to give support. With `--json` they are JSON on stdout, `{"error": {"code", "message", "status", "request_id", "doc_url", ...}}`, and the exit code is 1. A 402 says how many credits are needed and where to buy more, on [Billing](/billing). See [errors](/docs/reference/errors). ## Update or choose a version Run the install command again for the latest version; your sign-in is kept. Once a day, `masko` also says when a newer version is out (`MASKO_NO_UPDATE_CHECK=1` turns that off). To install a specific version, use its number from the [changelog](/docs/reference/changelog): ```bash curl -fsSL https://releases.masko.ai/cli/install.sh | MASKO_VERSION=0.4.0 sh masko --version ``` ## Next steps - [Command line reference](/docs/terminal/commands): every command and option. - [Quickstart](/docs/quickstart): the API requests to make a mascot and its animations. - [TypeScript SDK](/docs/sdk/typescript): the same calls from Node.js or Electron main. - [Masko MCP server](/docs/mcp): connect Claude Code, Codex, Cursor, Claude or ChatGPT. --- ---url: /docs/authentication--- Source: https://masko.ai/docs/authentication # API keys and workspaces Send a bearer credential with protected REST requests: an API key for a backend or script, or [browser login](/docs/authentication/cli) for the Masko CLI (`masko login`). ## Create an API key Create a key in [API keys](/api-keys) for your workspace. **Write** access can generate and edit; **read** allows GET only. Keys look like `masko_{64-hex-characters}` and are shown once (Masko keeps a SHA-256 hash). Save yours as `MASKO_API_KEY` in your server environment or secret manager. ## Check your key A balance request is free: ```bash curl https://api.masko.ai/v1/credits \ -H "Authorization: Bearer $MASKO_API_KEY" ``` ```javascript const response = await fetch('https://api.masko.ai/v1/credits', { headers: { Authorization: `Bearer ${process.env.MASKO_API_KEY}` } }); const body = await response.json(); if (!response.ok) throw new Error(body.error?.message || `HTTP ${response.status}`); console.log(body.data.total); ``` ### Personal and organization keys - **Personal keys** use personal projects and credits. - **Organization keys** use team projects and the team's credits. Team owners and admins create them; project restrictions still apply. Switch workspace in the dashboard before creating a key; a resource in another workspace returns `404`, even if you belong to both. Unattached uploads, user templates and webhooks stay account-owned, while source assets attached to a mascot must pass its workspace checks. ## Keys in shipped apps and websites Keep the key on your server, never in browser JavaScript, a shipped app or a public repository. Public files use hosted [CDN URLs](/docs/generate/cdn); for a private canvas release, your backend fetches its signed delivery and passes only that to the client ([deliver a release](/docs/player/canvas)). ## Error codes Missing or invalid credentials return `401`, insufficient permissions `403`, inaccessible resources `404`, and rate limits `429`. See [errors and recovery](/docs/reference/errors) for backoff and safe retries, then continue with the [quickstart](/docs/quickstart). --- ---url: /docs/authentication/cli--- Source: https://masko.ai/docs/authentication/cli # CLI browser login Sign the Masko CLI in through your browser instead of pasting an API key. Install it with the [terminal quickstart](/docs/terminal); server integrations use [API keys](/docs/authentication). ## Sign in from the terminal ```bash masko login # Open browser, approve, return automatically masko login --no-browser # Open the printed URL yourself on this computer masko login --device --no-browser # Device code for SSH or a remote terminal masko login --scope read # Request read-only access masko whoami # Account, workspace, access, credits masko logout # End this CLI session ``` Sign in on Masko, choose the workspace and click **Connect**; the CLI completes sign-in with PKCE (Control+C cancels). Without server support for browser sign-in, it shows an error and never falls back to API-key login. - Team authorization requires an owner or admin. - Access tokens last 15 minutes; refresh tokens rotate automatically and are stored in macOS Keychain. - Sessions last 90 days. See them, and sign one out, under Signed-in computers in [Developers → API keys](/api-keys). ### Choosing a workspace Your choice (**Personal workspace** or a team) sets which projects, mascots, canvases, assets and jobs the session reaches and which balance pays. Run `masko login` again to switch; membership and project restrictions still apply. IDs from another workspace return `404`, as with API keys, while public mascot releases stay public. Unattached uploads, user templates, creator profiles and listings, and webhook registrations stay account-owned; source assets for mascot creation, references and template extraction must pass workspace checks. An older CLI that gets `410` at login needs an update; API keys stay valid. `MASKO_API_KEY` overrides a saved session, and an invalid key fails rather than switching accounts. Save a key with `masko login --api-key` (asks for it) or `--api-key-stdin`. `masko logout` removes the login on this Mac even when Masko cannot be reached; the session then stays valid until you sign it out under Signed-in computers. ### Browser authorization (PKCE) `GET /v1/auth/config` returns the `authorization_endpoint` and supported grants. The client `masko-cli` opens it with: - `client_id=masko-cli`, `response_type=code`, `device_name`, and `scope=read|write`. - `redirect_uri=http://127.0.0.1:PORT/oauth/callback`, using a temporary local port between 1024 and 65535. Other hosts, paths, query strings and fragments are rejected. - Independent random `state` and `code_verifier` values generated from 32 random bytes, base64url encoded. Send only `state`, `code_challenge=BASE64URL(SHA256(code_verifier))`, and `code_challenge_method=S256` in the browser URL. Keep the verifier in memory. After approval, check the exact callback path and `state`, then send `client_id`, `grant_type: "authorization_code"`, `code`, the same `redirect_uri` and `code_verifier` as JSON to `POST /v1/auth/token`. Codes expire after 120 seconds and work once. The callback never carries a token or API key. Cancelling returns `error=access_denied` with the original state; the CLI closes its listener on completion, cancellation or timeout (10 minutes at most). ### Device-login REST contract For `masko login --device`, when the browser runs on another computer: - `POST /v1/auth/device`: `{ "client_id": "masko-cli", "device_name": "My Mac", "scope": "write" }`. Returns `device_code`, `user_code`, `verification_uri`, `verification_uri_complete`, `expires_in` (600 seconds), and `interval` (5 seconds) under `data`. - `POST /v1/auth/token`: send `client_id`, `grant_type: "urn:ietf:params:oauth:grant-type:device_code"`, and `device_code`. Poll at the returned interval: keep waiting on `authorization_pending`, add 5 seconds to all later intervals on `slow_down`, and stop on `access_denied`, `expired_token` or `invalid_grant`. - On success, `data` contains `access_token`, `refresh_token`, `token_type: "Bearer"`, `expires_in`, `session_id`, and `session_expires_at`. Never log these tokens. - Refresh at the same endpoint with `client_id`, `grant_type: "refresh_token"` and `refresh_token`, one refresh at a time, replacing the pair securely. Reusing a consumed refresh token revokes the session; if a refresh response is lost, log in again. - `GET /v1/auth/session` identifies the current key or session without secrets. - `POST /v1/auth/revoke` with `{ "refresh_token": "..." }` ends only that session, immediately and idempotently. These endpoints use Masko's JSON bodies and `{data}` / `{error}` envelopes, not a general third-party OAuth server or form-encoded token endpoint. All auth responses are `no-store`, and `masko-cli` is the only registered client. --- ---url: /docs/how-mascots-work--- Source: https://masko.ai/docs/how-mascots-work # How mascots work How projects, mascots, items and assets fit together in the Masko API. ## The hierarchy Projects own mascots and canvases. A mascot holds items (poses or actions), and items hold assets (files); canvases use mascot assets without belonging to a mascot. ```text Project └── Mascot (identity, variants and assets) ├── Item: "Wave" │ ├── Asset: image (pose.png) │ ├── Asset: transparent_image (pose_nobg.png) │ ├── Asset: video (wave.mp4) │ ├── Asset: webm (wave.webm) │ └── Asset: hevc (wave.mov) ├── Item: "Idle" │ └── ... └── Item: "Thumbs Up" └── ... ``` ## Mascots and variants `/v1/mascots` holds identity, references and generation settings. Each mascot has an Original context and optional named variants; shared approved context is read-only, so duplicate it for your own variant. New poses use the current context, and existing media never changes. ## Items An item is one pose or action, such as "Wave", with a name, a prompt and a type (`image`, `animation` or `logo`). Its prompt is combined with the mascot's character prompt. ## Assets An asset is one generated file: | Type | Format | Description | | --- | --- | --- | | `image` | .png | Generated image with background | | `transparent_image` | .png | Background removed | | `video` | .mp4 | Animation (H.264) | | `webm` | .webm | Animation with alpha, for the web | | `hevc` | .mov | Animation with alpha, for Apple | | `stacked_video` | .mp4 | Color and alpha stacked, for custom compositing | | `stacked_video_av1` | .mp4 | The same layout in AV1, lighter, for the Masko player in apps | | `audio` | .mp3 | Voice of a [talking animation](/docs/generate/talking) | | `transcript` | .json | Timed words, lines and movements of a talking animation | ## Generation graph Each file links to the file it was made from: ```text image (.png) └── transparent_image (.png) [role: source] └── video (.mp4) [role: source, end_frame] ├── webm (.webm) [role: source] └── hevc (.mov) [role: source] ``` A link's `role` is `source` (derived from that asset) or `end_frame` (the final pose guiding an animation). ## Reference images and style cards Up to 6 reference images guide every generation. On the first generation, Masko extracts from them a style card (colors, proportions, line style, shading) that joins every prompt; changing references clears it until the next generation. A configured `caution_list` adds consistency instructions to prompts but does not validate results. ## Size variants Set `settings.animation_sizes` at creation, or `publish_params.animation_sizes` later, to get each animation at up to two smaller sizes such as `[480, 240]`, for free. Every animation already has its 720 px files, so 720 and larger sizes are ignored. ## Canvases, releases and publication A canvas belongs to a project and is edited with `/v1/canvases`. Checkpoints keep unfinished work and releases freeze complete graphs with their media, without generating media, publishing a library or activating a mascot on a Mac ([canvases and releases](/docs/canvas)). Marketplace libraries are published separately from canvas releases ([publish to the Marketplace](/docs/manage/collections#publish-to-the-marketplace)). --- ---url: /docs/credits--- Source: https://masko.ai/docs/credits # Credits and costs See what each generation costs, check your balance and buy credits. ## What generation costs | Operation | Credits | | --- | --- | | One image, image edit, or preview | 1 per generated image | | [Animation](/docs/generate/animations), Standard | 2 per second (2 to 15 seconds) | | [Animation](/docs/generate/animations), Premium | 6 per second (4 to 30 seconds) | | Source image for an animation | 1 when a new image is needed | | [From Video](/docs/generate/animations#from-video), Premium only | 6 per second (4 to 30 seconds), plus 1 for the pose image | | [Talking animation](/docs/generate/talking) | 2 per second (120 a minute), up to 5 minutes, settled once the voice is recorded | | Talking estimate (`dry_run: true`) | 0 | | Three voice samples | 1 | | [Logo](/docs/generate/design-assets) | 5 | | [SVG from a logo or image](/docs/generate/design-assets#generate-an-svg-from-a-logo) | 30 | | Background | 3 | | Sticker from an image | 1 | | Animated sticker from an animation | 0 | | [Cursor follower](/docs/generate/cursor-follower) (nine directions) | 9 | | Reversing an existing video | 0 | Always send `animation_model` (`"standard"` or `"premium"`). A request without it uses the older price of 5 credits per second, 4 to 10 seconds. No credits for background removal, format conversion, resizing, uploads, reading metadata, analyzing an image or website, or reviewing a generation plan. Preview images are paid even though their URLs expire. ## What credits cost Bigger purchases give more credits per dollar, so a minute of talking costs $18 with the smallest pack and $13.33 from $1,000. Any amount from $10 works in [Billing](/billing). | Purchase | Credits | Talking it buys | Price per minute of talking | | --- | --- | --- | --- | | $15 | 100 | 50 seconds | $18.00 | | $50 | 400 | 3 min 20 s | $15.00 | | $200 | 1,700 | 14 minutes | $14.12 | | $1,000 | 9,000 | 75 minutes | $13.33 | Amounts below $50 give 6⅔ credits per dollar, $50 to $199.99 give 8, $200 to $999.99 give 8.5, and $1,000 or more give 9. ## Check your balance Run `masko credits` (or `--json`), or await `Masko.client().credits()` in the [TypeScript SDK](/docs/sdk/typescript). `masko whoami` shows the account and workspace it belongs to. ```bash curl https://api.masko.ai/v1/credits \ -H "Authorization: Bearer $MASKO_API_KEY" ``` ```json { "data": { "subscription": 450, "topup": 100, "total": 550 } } ``` Subscription credits are spent before top-up credits. Buy credits and see terms in [Billing](/billing); asset hosting is billed separately. ## Buy credits from an API or agent Create a Stripe-hosted checkout page for your credential's workspace; it never charges a saved card or buys automatically. Team purchases require the active team owner. ```bash curl https://api.masko.ai/v1/credit-checkouts \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: credit-purchase-001" \ -d '{"amount_cents":2000}' ``` - `amount_cents`: whole USD cents from 1000 to 1000000 ($10 to $10,000). Send no organization ID, price, credit quantity, payment method or return URL. - The response has `id`, `url`, `credits`, `amount_cents`, `currency`, `organization_id`, `expires_at` and `status_url`. Open `url` to confirm the final amount on Stripe; never give card details to an agent. - After a timeout, reuse the same `Idempotency-Key` and body (replays last 24 hours). If the URL expired, check status before buying again. ```bash curl https://api.masko.ai/v1/credit-checkouts/CHECKOUT_ID \ -H "Authorization: Bearer $MASKO_API_KEY" ``` Only its creator can read a checkout, from the same workspace. A paid checkout with `credits_added: false` is still delivering credits, so check again rather than paying twice; `balance` is the workspace balance, not a receipt. Only Stripe's verified webhook grants credits, once; reading status never does. Connected agents get `create_credit_checkout` (`amount_cents` and a stable UUID `operation_id`) and `get_credit_checkout` (`id`). Create a checkout only when the user asks to buy credits; it is a hosted link, not an automatic payment. ChatGPT does not get these tools: it spends the credits already in the workspace you connected, and you buy credits in [Billing](/billing). ## Approve a plan For [canvas generation](/docs/canvas/generate-all), dry-run first, then submit the same selection, approved plan ID and graph hash, so a changed graph cannot silently cost more. Credits are allocated when work is accepted, and a client timeout does not prove failure. Check the job or run before retrying, and use persisted idempotency keys where supported; `/generate` has no run-level replay. ## Insufficient credits A rejected request returns HTTP `402`: ```json { "error": { "code": "insufficient_credits", "message": "Insufficient credits", "details": { "required": 21, "balance": 5 } } } ``` Rely on `error.code`; messages and optional details differ by endpoint. ## Failures and refunds A partly failed run can still hold successful paid assets. Runs report `allocated_credits`, `credits_refunded` and `refund_status` where available; a missing refund value promises nothing. If the source video succeeded but transparent formats failed, use media repair instead of a new paid generation ([generation progress and repair](/docs/canvas/generate-all#poll-progress)). --- ---url: /docs/create/from-text--- Source: https://masko.ai/docs/create/from-text # Create a mascot from text Describe your mascot and pick a style; the API draws its reference image. ## Two Approaches Preview options first (recommended, 1 credit per preview), or create the mascot in one call (1 credit for its reference image). If direct creation fails, its credit is refunded, with background retries when needed. Retry with the same `Idempotency-Key` and request: completed errors replay too, so a retry never charges again. ## Step 1: Browse Styles GET /v1/styles Each style has an `id` to pass as `preset_id`. ```bash curl https://api.masko.ai/v1/styles \ -H "Authorization: Bearer masko_YOUR_API_KEY" ``` ```javascript const res = await fetch('https://api.masko.ai/v1/styles', { headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY' } }); const { data: styles } = await res.json(); ``` ```json { "data": [ { "id": "STYLE_ID", "name": "3D Render", "preview_url": "..." }, { "id": "pixel-art", "name": "Pixel Art", "preview_url": "..." }, { "id": "flat-vector", "name": "Flat Vector", "preview_url": "..." } ] } ``` ## Step 2: Generate Previews POST /v1/generate/preview Generate several images from your description and pick one before creating the mascot. ```bash curl -X POST https://api.masko.ai/v1/generate/preview \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "A friendly orange fox wearing a space helmet", "preset_id": "STYLE_ID", "count": 4 }' ``` ```javascript const res = await fetch('https://api.masko.ai/v1/generate/preview', { method: 'POST', headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: 'A friendly orange fox wearing a space helmet', preset_id: 'STYLE_ID', count: 4 }) }); const { data } = await res.json(); const images = data.images; ``` ```json { "data": { "images": [ { "url": "https://...", "expires_in": 3600 }, { "url": "https://...", "expires_in": 3600 } ], "cost": 2 } } ``` ## Step 3: Create Mascot POST /v1/mascots Use the preview URL you like as a reference image. ```bash curl -X POST https://api.masko.ai/v1/mascots \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Astro Fox", "project_id": "PROJECT_ID", "prompt": "A friendly orange fox wearing a space helmet", "reference_image_urls": [ "URL_FROM_PREVIEW_RESPONSE" ] }' ``` ```javascript const res = await fetch('https://api.masko.ai/v1/mascots', { method: 'POST', headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Astro Fox', project_id: 'PROJECT_ID', prompt: 'A friendly orange fox wearing a space helmet', reference_image_urls: [images[0].url] }) }); const { data: mascot } = await res.json(); console.log(mascot.id); // Use this for generation ``` ```json { "data": { "id": "3abe65fd-1d5c-5348-8e16-357a86d97ec6", "name": "Astro Fox", "slug": "astro-fox-a1b2c3d4", "type": "mascot", "reference_asset_ids": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"], "settings": { "cdn_enabled": true, "animation_sizes": [] } } } ``` Hosting is on by default for new mascots. Set up to two numeric `animation_sizes` below 720 at creation, such as `[480, 240]`, to choose which smaller sizes to generate. ## Shortcut: Direct Creation Skip previews: the API generates a reference image from your prompt for 1 credit. `style` optionally applies a preset. ```bash curl -X POST https://api.masko.ai/v1/mascots \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Astro Fox", "project_id": "PROJECT_ID", "prompt": "A friendly orange fox wearing a space helmet", "style": "3d" }' ``` ```javascript const res = await fetch('https://api.masko.ai/v1/mascots', { method: 'POST', headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Astro Fox', project_id: 'PROJECT_ID', prompt: 'A friendly orange fox wearing a space helmet', style: '3d' }) }); ``` ### Design touches `design_touches` adds the Studio creator's design ideas after your prompt, only where they fit: a touch never adds ears, limbs or a mouth the character lacks, and your prompt wins where they differ. Touches need `prompt` and no references, and cost nothing extra. | Group | Touch IDs | | --- | --- | | Shape | `proportions` (big head, small body), `simple`, `asymmetry` (a little lopsided), `grounded`, `few-colors` | | Face | `no-mouth`, `looks-at-you`, `blush`, and at most one of `eyes-big` or `eyes-dots` | | Character | `signature`, `weird` (one weird thing), `twist`, `grown-up-flaw`, `magic-part`, and at most one mood: `mood-cozy`, `mood-mischievous`, `mood-melancholy`, `mood-bold`, `mood-mysterious`, `mood-chaotic` or `mood-serene` | ```bash curl -X POST https://api.masko.ai/v1/mascots \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Ember", "project_id": "PROJECT_ID", "prompt": "a dragon", "design_touches": ["proportions", "eyes-big", "mood-cozy"] }' ``` ```javascript const res = await fetch('https://api.masko.ai/v1/mascots', { method: 'POST', headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Ember', project_id: 'PROJECT_ID', prompt: 'a dragon', design_touches: ['proportions', 'eyes-big', 'mood-cozy'] }) }); ``` ## Next Steps - [Generate Images](/docs/generate/images) - Create poses and actions for your mascot. - [Generate Animations](/docs/generate/animations) - Bring your mascot to life with animated sequences. --- ---url: /docs/create/from-image--- Source: https://masko.ai/docs/create/from-image # Create a mascot from an image Upload your existing mascot design; the API detects the character's name and description from it. ## Upload Your Image POST /v1/upload Upload multipart form data or a URL. Multipart needs a binary `file` field; let your HTTP client set the boundary, since a malformed body or missing file returns `400`. ### Multipart Upload ```bash curl -X POST https://api.masko.ai/v1/upload \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -F "file=@mascot.png" ``` ```javascript const formData = new FormData(); formData.append('file', fileBlob, 'mascot.png'); const res = await fetch('https://api.masko.ai/v1/upload', { method: 'POST', headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY' }, body: formData }); const { data } = await res.json(); const asset_id = data.asset_id; ``` ### URL Upload Public HTTP(S) PNG, JPEG, WebP, GIF or SVG (converted to PNG), up to 10 MB, downloaded within 15 seconds. Every redirect must stay public; private addresses and URLs with credentials are rejected. Reference image URLs follow the same rules. ```bash curl -X POST https://api.masko.ai/v1/upload \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/my-mascot.png" }' ``` ```javascript const res = await fetch('https://api.masko.ai/v1/upload', { method: 'POST', headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ url: 'https://example.com/my-mascot.png' }) }); const { data } = await res.json(); const asset_id = data.asset_id; ``` ```json { "data": { "asset_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" } } ``` ## Create Mascot POST /v1/mascots Pass the upload's `asset_id` as a reference. `prompt` and `name` are optional: both are detected from the image. ```bash curl -X POST https://api.masko.ai/v1/mascots \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "project_id": "PROJECT_ID", "reference_asset_ids": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"] }' ``` ```javascript const res = await fetch('https://api.masko.ai/v1/mascots', { method: 'POST', headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ project_id: 'PROJECT_ID', reference_asset_ids: [asset_id] }) }); const { data: mascot } = await res.json(); // mascot.name is auto-detected from the image // mascot.reference_asset_ids contains the linked reference ``` `reference_image_urls` takes public URLs without uploading first. ## How Auto-Analysis Works AI vision reads the reference and writes the character's name and a detailed prompt of its visual traits, for free. ## Adding More References POST /v1/mascots/:id/references Up to 6 references; more give better consistency. Send an `asset_id`, or a `url` for a public image. ```bash curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/references \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "asset_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }' ``` ```javascript await fetch(`https://api.masko.ai/v1/mascots/${mascotId}/references`, { method: 'POST', headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ asset_id: assetId // from POST /upload }) }); ``` Changing references clears the style card, which is extracted again on the next generation. ## Best Practices - 3 or 4 references from different angles. - One art style; do not mix 3D and flat vector. - 1024 × 1024 or larger. - White or transparent backgrounds. ## Animate the uploaded image directly For a new mascot, [upload and animate](/docs/generate/animations#upload-and-animate-in-one-request) creates it in the animation request. For an existing mascot, send the unattached upload to `/generate`: ```bash curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/generate \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Idempotency-Key: animate-my-upload-001" \ -H "Content-Type: application/json" \ -d '{ "type": "animation", "name": "Gentle wave", "source_image_asset_id": "UPLOADED_IMAGE_ASSET_ID", "animation_prompt": "Wave gently, keeping the feet planted.", "animation_model": "standard", "duration": 5, "loop": true }' ``` It returns `202` with a job to poll at `/v1/jobs/{job_id}?api_version=2026-09-26`: 10 credits for 5 seconds of Standard, with no image charge. - The upload is kept; non-square images are center-cropped to a separate 1024 × 1024 source, or use `source_image_crop` ([image framing](/docs/generate/animations#image-framing)). The video model can still change details. - It is imported as a source image, not a reference, and does not change the mascot's identity. - An approved `variant_id` ties a fresh upload to that variant. Existing images keep their saved context, and a conflicting variant returns `409`. - Retry an uncertain response with the same idempotency key and request to avoid a second charge. --- ---url: /docs/create/from-website--- Source: https://masko.ai/docs/create/from-website # Create a mascot from a website Analyze a website to get mascot ideas that match its brand. ## Analyze a URL POST /v1/analyze Send the URL with `type: "url"`. The API screenshots the page and returns its metadata, a brand summary and any suggestions, without base64 payloads, for free. ```bash curl -X POST https://api.masko.ai/v1/analyze \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "url", "url": "https://example.com" }' ``` ```javascript const res = await fetch('https://api.masko.ai/v1/analyze', { method: 'POST', headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ type: 'url', url: 'https://example.com' }) }); const { data } = await res.json(); const { markdown, metadata, description, suggestions } = data; ``` ```json { "data": { "description": "Developer tools for example teams", "metadata": { "title": "Example Corp" }, "markdown": "# Example Corp\n\nDeveloper tools...", "suggestions": [ { "name": "Codey", "prompt": "A friendly blue robot with rounded features..." } ] } } ``` Send `type: "image"` with `image_url` to get a name and character description from an existing mascot image. ## Use Suggestions Pass a suggestion's prompt to `POST /v1/mascots` to create the mascot in one call. ```bash curl -X POST https://api.masko.ai/v1/mascots \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Codey", "project_id": "PROJECT_ID", "prompt": "A friendly blue robot with rounded features..." }' ``` ```javascript const suggestion = suggestions?.[0]; if (!suggestion) throw new Error('No suggestion returned. Create from text instead.'); const res = await fetch('https://api.masko.ai/v1/mascots', { method: 'POST', headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ name: suggestion.name, project_id: 'PROJECT_ID', prompt: suggestion.prompt }) }); ``` ## When to Use This - **From website**: you have a brand but no mascot idea yet. - **[From text](/docs/create/from-text)**: you know the character you want. - **[From image](/docs/create/from-image)**: you already have artwork. --- ---url: /docs/create/from-brand-book--- Source: https://masko.ai/docs/create/from-brand-book # Create a mascot from a brand book Send your brand book and get a mascot back: every view as a reference, and its description, colors, style, don'ts and props in its context. ## Create the mascot POST /v1/mascots/from-brand-book Send the files in reading order, as public links or `POST /v1/upload` assets: - a PDF up to 50 MB; - PNG, JPEG or WebP pages up to 10 MB each; - at most 30 pages in total. ```bash curl -X POST https://api.masko.ai/v1/mascots/from-brand-book \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "project_id": "PROJECT_ID", "files": [{ "url": "https://example.com/brand-book.pdf" }], "max_credits": 17 }' ``` ```javascript const res = await fetch('https://api.masko.ai/v1/mascots/from-brand-book', { method: 'POST', headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ project_id: 'PROJECT_ID', files: [{ url: 'https://example.com/brand-book.pdf' }], max_credits: 17 }) }); const { data } = await res.json(); ``` ```json { "data": { "job_id": "JOB_ID", "status": "pending", "type": "mascot_brand_book", "max_cost_credits": 17, "poll_url": "/v1/jobs/JOB_ID" } } ``` | Field | Required | Description | | --- | --- | --- | | `project_id` | Yes | Project the mascot is created in. | | `files` | Yes | Each file is `{ "url": "..." }` or `{ "asset_id": "..." }`, with an optional `name`. | | `name` | No | Mascot name. By default, the name written in the brand book. | | `max_credits` | No | Spending cap, from 1 to 17. | ## Send a PDF from your computer Ask for a signed upload URL, upload the file straight to storage, then pass its `asset_id`: ```bash curl -X POST https://api.masko.ai/v1/upload \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "content_type": "application/pdf", "size": 31759881, "file_name": "brand-book.pdf" }' # PUT the file with the returned upload_headers before expires_at (15 minutes) curl -X PUT "UPLOAD_URL" \ -H "Content-Type: application/pdf" \ -H "x-goog-content-length-range: 0,52428800" \ --data-binary @brand-book.pdf ``` ```json { "data": { "asset_id": "ASSET_ID", "upload_url": "https://storage.googleapis.com/...", "upload_method": "PUT", "upload_headers": { "Content-Type": "application/pdf", "x-goog-content-length-range": "0,52428800" }, "expires_at": "2026-10-09T12:15:00.000Z" } } ``` Then send `"files": [{ "asset_id": "ASSET_ID" }]`. Storage refuses files above 50 MB, and calling before the upload finishes returns `400`. ## Wait for the mascot Poll the job, which takes about 2 to 4 minutes. `step` goes `pages`, `read` (free), `generate` (views and props drawn), then `mascot` (context filled). ```bash curl "https://api.masko.ai/v1/jobs/JOB_ID?wait=true&timeout=120" \ -H "Authorization: Bearer masko_YOUR_API_KEY" ``` ```json { "data": { "id": "JOB_ID", "status": "completed", "type": "mascot_brand_book", "step": "mascot", "cost_credits": 13, "result": { "mascot_id": "MASCOT_ID", "mascot_name": "Teddie", "references": 4, "props": 9, "credits_charged": 13 } } } ``` Use `result.mascot_id` with any mascot endpoint. A prop appears in a new image only when the prompt asks for it. ## What it costs Reading is free. Each view and prop found is drawn for 1 credit, at most 5 views and 12 props (17 credits); failed images are refunded. The job stops after reading, with no charge, if there is no mascot, the cost exceeds `max_credits` or your balance is too low; its `error` gives the reason and price. Each account can read 15 brand books per day. ## Tips - Links must be public: a signed storage URL works, a page behind a login does not. - Page images can be uploaded one by one and sent as `asset_id`s in reading order. - An `Idempotency-Key` header lets you retry without starting a second job. --- ---url: /docs/manage/references--- Source: https://masko.ai/docs/manage/references # Reference images and style consistency Keep a mascot on-model in every pose with persistent reference images and a style card. Several fields accept images, each for a different purpose. URL imports, including reference URLs: - Public HTTP(S) images up to 10 MB, downloaded within 15 seconds. - Every redirect must resolve to a public address. Private addresses and URLs with credentials are rejected. - PNG, JPEG, WebP, GIF and SVG (converted to PNG). ## Which image field to use | Where | Persistence | Use case | | --- | --- | --- | | `POST /v1/mascots/:id/references` (body: `asset_id` or `url`) | Persistent | Style anchors for every generation in this mascot. Max 6. | | `reference_image_urls` / `reference_asset_ids` on `POST /v1/mascots` | Persistent (seeded at creation) | Same as above, set when creating the mascot. An image that already belongs to another mascot is copied: that mascot keeps it. | | `PreviewBody.reference_image_urls` on `/v1/generate/preview` | Ephemeral (one-off) | Style hints for a single preview, not stored. | | `visual_references` on image generation or image editing | One request | Up to 4 pose, expression, motion, prop, style, or scene hints, each with `asset_id` or `url`, optional `role` and `note`. Does not change the character references. | | `GenerateBody.source_image_asset_id` on `/v1/mascots/:id/generate` | N/A | The image to animate or edit. NOT a style reference. | ## Removing a reference DELETE /v1/mascots/:id/references/:assetId ```bash curl -X DELETE https://api.masko.ai/v1/mascots/MASCOT_ID/references/ASSET_ID \ -H "Authorization: Bearer masko_YOUR_API_KEY" ``` ## How the style card works On a mascot's first generation, Masko extracts a **style card** from the references: a short description of the palette, proportions, outline style and other defining traits. It is cached on the mascot config and sent with the reference images in every prompt. Adding or removing a reference clears it, and the next generation extracts it again. ## Common mistakes - **Using a preview as a source.** Previews from `/v1/generate/preview` are not persisted and have no asset ID. [Generate the pose](/docs/generate/images) with `POST /v1/mascots/:id/generate`, then use `asset_ids.image`. - **Passing a `transparent_image` ID as `source_image_asset_id`.** The source must be the full `image` asset (`asset_ids.image`), not the background-removed one. - **Confusing the source with references.** `source_image_asset_id` is the exact pose to [animate](/docs/generate/animations) or edit; references define what the character looks like. --- ---url: /docs/generation--- Source: https://masko.ai/docs/generation # What you can generate Every asset below keeps your mascot's look. Most come from `POST /v1/mascots/:id/generate`, which returns `202` with `data.job_id` and `data.poll_url` and runs in the background. | Task | Guide | Cost | | --- | --- | --- | | Pose image or image edit | [Images and edits](/docs/generate/images) | 1 credit | | Animation | [Animations](/docs/generate/animations) | Standard: 2 credits/sec; Premium: 6 credits/sec; +1 if a new source image is needed | | Motion from a video | [From Video](/docs/generate/animations#from-video) | Premium: 6 credits/sec + 1 image credit | | Talking animation with a voice | [Talking animations](/docs/generate/talking) | 2 credits/sec, up to 5 minutes; free estimate | | Logo or app icon | [Logos, backgrounds and stickers](/docs/generate/design-assets) | 5 credits | | SVG from a logo or image | [Generate an SVG](/docs/generate/design-assets#generate-an-svg-from-a-logo) | 30 credits | | Background | [Logos, backgrounds and stickers](/docs/generate/design-assets#background) | 3 credits | | Sticker | [Logos, backgrounds and stickers](/docs/generate/design-assets#sticker) | 1 credit; 0 for an animated sticker | | Cursor follower | [Cursor follower](/docs/generate/cursor-follower) | 9 credits | | Multiple requests | [Batch generation](/docs/generate/batch) | Sum of accepted requests | | Character preview | [Create from text](/docs/create/from-text) | 1 credit per generated preview | Always send `animation_model` on animation requests; without it, animations bill at the older 5 credits per second ([credits and costs](/docs/credits)). Start with the [quickstart](/docs/quickstart). Find request fields in the [endpoint reference](/docs/reference), and collect results with [jobs and polling](/docs/manage/jobs) and [hosting and file formats](/docs/generate/cdn). --- ---url: /docs/generate/workflow--- Source: https://masko.ai/docs/generate/workflow # Pose-to-animation walkthrough Create a mascot with references, generate a pose, check it, then animate that exact pose, so you see the pose before paying for the video. Each step feeds IDs into the next. ## Step 1: Create a project and mascot Create the project, then a mascot seeded with `reference_image_urls`, so its style card is extracted on the first generation. POST /v1/projects ```bash curl -X POST https://api.masko.ai/v1/projects \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "My SaaS" }' ``` ```json { "data": { "id": "75270a54-f2f9-58e5-83c0-8575c720ca86", "name": "My SaaS", "created_at": "2026-04-18T08:00:00Z" } } ``` POST /v1/mascots ```bash curl -X POST https://api.masko.ai/v1/mascots \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "project_id": "75270a54-f2f9-58e5-83c0-8575c720ca86", "name": "Felix the Fox", "context": "Friendly brand mascot for our SaaS", "reference_image_urls": [ "https://example.com/fox-front.png", "https://example.com/fox-side.png" ] }' ``` ```json { "data": { "id": "8bf14263-eabc-58a4-80f6-be9e4f7bceeb", "name": "Felix the Fox", "slug": "felix-the-fox-a1b2c3d4", "type": "mascot", "reference_asset_ids": ["ast_ref_001", "ast_ref_002"], "settings": { "cdn_enabled": true, "animation_sizes": [] } } } ``` ## Step 2: Add additional references (optional) A mascot can have up to 6 references. Adding or removing one clears the style card, which the next generation extracts again. POST /v1/mascots/:id/references ```bash curl -X POST https://api.masko.ai/v1/mascots/8bf14263-eabc-58a4-80f6-be9e4f7bceeb/references \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/fox-back.png" }' ``` ```json { "data": { "reference_asset_ids": [ "ast_ref_001", "ast_ref_002", "ast_ref_003" ] } } ``` ## Step 3: Generate a pose image Generate the pose you want to animate, and keep `asset_ids.image` for step 4. POST /v1/mascots/:id/generate ```bash curl -X POST https://api.masko.ai/v1/mascots/8bf14263-eabc-58a4-80f6-be9e4f7bceeb/generate \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "image", "name": "Waving hello", "image_prompt": "waving hello with a friendly smile, one arm raised" }' ``` ```json { "data": { "job_id": "0818df65-d092-5901-a506-3b2e64fb88ef", "status": "pending", "type": "image", "item_id": "b3bbee96-0599-5ca2-ab23-e7b157f50091", "item_name": "Waving hello", "estimated_cost": 1, "asset_ids": { "image": "3c8ce568-35ce-59c2-b3f8-9fb7e4a16e52", "transparent_image": "74568ced-6281-51fe-b8e4-a9ce3101ba33" }, "urls": { "image": "https://assets.masko.ai/u/felix-the-fox/waving-hello-a1b2c3.png", "transparent_image": "https://assets.masko.ai/u/felix-the-fox/waving-hello-d4e5f6.png" }, "poll_url": "/api/v1/jobs/0818df65-d092-5901-a506-3b2e64fb88ef?api_version=2026-09-26" } } ``` Poll `/v1/jobs/0818df65-d092-5901-a506-3b2e64fb88ef?wait=true&api_version=2026-09-26` until the job is `completed`, then check the pose. ## Step 4: Animate that pose Send `type: "animation"` with `source_image_asset_id` set to the image from step 3. `animation_model: "standard"` costs 2 credits/sec (2–15s) and `"premium"` 6 credits/sec (4–30s), both 5s by default; this example costs **10 credits**. POST /v1/mascots/:id/generate ```bash curl -X POST https://api.masko.ai/v1/mascots/8bf14263-eabc-58a4-80f6-be9e4f7bceeb/generate \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "animation", "item_id": "b3bbee96-0599-5ca2-ab23-e7b157f50091", "source_image_asset_id": "3c8ce568-35ce-59c2-b3f8-9fb7e4a16e52", "animation_prompt": "waving arm back and forth in a friendly greeting", "animation_model": "standard", "duration": 5 }' ``` ```json { "data": { "job_id": "1ffe886d-01a1-5941-81da-4d160647e693", "status": "pending", "type": "animation", "item_id": "b3bbee96-0599-5ca2-ab23-e7b157f50091", "estimated_cost": 10, "asset_ids": { "video": "15c338b1-4d6e-5686-8e2f-0000ce9e0bd4", "webm": "77681b66-c6ad-5a4c-880c-e260a79d1937", "hevc": "65fbd6d1-6d0b-5738-a893-09ef4d74ad7d" }, "poll_url": "/api/v1/jobs/1ffe886d-01a1-5941-81da-4d160647e693?api_version=2026-09-26" } } ``` `source_image_asset_id` takes a completed image asset: the `asset_ids.image` from step 3, an uploaded mascot reference, or an unattached image from `/v1/upload`. It cannot be: - A preview URL from `/v1/generate/preview` (previews are temporary, not assets) - A `transparent_image` asset ID (the source must be the full image) ## Next steps - [Animations guide](/docs/generate/animations) covers transitions between poses, auto-reverse looping, and size variants - [Reference images and style consistency](/docs/manage/references) explains the three kinds of reference images and when to use each --- ---url: /docs/generate/images--- Source: https://masko.ai/docs/generate/images # Images and poses Generate still poses of your mascot from a prompt. Each image costs 1 credit and comes with a transparent version. POST /v1/mascots/:id/generate ## Generate an image Send `type: "image"` with an item `name` and an `image_prompt`. It creates a new item with pending `image` and `transparent_image` assets. ```bash curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/generate \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "image", "name": "Waving hello", "image_prompt": "waving hello with a friendly smile, one arm raised" }' ``` ```json { "data": { "job_id": "0818df65-d092-5901-a506-3b2e64fb88ef", "status": "pending", "type": "image", "item_id": "b3bbee96-0599-5ca2-ab23-e7b157f50091", "item_name": "Waving hello", "estimated_cost": 1, "asset_ids": { "image": "3c8ce568-35ce-59c2-b3f8-9fb7e4a16e52", "transparent_image": "74568ced-6281-51fe-b8e4-a9ce3101ba33" }, "urls": { "image": "https://assets.masko.ai/u/felix-the-fox/waving-hello-a1b2c3.png", "transparent_image": "https://assets.masko.ai/u/felix-the-fox/waving-hello-d4e5f6.png" }, "poll_url": "/api/v1/jobs/0818df65-d092-5901-a506-3b2e64fb88ef" } } ``` ## Edit an image Send `type: "edit"` with `source_image_asset_id` and `edit_instructions`, for **1 credit**. The edit adds new assets to the same item. ```bash curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/generate \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "edit", "item_id": "b3bbee96-0599-5ca2-ab23-e7b157f50091", "source_image_asset_id": "3c8ce568-35ce-59c2-b3f8-9fb7e4a16e52", "edit_instructions": "Add a party hat and confetti falling around" }' ``` ```json { "data": { "job_id": "18f40bd6-32f2-554c-8fa2-b3d209cf8e93", "status": "pending", "type": "edit", "item_id": "b3bbee96-0599-5ca2-ab23-e7b157f50091", "item_name": "Waving hello", "estimated_cost": 1, "asset_ids": { "image": "a9d57fd3-174a-5d18-9ab6-8e6f33cf6f4c", "transparent_image": "b706a6c3-340f-57ee-97c7-2cdeb614bb07" }, "urls": { "image": "https://assets.masko.ai/u/felix-the-fox/waving-hello-g7h8i9.png", "transparent_image": "https://assets.masko.ai/u/felix-the-fox/waving-hello-j0k1l2.png" }, "poll_url": "/api/v1/jobs/18f40bd6-32f2-554c-8fa2-b3d209cf8e93" } } ``` ## Regenerate Send the same `item_id` with a new `image_prompt`. Each run adds new assets to the item; nothing is overwritten. ```bash curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/generate \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "image", "item_id": "b3bbee96-0599-5ca2-ab23-e7b157f50091", "image_prompt": "waving hello with both arms raised high" }' ``` ## Style consistency Two things keep the mascot consistent, with no setup: - **Reference images**: up to 6, sent as visual context with every generation. Add them with the [references endpoint](/docs/manage/references). - **Style card**: traits such as color palette, proportions and design style, extracted from the references on the first generation and added to every prompt. It is rebuilt when references change. If the character drifts between generations, add 2 or 3 references from different angles. ## Image or animation Use **images** for static poses: profile pictures, thumbnails and marketing assets. Use [animations](/docs/generate/animations) for movement: idle loops, transitions, reactions and interactive behavior. They take longer and start at 4 credits for 2 seconds of Standard from an existing image. --- ---url: /docs/generate/animations--- Source: https://masko.ai/docs/generate/animations # Animations Animate your mascot on a transparent background: a loop that repeats, or a transition from one pose to another. Each animation comes as a transparent WebM (Chrome, Firefox, Edge), a transparent HEVC `.mov` (Safari, Apple devices) and an MP4. Ko's idle loop at its 360-pixel size. POST /v1/mascots/:id/generate ## Upload and animate in one request POST /v1/animations Upload the image with `POST /v1/upload`, then animate its `data.asset_id`. `create_mascot` creates the mascot in the same request: ```bash curl -X POST https://api.masko.ai/v1/animations \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: first-wave-001" \ -d '{ "source_image_asset_id": "UPLOADED_IMAGE_ID", "create_mascot": { "project_id": "PROJECT_ID" }, "name": "Wave", "animation_prompt": "Wave gently, keeping the feet planted.", "animation_model": "standard", "duration": 5 }' ``` - Defaults: Standard, 5 seconds, looping, **10 credits**. Naming, mascot creation and cropping are free; non-square images get a centered square crop ([image framing](#image-framing)). - Without `create_mascot.name`, AI picks a short name such as Pip (`Mascot ` if naming fails) and writes no description. The upload becomes the mascot's first reference as is. - The `202` response has `data.mascot_id`, `mascot_name`, `mascot_created`, `job_id`, `item_id`, `asset_ids`, `estimated_cost` and `poll_url`. Each `create_mascot` makes a new mascot, so send the returned `mascot_id` for the next animation of that character: ```json { "mascot_id": "RETURNED_MASCOT_ID", "source_image_asset_id": "UPLOADED_IMAGE_ID", "name": "Bounce", "animation_prompt": "Bounce gently in place." } ``` Send exactly one of `mascot_id` (optionally with `variant_id`) or `create_mascot`. Retry an uncertain response with the same `Idempotency-Key` and body; if generation fails after the mascot was created, reuse `error.details.mascot_id` with a new key. ## New pose and animation Send an `image_prompt` for the pose and an `animation_prompt` for the motion. Five seconds cost **11 credits** with Standard (1 for the image) and **31** with Premium. ```bash curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/generate \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "animation", "animation_model": "standard", "name": "Idle breathing", "image_prompt": "standing relaxed with arms at sides", "animation_prompt": "gentle breathing motion, subtle body sway", "duration": 5, "loop": true }' ``` ```json { "data": { "job_id": "4f2bcc18-0cfa-5621-8c17-3f9affd86034", "status": "pending", "type": "animation", "animation_model": "standard", "item_id": "2cca0cc6-7d5d-5afb-97af-fa510019190d", "item_name": "Idle breathing", "estimated_cost": 11, "asset_ids": { "image": "0106e9fa-bdec-539d-ab37-107a319c4a6b", "transparent_image": "62788cae-cb57-5366-b779-82d48716864c", "video": "989462f1-7151-5098-965e-6e5236cdc3f6", "webm": "e94545e4-dc01-5ab4-b59a-1255a1cb83c8", "hevc": "038a34f6-c3fa-53e9-9875-638b4963864e" }, "urls": { "image": "https://assets.masko.ai/u/felix-the-fox/idle-breathing-a1b2.png", "transparent_image": "https://assets.masko.ai/u/felix-the-fox/idle-breathing-c3d4.png", "video": "https://assets.masko.ai/u/felix-the-fox/idle-breathing-e5f6.mp4", "webm": "https://assets.masko.ai/u/felix-the-fox/idle-breathing-g7h8.webm", "hevc": "https://assets.masko.ai/u/felix-the-fox/idle-breathing-i9j0.mov" }, "poll_url": "/api/v1/jobs/4f2bcc18-0cfa-5621-8c17-3f9affd86034?api_version=2026-09-26" } } ``` ## Animate an existing image Pass `source_image_asset_id` to skip image generation: **10 credits** for 5 seconds with Standard. ```bash curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/generate \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "animation", "animation_model": "standard", "name": "Waving animated", "source_image_asset_id": "3c8ce568-35ce-59c2-b3f8-9fb7e4a16e52", "animation_prompt": "waving hello with smooth arm motion", "duration": 5, "loop": true }' ``` ```json { "data": { "job_id": "883c13a7-a649-5f7b-adf2-c090f47f39af", "status": "pending", "type": "animation", "animation_model": "standard", "item_id": "90d5fde0-d999-5515-aecd-02658b15058a", "item_name": "Waving animated", "estimated_cost": 10, "asset_ids": { "video": "c24ef85c-3a6c-5c41-bd51-0b9dc0d6b4d8", "webm": "78e05662-0491-504a-8289-77b47704d9bd", "hevc": "a3dedc35-d3ed-520d-9ad1-3f8f32727396" }, "urls": { "video": "https://assets.masko.ai/u/felix-the-fox/waving-animated-a1b2.mp4", "webm": "https://assets.masko.ai/u/felix-the-fox/waving-animated-c3d4.webm", "hevc": "https://assets.masko.ai/u/felix-the-fox/waving-animated-e5f6.mov" }, "poll_url": "/api/v1/jobs/883c13a7-a649-5f7b-adf2-c090f47f39af?api_version=2026-09-26" } } ``` ## From Video POST /v1/mascots/:id/animations/reference-video Copy a video's motion onto your mascot, Premium only: **6 credits per output second plus 1 image credit**, 4 to 30 whole seconds, **31 credits** at the default 5. Upload the video (MP4 or WebM, up to 50 MB) and its first frame (PNG, JPEG or WebP, up to 10 MB), then send both IDs: ```bash curl -X POST https://api.masko.ai/v1/upload \ -H "Authorization: Bearer $MASKO_API_KEY" \ -F "file=@motion.mp4" curl -X POST https://api.masko.ai/v1/upload \ -H "Authorization: Bearer $MASKO_API_KEY" \ -F "file=@first-frame.png" curl -X POST https://api.masko.ai/v1/mascots/$MASCOT_ID/animations/reference-video \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "video_asset_id": "VIDEO_ASSET_ID", "first_frame_asset_id": "FRAME_ASSET_ID", "name": "Friendly wave from video", "animation_model": "premium", "duration": 5 }' ``` - Both sources must be completed, active and accessible to your key. Optional: `variant_id`, `context_asset_id` (reuse a mascot asset's saved context when no variant is set) and `prompt` (pose or look). - The `202` response has `data.job_id`, `poll_url`, `estimated_cost` and `asset_ids`, for one animation item and two pose items (`item_id`, `start_item_id`, `end_item_id`). - Poll `GET /v1/jobs/{job_id}?api_version=2026-09-26` without resubmitting, then fetch each file with `GET /v1/assets/{id}`. ## Transitions Send both `source_image_asset_id` and `end_image_asset_id`; `loop` becomes `false`. **10 credits** for 5 seconds with Standard. ```bash curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/generate \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "animation", "animation_model": "standard", "name": "Idle to Wave", "source_image_asset_id": "6aec5b1a-cb7f-5f10-8d6e-235ec16b4d97", "end_image_asset_id": "18d4fd5e-7193-57f1-8af9-f3bdff007783", "animation_prompt": "smoothly transitioning from idle stance to waving", "duration": 5 }' ``` **Variant context.** A loop uses its source pose's saved context, even on a canvas that defaults to Original, and a transition keeps its start and end contexts separately. - A conflicting `variant_id` returns `409`, unless that variant lists the image among its references. - An upload with no generation receipt uses the `variant_id` that references it, else Original if it does, else the only variant that does. - If several variants share an upload and Original does not, the request returns `409` before charging. Pick a variant or generate distinct poses first. ## Auto-reverse On a transition, `auto_reverse: true` also makes the return clip (B to A), named by `reverse_name`, for **0 extra credits**. It comes back as `reverse_job`. ```bash curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/generate \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "animation", "animation_model": "standard", "name": "Idle to Wave", "source_image_asset_id": "6aec5b1a-cb7f-5f10-8d6e-235ec16b4d97", "end_image_asset_id": "18d4fd5e-7193-57f1-8af9-f3bdff007783", "animation_prompt": "transitioning from idle to waving", "auto_reverse": true, "reverse_name": "Wave to Idle", "duration": 5 }' ``` ```json { "data": { "job_id": "1d340bef-facc-5ede-96a0-d9f7c2ba9628", "status": "pending", "type": "animation", "animation_model": "standard", "item_id": "85aea06c-a445-5a1b-b45b-3e118ababe19", "item_name": "Idle to Wave", "estimated_cost": 10, "asset_ids": { "video": "7e7a871f-be4b-5950-9957-9d95cdc0f120", "webm": "c5e15eb8-1f22-527c-9d53-16a82cec7502", "hevc": "34c1bb11-5dc0-5754-bbd6-804bfde9eed7" }, "urls": {}, "reverse_job": { "job_id": "a7110d36-0bbd-5dfb-9804-942e24c565ce", "item_id": "aa760366-fe84-54f3-85ba-77b0c5ac149d", "status": "pending", "cost": 0, "asset_ids": { "video": "5b1953b9-4805-5bdc-9c9e-6ad481e2993f", "webm": "ae3ac1d5-8c50-52d8-9aa3-e2a65f0aca31", "hevc": "2c9a3d58-80bc-5743-83ef-3ce1ee34e982" } }, "poll_url": "/api/v1/jobs/1d340bef-facc-5ede-96a0-d9f7c2ba9628?api_version=2026-09-26" } } ``` ## Models, duration and looping `animation_model` works on `/generate`, each `generate-batch` request, and canvas edge and `generate-all` requests. | `animation_model` | Duration | Default | Credits per second | | --- | --- | --- | --- | | `standard` | 2–15 whole seconds | 5s | 2 | | `premium` | 4–30 whole seconds | 5s | 6 | Always send `animation_model`. Without it, the request uses the older price of 5 credits per second (4 to 10 seconds, 4 by default), so a default clip costs 20 credits instead of 10 with Standard. `POST /v1/animations` defaults to Standard. A duration outside the range returns `400` before charging. `loop` defaults to `true` (`false` for transitions) and asks for a seamless clip; your player decides whether to repeat it. ## Output formats Each animation has `video` (MP4, opaque), `webm` (VP9 with alpha), `hevc` (MOV with alpha) and `stacked_video` (MP4 with color and alpha stacked, for Android). See [hosting and file formats](/docs/generate/cdn). ## Size variants Every animation has its files at 720 px. Set up to two smaller `animation_sizes`, such as `480`, `360`, `240` or `128`, in the mascot settings to get free resized copies of new animations. Sizes of 720 or more are ignored, and a third smaller size is refused. PATCH /v1/mascots/:id/settings ```bash curl -X PATCH https://api.masko.ai/v1/mascots/MASCOT_ID/settings \ -H "Authorization: Bearer masko_..." \ -H "Content-Type: application/json" \ -d '{ "publish_params": { "animation_sizes": { "enabled": true, "sizes": [480, 240] } } }' ``` For finished animations, call the backfill with an empty body. It uses the configured sizes, or `[360]`, and makes only the files each animation lacks: WebM, HEVC, stacked MP4 and stacked AV1, at its own width and at each size. Files that exist keep their links, and animations made before AV1 get their AV1 files. POST /v1/mascots/:id/size-variants ```bash curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/size-variants \ -H "Authorization: Bearer masko_..." \ -H "Content-Type: application/json" \ -d '{}' ``` Do not toggle the settings to force a backfill. `force: true` regenerates existing variants, for explicit recovery only. ### Instant CDN URLs for size variants Pass `sizes` when generating to get variant URLs at once: ```bash curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/generate \ -H "Authorization: Bearer masko_..." \ -H "Content-Type: application/json" \ -d '{ "type": "animation", "animation_model": "standard", "name": "wave", "image_prompt": "standing with a smile", "animation_prompt": "waving hello", "duration": 5, "sizes": [480] }' ``` ```json { "data": { "urls": { "webm": "https://assets.masko.ai/.../wave.webm", "hevc": "https://assets.masko.ai/.../wave.mov", "webm_480": "https://assets.masko.ai/.../wave-480.webm", "hevc_480": "https://assets.masko.ai/.../wave-480.mov", "stacked_video_480": "https://assets.masko.ai/.../wave-480.mp4" } } } ``` Only sizes enabled in the settings come back, and each URL serves a placeholder until its file is ready; poll the job's `size_variants.status`. ## Direct uploads `source_image_asset_id` also takes your own unattached `POST /v1/upload` image. It is imported as a source under the mascot, with the original file kept, no image generation and no change to mascot references. A new upload can take an approved `variant_id`; existing sources keep their context. ## Image framing Animations use square images. Other images, including those from `item_id` and transition end frames, are center-cropped into a separate 1024 × 1024 source; originals and references stay unchanged, and cropping is free. For an off-center character, set `source_image_crop`: ```json { "type": "animation", "name": "Wave", "source_image_asset_id": "IMAGE_ASSET_ID", "source_image_crop": { "x": 200, "y": 0, "width": 800, "height": 800 }, "animation_prompt": "Wave gently.", "animation_model": "standard", "duration": 5 } ``` - Coordinates are integer pixels after EXIF orientation; this takes 800 × 800 from an image at least 1000 × 800. - The crop must be square (within one pixel) and inside the image, or the request returns `400` before any job or charge. - `end_image_crop` crops the end frame and needs `end_image_asset_id`. Crops do not apply when reversing a video. --- ---url: /docs/generate/talking--- Source: https://masko.ai/docs/generate/talking # Talking animations Make your mascot say a line in its own voice, moving as it talks. You get MP4 and transparent WebM and HEVC with sound, the voice as MP3 and a word-timed transcript. Ko welcoming a new user, from this line: ```text [waves hello, cheerful] Welcome in! [leans in, whispering] Psst... the big orange button does everything. [points down, proud] That one! ``` ## 1. Give the mascot a voice A mascot has one voice; a variant uses the Original's until it gets its own. Get ideas, hear three samples, keep one. Every account can add voices. GET /v1/mascots/:id/voice/suggestions Ideas come from the mascot's references and description; `refresh=true` gives new ones. ```bash curl https://api.masko.ai/v1/mascots/MASCOT_ID/voice/suggestions \ -H "Authorization: Bearer $MASKO_API_KEY" ``` ```json { "data": { "ideas": [ { "title": "Cozy and warm", "description": "A round, cozy pond creature with a big belly. Male voice, warm and round, medium-low, smiling while he talks, relaxed and kind, easy natural pace. American accent." }, { "title": "Excited little sidekick", "description": "An excited little cartoon sidekick. Young male voice, fast and bouncy, high energy, can barely contain his excitement, slightly breathless and giggly. American accent." } ] } } ``` POST /v1/mascots/:id/voice/adjust Send a description and a `direction` such as "Older" or "A little grumpy" to get the rewritten text, the changes and new directions. Without `direction`, only directions come back. ```json { "data": { "description": "A round, cozy pond creature with a big belly. Male voice, warm and round, medium-high, smiling while he talks, relaxed and kind, easy natural pace. American accent.", "changes": [{ "from": "medium-low", "to": "medium-high" }], "directions": ["Younger", "More excited", "Slower", "Drier", "Sleepier"] } } ``` POST /v1/mascots/:id/voice/samples Three voices from one description reading the same line, for 1 credit. `sample_line` is optional. ```bash curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/voice/samples \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "description": "An excited little cartoon sidekick. Young male voice, fast and bouncy, high energy, slightly breathless and giggly. American accent." }' ``` ```json { "data": { "samples": [ { "id": "5b1c2d8e-8a0f-4c55-9f7e-1d2a3b4c5d61", "url": "https://storage.googleapis.com/...", "duration": 9.6 }, { "id": "6c2d3e9f-9b10-4d66-a08f-2e3b4c5d6e72", "url": "https://storage.googleapis.com/...", "duration": 8.3 }, { "id": "7d3e4f0a-ac21-4e77-b190-3f4c5d6e7f83", "url": "https://storage.googleapis.com/...", "duration": 9.3 } ], "sample_line": "Hi! I'm Gubby. I live in your app, and I love showing people around.", "cost_credits": 1, "expires_at": "2026-09-30T09:12:00Z" } } ``` PUT /v1/mascots/:id/voice Keep a sample within 24 hours; it replaces the current voice. `name` is optional. ```bash curl -X PUT https://api.masko.ai/v1/mascots/MASCOT_ID/voice \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "sample_id": "7d3e4f0a-ac21-4e77-b190-3f4c5d6e7f83" }' ``` ```json { "data": { "object": "voice", "name": "Gubby's voice", "description": "An excited little cartoon sidekick. Young male voice, fast and bouncy, high energy, slightly breathless and giggly. American accent.", "sample_url": "https://storage.googleapis.com/...", "source": "original", "created_at": "2026-09-29T09:14:00Z" } } ``` - `GET /v1/mascots/{id}/voice` returns the voice or `null`. `source` is `original`, `variant` or `creator` (a Marketplace mascot using its creator's voice). - A new Original voice also applies to every variant without its own. - Variants use the same calls under `/v1/mascots/{id}/variants/{variantId}/voice`; `DELETE` there restores the Original's voice. ## 2. Write the line Send `speech.script` with bracketed movements where each starts. Bracketed words are never spoken; a feeling after a comma steers the voice. ```text [waves hello, excited] Hi! I'm Gubby. [points to the right] The docs are right here! ``` - A movement starts on the words after it. With no words after it, it leads into the next one, or plays in the closing silence. - Movements happen in place, so leaning in never changes the framing. - The voice only says what is written. To hear a laugh, write it: `[throws its head back, laughing] Ha ha!`. - Or send `speech.text` with an optional `speech.direction`, such as "excited, points at the docs at the end". Masko writes the movements and returns the script. - `speech.language` takes an ISO 639-1 code such as `fr`; without it, the language is detected from the words. Length follows the speech, up to 5 minutes. Longer than 15 seconds, the voice is recorded once and cut at real pauses into 5 to 15 second takes; each starts and ends on the start image, so the mascot returns to that pose at cuts and pauses can grow slightly. ## 3. Estimate for free Add `dry_run: true` to get the script and estimate without charging or generating. ```bash curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/generate \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "animation", "item_id": "ITEM_ID", "speech": { "text": "Hi! I'\''m Gubby. Here'\''s how the Masko API works.", "direction": "excited about what he does" }, "dry_run": true }' ``` ```json { "data": { "dry_run": true, "script": "[waves hello, excited] Hi! I'm Gubby. [opens an arm to the right] Here's how the Masko API works.", "estimate": { "speech_seconds": 4.3, "duration": 6, "max_duration": 6, "credits": 12, "too_long": false } } } ``` ## 4. Generate POST /v1/mascots/:id/generate Send an animation with `speech`. It starts from `item_id` or `source_image_asset_id` and ends on `end_image_asset_id`, or its start image; `variant_id` also picks that variant's voice. ```bash curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/generate \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "animation", "item_id": "ITEM_ID", "end_image_asset_id": "END_IMAGE_ID", "speech": { "script": "[waves hello, excited] Hi! I'\''m Gubby. [opens an arm to the right] Here'\''s how the Masko API works." } }' ``` ```json { "data": { "job_id": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f", "status": "pending", "type": "animation", "animation_model": "standard", "item_id": "4b5c6d7e-8f90-4a1b-9c2d-3e4f5a6b7c8d", "item_name": "Hi, I'm Gubby", "estimated_cost": 12, "estimate": { "speech_seconds": 4.3, "duration": 6, "max_duration": 6, "credits": 12, "too_long": false }, "script": "[waves hello, excited] Hi! I'm Gubby. [opens an arm to the right] Here's how the Masko API works.", "asset_ids": { "video": "5d6e7f80-91a2-4b3c-8d4e-5f6a7b8c9d0e", "webm": "6e7f8091-a2b3-4c4d-9e5f-6a7b8c9d0e1f", "hevc": "7f8091a2-b3c4-4d5e-8f6a-7b8c9d0e1f20", "audio": "8091a2b3-c4d5-4e6f-9a7b-8c9d0e1f2031", "transcript": "91a2b3c4-d5e6-4f7a-8b8c-9d0e1f203142" }, "poll_url": "/api/v1/jobs/3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f?api_version=2026-09-26" } } ``` It costs **2 credits per second**. The request charges for `estimate.max_duration`, the longest the line could need, and refunds unused seconds once the voice is recorded; the job's `cost_credits` shows the final price. Poll `https://api.masko.ai/v1/jobs/JOB_ID?api_version=2026-09-26` ([jobs and polling](/docs/manage/jobs)) and retry safely with the same `Idempotency-Key`. ## The transcript Fetch the `transcript` JSON from the `file_url` of `GET /v1/assets/{id}`. Times are seconds from the first video frame, silence included, and `lines` end at `.`, `!` or `?` for captions. ```json { "text": "Hi! I'm Gubby. Here's how the Masko API works.", "language": "en", "duration": 6, "words": [ { "text": "Hi!", "start": 0.26, "end": 0.6 }, { "text": "I'm", "start": 0.92, "end": 1.04 }, { "text": "Gubby.", "start": 1.16, "end": 1.5 }, { "text": "Here's", "start": 2.04, "end": 2.24 }, { "text": "how", "start": 2.26, "end": 2.38 }, { "text": "the", "start": 2.42, "end": 2.5 }, { "text": "Masko", "start": 2.54, "end": 2.9 }, { "text": "API", "start": 3.0, "end": 3.46 }, { "text": "works.", "start": 3.56, "end": 3.9 } ], "lines": [ { "text": "Hi! I'm Gubby.", "start": 0.26, "end": 1.5 }, { "text": "Here's how the Masko API works.", "start": 2.04, "end": 3.9 } ], "movements": [ { "text": "waves hello, excited", "start": 0.26, "end": 2.04 }, { "text": "opens an arm to the right", "start": 2.04, "end": 3.9 } ] } ``` The `audio` asset is the voice alone as MP3; video assets report `has_audio: true` in their metadata. ## What a line can do Tubey, a robot whose screen is his face, shows one ability per clip (his real lines also asked for a pixel mouth). **Whisper, shout and laugh.** ```text [raises one hand beside his screen, whispering] I can whisper a little secret... [throws both fists up, excited] or shout the big news! [shakes with laughter, laughing] Ha ha! ``` **Several languages in one line.** ```text [waves, happy] I speak your language too. [waves with both hands, excited] Bonjour ! ¡Hola! Ciao! [puts a hand on his chest, proud] And my voice was made from my look. ``` **Act with the mascot's own features**, such as Tubey's screen. ```text [his screen shows a big pixel heart, happy] I love this feature. [his screen shows a question mark, curious] Any questions? [his screen shows two smiling eyes, calm] Just ask. ``` ## Limits - `speech` needs a start image: `item_id` or `source_image_asset_id`. - The line sets the length, so `speech` refuses `duration`, `loop`, `reverse`, `auto_reverse`, `sizes`, `animation_prompt`, `image_prompt` and the image crops. Talking uses Standard; `animation_model: "premium"` is refused. - A mascot without a voice returns `409` with `details.reason: "voice_required"`, before any charge. - A talk longer than 5 minutes returns `400`; split it into two animations. - A minute of talk is about 12 MB in WebM and 30 MB in HEVC at 720 px; see [file sizes](/docs/generate/file-sizes). - Talking animations cannot be reversed or edited, since the voice would not follow. For a new take, resend the script. - `generate-batch` does not accept `speech`; send each talking animation to `/generate`. --- ---url: /docs/generate/design-assets--- Source: https://masko.ai/docs/generate/design-assets # Logos, backgrounds and stickers Turn your mascot into a logo or app icon, an SVG, a background or a sticker. Logos, SVGs and backgrounds return jobs to poll; a sticker can complete right away. ## Logo | Rudy, the mascot | Graphic app icon | Cut-paper mark | Soft-depth icon | | --- | --- | --- | --- | | ![Rudy, a puppy mascot](https://masko.ai/studio/logos/rudy-mascot.webp) | ![Rudy as a graphic app icon](https://masko.ai/studio/logos/rudy-graphic-app-icon-1.webp) | ![Rudy as a cut-paper mark](https://masko.ai/studio/logos/rudy-cut-paper-11.webp) | ![Rudy as a soft-depth icon](https://masko.ai/studio/logos/rudy-soft-depth-21.webp) | Three logos from one mascot; more styles on the [mascot logo page](/studio/logos). A logo costs **5 credits**. The references supply the identity, `logo_description` the composition (such as a face portrait with asymmetrical ears) and `logo_style_instruction` the treatment (such as flat colors or bold outlines), so you can change one without the other. For a filled icon, name the background color and ask it to fill the canvas; the master keeps it. The transparent version, used for resized PNGs and the favicon, can lose colored backing shapes, so use the master for a filled badge. Output is raster; for vector paths, [generate an SVG](#generate-an-svg-from-a-logo). ```bash curl https://api.masko.ai/v1/mascots/MASCOT_ID/generate \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "type": "logo", "name": "App icon", "logo_description": "A face-only icon of the character", "logo_style_name": "Flat", "logo_style_instruction": "Solid colors, simple silhouette, no gradients" }' ``` ## Logo presets and ideas - `GET /v1/logo-styles`: Studio's nine logo treatments with previews and full instructions (not the mascot styles at `/v1/styles`). - `GET /v1/mascots/{id}/logos/suggestions`: five free logo directions and three style suggestions, with optional `variant_id` and `refresh=true`. A direction's custom style has `name` and `instruction`, plus `preset_id` for an exact built-in match. Send a preset as `logo_style_id`: ```json { "type": "logo", "name": "Playful portrait", "logo_description": "An oversized face portrait with a natural tilt", "logo_style_id": "graphic-app-icon" } ``` The preset resolves to its full instructions; `logo_style_name` and `logo_style_instruction` override them. For several logos, use `/generate-batch` with one request each. For safe retries, create the item first and send its `item_id` with a UUID `request_id`. The same ID and body return the existing job without a new charge; a new ID is a new paid attempt. ## Generate an SVG from a logo | Logo image | SVG | | --- | --- | | ![Rudy's graphic app icon as an image](https://masko.ai/studio/logos/rudy-graphic-source.webp) | ![The same icon as an SVG](https://masko.ai/studio/logos/rudy-graphic.svg) | `POST /v1/assets/{id}/svg` with a UUID `request_id` turns a completed logo master, image, transparent image or sticker into an SVG on the same item, for **30 credits**. ```bash curl -X POST 'https://api.masko.ai/v1/assets/ASSET_ID/svg' \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H 'Content-Type: application/json' \ -d '{"request_id":"bba386b8-c756-4f7c-89a2-132862188bf4"}' ``` - The `202` response has `data.job_id`, `data.asset_ids.svg`, `data.estimated_cost` and `data.poll_url`; the job can take several minutes. - Reuse the `request_id` after an interruption or a brief `409`. A new ID is another paid generation; failures are refunded. - Read it with `GET /v1/assets/{svg_asset_id}` (`file_url` or `cdn_url`). In `cdn-export`, hosted logo items show it as `logos.svg` and image items as `svg`. The source PNG stays unchanged. Paths are editable and scale cleanly, but fine details can differ from the source. ## Background A background costs **3 credits**. It is an empty place in your mascot's own style, with no mascot in it: calm areas for your app's title and button, and a spot where the mascot's animation plays on top with the [Masko player](/docs/player). ```bash curl https://api.masko.ai/v1/mascots/MASCOT_ID/generate \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "type": "background", "name": "Splash meadow", "purpose": "splash", "description": "A quiet meadow under a big sky" }' ``` - `purpose` says what the background is for: `splash`, `onboarding`, `signup`, `home`, `paywall`, `reward`, `empty`, `loading`, `landing` or `banner`, or your own words such as `"a quiz result page"`. It sets the shape and is saved with the job. - For five ideas of places from your mascot's world, free, call `GET /v1/mascots/MASCOT_ID/backgrounds/suggestions?purpose=onboarding`. Each suggestion has a `title` and a `description` to send back as `description`. For your own words, the answer also has the `place` that fits. - `place` overrides the shape and the layout: `phone_screen` (9:19.5, splash and onboarding), `phone_header` (10:9, the top of a home screen), `square` (1:1, cards and rewards), `web_hero` (16:9, the top of a landing page) or `banner` (21:9). Without it, the purpose decides, and `phone_screen` is the default. - `description` is optional. Without it, Masko picks a simple place that fits the mascot's style. - `source_asset_id` picks the mascot picture used as the style sample; otherwise its first reference is used. The mascot itself never appears in the background. - To change a background, send its `source_image_asset_id` with another `place` (the same place, drawn in that shape), `edit_instructions` such as `"The same place at night."`, or both. Each change costs 3 credits. - `variant_id` targets a named variant, for logos too. - The result is a 4K image. For an app, export a WebP at screen size with [image exports](/docs/manage/media-exports). - In Masko Studio, six plain backgrounds are also drawn from your mascot's colours, free and instant: brand colour, soft gradient, quiet stage, sky with clouds, celebration rays and ground line. Each purpose offers the ones apps use most for it. - `type: "scene"` was replaced by `type: "background"` and returns `400`. ## Sticker | Ko with a trophy | Ko asleep | | --- | --- | | ![Ko holding a trophy, as a sticker with a white border](https://masko.ai/studio/stickers/ko-trophy.png) | ![Ko curled up asleep, as a sticker with a white border](https://masko.ai/studio/stickers/ko-sleeping.png) | `POST /v1/assets/:id/sticker` turns a completed mascot image into a transparent PNG sticker with a white border, for **1 credit**, with no background-removal step. A completed result returns `201`; check `data.status`. From a completed animation with background removal, it makes an animated sticker in all four chat platform versions for 0 credits; poll `GET /v1/assets/{sticker_id}/sticker`. See [animated sticker creation](/docs/manage/media-exports#create-an-animated-sticker) and the [endpoint reference](/docs/reference) for source requirements. ## Collect the result Save `data.job_id`, follow [job progress](/docs/manage/jobs), and use the returned URLs ([hosting and file formats](/docs/generate/cdn)) rather than building paths. --- ---url: /docs/generate/cursor-follower--- Source: https://masko.ai/docs/generate/cursor-follower # Cursor follower Make your mascot look toward the visitor's cursor on a website. Masko draws nine still images of one transparent pose, one per gaze direction, and a small script shows the right one. | ![Ko looking up and to the left](https://assets.masko.ai/marketing/studio/follow-cursor/ko/up-left-360.webp) | ![Ko looking up](https://assets.masko.ai/marketing/studio/follow-cursor/ko/up-360.webp) | ![Ko looking up and to the right](https://assets.masko.ai/marketing/studio/follow-cursor/ko/up-right-360.webp) | | --- | --- | --- | | ![Ko looking left](https://assets.masko.ai/marketing/studio/follow-cursor/ko/left-360.webp) | ![Ko looking at you](https://assets.masko.ai/marketing/studio/follow-cursor/ko/center-360.webp) | ![Ko looking right](https://assets.masko.ai/marketing/studio/follow-cursor/ko/right-360.webp) | | ![Ko looking down and to the left](https://assets.masko.ai/marketing/studio/follow-cursor/ko/down-left-360.webp) | ![Ko looking down](https://assets.masko.ai/marketing/studio/follow-cursor/ko/down-360.webp) | ![Ko looking down and to the right](https://assets.masko.ai/marketing/studio/follow-cursor/ko/down-right-360.webp) | Ko's nine directions. Try them live on the [cursor follower page](/studio/cursor-follower). ## Cost **9 credits** for all nine, refunded if generation fails. Downloads and hosting add no generation credits. ## What you need A completed image with a visible face and a completed transparent version in the same mascot. The follower keeps the image's variant, so a Sporty pose gives a Sporty follower. Check all nine before use: the gaze can miss, and the head or body can shift slightly between images. ## Make one in Studio 1. **Open your mascot** - In the Interactive tab, choose Create Interactive (or use it on an image's page). 2. **Pick the image** - The Follow cursor template lists images with a transparent version. Choose one, then Create · 9 credits. 3. **Check the result** - Its page shows a live preview and one button per direction. 4. **Download or host it** - Download the Follow cursor package, or use hosted URLs from Get Links. ## Make one with the API POST /v1/mascots/:id/interactive Send a completed image that has a transparent version. `name` (up to 60 characters) defaults to the image's name. ```bash curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/interactive \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "source_asset_id": "SOURCE_IMAGE_ID", "kind": "cursor-follower", "name": "Website cursor follower" }' ``` The `202` response has `data.job_id`, `asset_id`, `asset_ids.interactive`, `variant_id`, `estimated_cost` and `poll_url`. Poll `GET /v1/jobs/JOB_ID?api_version=2026-09-26` until `completed` ([jobs and polling](/docs/manage/jobs)); resending charges a new set. The job type is `interactive_generation`; the set is an `interactive` asset with one `interactive_frame` asset per direction. ### Get the hosted images With hosting on, `GET /v1/mascots/MASCOT_ID/cdn-export` lists complete followers under `interactions`: ```json { "interactions": [ { "version": 1, "id": "INTERACTION_ID", "name": "Website cursor follower", "kind": "cursor-follower", "width": 1024, "height": 1024, "manifest_url": "https://assets.masko.ai/.../manifest.json", "frames": { "up-left": "https://assets.masko.ai/.../up-left.webp", "up": "https://assets.masko.ai/.../up.webp", "up-right": "https://assets.masko.ai/.../up-right.webp", "left": "https://assets.masko.ai/.../left.webp", "center": "https://assets.masko.ai/.../center.webp", "right": "https://assets.masko.ai/.../right.webp", "down-left": "https://assets.masko.ai/.../down-left.webp", "down": "https://assets.masko.ai/.../down.webp", "down-right": "https://assets.masko.ai/.../down-right.webp" } } ] } ``` Frames are lossless 1024 × 1024 WebP on `https://assets.masko.ai`, with links that never expire. A pose can have several followers, so pick yours by `id`; for one made before hosting was on, choose **Update links** in **Get Links**. ## Put it on your website The **Follow cursor package** (`follow-cursor.zip`) needs no API key or Masko connection: | File | What it is | | --- | --- | | `frames/*.webp` | The nine directions, lossless WebP | | `originals/*.png` | The original 1024 × 1024 PNGs | | `cursor-follower.js` | The script, with no dependencies | | `cursor-follower.d.ts` | TypeScript types | | `CursorFollower.tsx` | React component | | `preview.html` | Test page: open it and move your cursor | Resize the 1024-pixel images to the displayed size to keep the page light. ```html
``` ```tsx // Copy frames/ to public/mascot/frames/, and copy CursorFollower.tsx, // cursor-follower.js and cursor-follower.d.ts into your components. import CursorFollower from './CursorFollower'; export function Hero() { return ; } ``` ```tsx // Pass one entry of `interactions` from the cdn-export JSON. import CursorFollower from './CursorFollower'; export function Hero({ interaction }) { return ; } ``` `mountCursorFollower(container, options)` takes `frames` (nine image URLs), optional `size` (64 to 640 pixels) and optional `label` (alt text). It returns: - `setDirection(direction)`, such as `'up-left'`; - `setTracking(enabled)`, to pause or resume tracking; - `destroy()`, to remove the mascot and its listeners. For example, look away while a password is typed: ```javascript const mascot = mountCursorFollower(element, { frames }); password.addEventListener('focus', () => { mascot.setTracking(false); mascot.setDirection('up-left'); }); password.addEventListener('blur', () => mascot.setTracking(true)); ``` On phones, tablets and with reduced motion, the mascot looks straight at the visitor, though `setDirection` still works. For reactions to clicks and app events, build a [canvas](/docs/canvas). --- ---url: /docs/generate/batch--- Source: https://masko.ai/docs/generate/batch # Batch generation and AI suggestions Queue up to 10 generations in one call, and get free pose ideas for your mascot. ## Batch Generation Each entry in `requests` uses the same fields as `/generate`, except `speech`, and all run in parallel. The call returns `202 Accepted` with the queued jobs. POST /v1/mascots/:id/generate-batch ```bash curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/generate-batch \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "requests": [ { "type": "image", "name": "Waving", "image_prompt": "waving hello with a friendly smile" }, { "type": "image", "name": "Thinking", "image_prompt": "hand on chin, looking up thoughtfully" }, { "type": "animation", "name": "Celebrating", "image_prompt": "jumping with arms raised in celebration", "animation_prompt": "jumping up and down excitedly, confetti motion", "animation_model": "standard", "duration": 5, "loop": true } ] }' ``` ```text { "data": { "jobs": [ { "job_id": "job_batch_001", "item_id": "item_b1", "item_name": "Waving", "status": "pending", "estimated_cost": 1, "asset_ids": { "image": "ast_001", "transparent_image": "ast_002" }, "urls": { "image": "https://assets.masko.ai/u/.../waving-a1b2.png", ... } }, { "job_id": "job_batch_002", "item_id": "item_b2", "item_name": "Thinking", "status": "pending", "estimated_cost": 1, "asset_ids": { "image": "ast_003", "transparent_image": "ast_004" }, "urls": { ... } }, { "job_id": "job_batch_003", "item_id": "item_b3", "item_name": "Celebrating", "status": "pending", "estimated_cost": 11, "asset_ids": { "image": "ast_005", "video": "ast_006", "webm": "ast_007", "hevc": "ast_008" }, "urls": { ... } } ], "total_cost": 13 } } ``` ## When to Batch vs Sequential Batch when you know every item up front, such as a full pose set for a new mascot. Send requests one by one when a generation needs an earlier result, such as animating a new image with `source_image_asset_id` after its job completes. ## AI Action Suggestions Get 6 to 8 action names that suit the mascot, from its references or its first completed image, for **free**. GET /v1/mascots/:id/suggestions ```bash curl https://api.masko.ai/v1/mascots/MASCOT_ID/suggestions \ -H "Authorization: Bearer masko_YOUR_API_KEY" ``` ```json { "data": { "suggestions": [ "Wave Hello", "Thumbs Up", "Thinking", "Celebrate", "Point Right", "Sleeping", "Running", "Confused" ] } } ``` Items that already exist are left out, so you can call it again as the pose library grows. The names work as both `name` values and image prompts in a batch. ## Partial acceptance A batch is not atomic: the outer `202` does not mean every request was accepted. Check each entry in `data.jobs` for a real `job_id`, poll accepted jobs individually, and review missing entries before retrying. Never resend the whole batch after an uncertain response. --- ---url: /docs/manage/media-exports--- Source: https://masko.ai/docs/manage/media-exports # Image and video exports Resize images and convert animations, one asset at a time or as saved defaults for a mascot, project or folder. Exports spend no generation credits and never replace source files. ## Export one image or video POST /v1/assets/:id/exports ```bash curl -X POST https://api.masko.ai/v1/assets/ASSET_ID/exports \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: image-webp-256" \ -d '{"format":"webp","size":256}' ``` Use the exact asset ID of the version you want (original image, transparent image or sticker). The asset must be completed, active and in an accessible mascot. | Source | Formats | Size | | --- | --- | --- | | Image, transparent image, sticker | `png`, `webp` | Maximum dimension, 32–2048 pixels | | Video, WebM, HEVC | `webm`, `hevc`, `prores`, `stacked_video`, `gif`, `animated_webp`, `lottie`, `dotlottie`, `png_sequence` | Width, 32–1920 pixels | | Video, WebM, HEVC | `mkpk` (ESP32 boards) | Exact width, a multiple of 16 from 32 to 1024 | - Omit `size` for the original delivery size. Nothing is enlarged. - Images keep their aspect ratio and alpha. - Video export needs an existing transparent-video result and keeps the delivery crop and reverse direction. - `animated_webp` is a transparent, looping, lossy animated WebP with no file-size cap, for apps and websites that play WebP natively. For a WhatsApp sticker, add `sticker_profile` (below). New work returns **202** with `data.job_id`, `asset_id`, `status` and `poll_url`. Poll `GET /v1/jobs/{job_id}`, then call `GET /v1/assets/{id}/exports` for signed `file_url` downloads, one current export per format and size. Signed URLs expire, so list again for fresh links. Matching pending work is reused and a completed export returns **200**. `"force":true` creates a replacement and leaves previous files unchanged. Retry with the same `Idempotency-Key`; a replay returns the original submission response, so poll for current status. In the app, use **Resize / export** on an image, sticker, background or logo, or the animation export dialog. ## Create an animated sticker Turn a completed animation into one sticker with Telegram, WhatsApp, WeChat and Discord files. It costs no generation credits. ```bash curl -X POST https://api.masko.ai/v1/assets/ANIMATION_ASSET_ID/sticker \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Masko-API-Version: 2026-09-26" \ -H "Content-Type: application/json" \ -d '{}' ``` The source is a completed video, WebM or HEVC asset with a background-removal result. One `sticker_animation` asset appears in the Stickers library with four platform files. Repeating the call reuses the sticker and its cached files and retries failed versions; `force:true` requests new files while keeping pending work. The response has `data.id`, `data.asset_ids.sticker_animation`, `data.formats` and `data.poll_url`, usually with 202. Poll `GET /v1/assets/STICKER_ASSET_ID/sticker`: each format has its own status and download URL, and overall status is `processing`, `completed`, `partial_failure` or `failed`. Each completed format has a `file_url`. With asset hosting enabled it also has a permanent `public_url` (Telegram `.webm`, WhatsApp `.webp`, WeChat `.gif`, Discord `.png` APNG); private mascots get `public_url:null`. Hosted link JSON lists them under `animated_stickers`, grouped by sticker ID with its source animation ID, with `formats` keyed `telegram`, `whatsapp`, `wechat` and `discord`. To retry one version, `POST /v1/assets/STICKER_ASSET_ID/exports` with that `sticker_profile`, such as `telegram`. Listing exports for the sticker ID returns its four versions. In the app, choose **Sticker** on an animation card. ## Add stickers to Telegram Open **Get Links → Stickers → Telegram**, click **Prepare for Telegram**, then **Open in Telegram** (or scan the QR code) and tap **Add Stickers**. Masko manages one pack per mascot with all ready image and animated stickers, up to Telegram's limit of 120, so you need no Telegram account or bot. **Add to Telegram** on an animated sticker opens the same pack. - Closing the dialog pauses preparation; Prepare again resumes it and adds new stickers without duplicates. - It is free and needs no public hosting, but anyone with the pack link can use the stickers. - Deleting a sticker in Masko does not remove it from Telegram. - Telegram import is Studio only; the v1 API covers creation, exports and file URLs. ## Download on your phone for WeChat Open **Get Links → Stickers → WeChat**, click **Prepare** and scan the QR code. It opens a mobile page to download each ready sticker (PNG or animated GIF) or a ZIP, which you add one by one through WeChat's custom sticker picker. It does not install a native sticker pack. The link needs no login or public hosting, costs no credits and expires after one hour; anyone with the QR can download during that hour. Prepare again to renew it or include new stickers. Get Links has no WhatsApp option; download WhatsApp files from each sticker's page. ## Animated chat stickers Export one platform version from a completed video asset with a background-removal result, or from an animated sticker asset ID: ```bash curl -X POST https://api.masko.ai/v1/assets/ASSET_ID/exports \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"sticker_profile":"telegram"}' ``` `sticker_profile` picks the chat app, and the app fixes the file type, canvas, duration cap and compression, so omit `size`. `format` can be left out; if you send it, it must be that app's file type, such as `{"format":"animated_webp","sticker_profile":"whatsapp"}`. A name without a version means the current one (`telegram` is `telegram-v1` today); add the version to pin it. The formats `sticker-telegram-v1`, `sticker-whatsapp-v1`, `sticker-wechat-v1` and `sticker-discord-v1` do the same. Files keep 10% size headroom and are validated and checksum-verified before download; a file that cannot meet its budget fails. | `sticker_profile` | Output | Canvas | File budget | Duration cap | | --- | --- | --- | --- | --- | | `telegram` | VP9 WebM, no audio | 512 × 512 | 230,400 bytes | 3 seconds | | `whatsapp` | Animated WebP | 512 × 512 | 450,000 bytes | 10 seconds | | `wechat` | Looping GIF | 240 × 240 | 450,000 bytes | 3 seconds | | `discord` | Looping APNG | 320 × 320 | 471,859 bytes | 5 seconds | Longer inputs are retimed to keep their full loop (WeChat's 3-second cap is Masko's v1 policy). Compression keeps at least 12 fps, and GIF uses binary transparency. The receipt includes `sticker_profile`, `poster_url` and validation metadata; cached results are invalidated when the source changes. WhatsApp import requires at least three animated stickers in a pack. ## ProRes 4444 for video editing `prores` exports a MOV with alpha for After Effects and other editors; files are much larger than WebM or HEVC. In the app, pick **ProRes 4444 with alpha (.mov)** in an animation's **Export** dialog. To export future animations automatically (off by default), enable **Get Links → Settings → Video editing exports**, or save: PATCH /v1/mascots/:id/settings ```json { "publish_params": { "prores_exports": { "enabled": true } } } ``` Each future animation gets one original-size file, queued after background removal, usually on the next minute's queue pass. For existing animations, call `POST /v1/mascots/:id/exports` with `{"types":["video"]}`; this also backfills enabled animation size variants. Disabling stops future exports and keeps existing files. ## .mkpk for ESP32 boards `mkpk` exports one animation as a `.mkpk` pack, the format the ESP32 Masko SDK plays: a 64-byte header, a frame index, then baseline JPEG frames. It makes exactly what you ask for and decides nothing on its own. ```bash curl -X POST https://api.masko.ai/v1/assets/ANIMATION_ASSET_ID/exports \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"format":"mkpk","size":384,"mkpk":{"screen_width":466,"screen_height":466,"fps":24,"quality":75,"background_color":"#000000"}}' ``` | Setting | Values | Default | | --- | --- | --- | | `size` | Frame width, a multiple of 16 from 32 to 1024, never wider than the animation | Required | | `mkpk.screen_width`, `mkpk.screen_height` | The screen the frames are centred on, together | The frame size | | `mkpk.fps` | 1 to 60 | 24 | | `mkpk.quality` | JPEG quality, 1 to 100 | 75 | | `mkpk.background_color` | The colour behind the mascot, `#RRGGBB` | `#000000` | Nothing is enlarged or shrunk for you: for a round screen, choose a width that keeps the mascot inside the circle. Reversed animations are refused because the player plays the forward pack backwards. The receipt repeats the settings under `mkpk`, and a completed export's `metadata` has `bytes`, `sha256`, `frame_count` and the frame position `x` and `y`. A published mascot also gets a permanent `public_url` that serves byte ranges, so a board can stream frames. In the app, pick **ESP32 boards (.mkpk)** in an animation's **Export** dialog. To give every future animation a pack with the same settings, enable **Get Links → Settings → ESP32 board exports**, or save: PATCH /v1/mascots/:id/settings ```json { "publish_params": { "mkpk_exports": { "enabled": true, "size": 384, "screen_width": 466, "screen_height": 466, "fps": 24, "quality": 75, "background_color": "#000000" } } } ``` Packs are queued after background removal. For existing animations, call `POST /v1/mascots/:id/exports` with `{"types":["video"]}`. Disabling stops future packs and keeps existing files. ## Save mascot image defaults PATCH /v1/mascots/:id/settings ```json { "publish_params": { "image_exports": { "enabled": true, "sources": ["original", "transparent", "sticker"], "formats": ["png", "webp"], "sizes": [256, 512] } } } ``` Pick versions, formats and up to five sizes. Only existing versions are exported (`transparent` does not trigger background removal). Exports queue when future images finish; disabling stops future scheduling and keeps existing files. Image settings leave video settings unchanged. Video sizes in `publish_params.animation_sizes` backfill when they change; see [size variants](/docs/generate/animations#size-variants). Image backfill is explicit: POST /v1/mascots/:id/exports ```json { "types": ["image", "video"], "force": false } ``` Omit `types` for both; disabled sections are skipped. The returned batch job completes once exports are scheduled, not when files are ready, so check each asset's export list. In the app, turn on **Smaller PNG and WebP** in the **Settings** tab of **Get Links**, then press **Sync**. ## Apply settings to a project or folder POST /v1/projects/:id/export-settings ```json { "folder_id": "FOLDER_UUID", "publish_params": { "image_exports": { "enabled": true, "sources": ["transparent", "sticker"], "formats": ["webp"], "sizes": [256, 512] } }, "apply_to_existing": true } ``` Omit `folder_id` for the whole project. Settings merge into the mascots currently in scope, keeping omitted settings; mascots created later do not inherit them. `apply_to_existing` backfills enabled image and video sections, and the response has `updated_count` and optional backfill `jobs`. Personal keys reach personal workspace assets and organization keys reach their organization's. Read-only keys can list exports but cannot create them or change settings. --- ---url: /docs/canvas--- Source: https://masko.ai/docs/canvas # Canvases and releases A canvas is the state machine behind an interactive mascot: nodes are poses, and edges are the animated transitions between them with the conditions that trigger them. A canvas belongs to a project and can mix Original and variant poses in one graph. ## Create a canvas Creating an empty draft generates no media. The mascot sets the starting identity and the project controls access. POST /v1/canvases ```bash curl -X POST https://api.masko.ai/v1/canvases \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"project_id":"PROJECT_ID","mascot_id":"MASCOT_ID","name":"Main Canvas"}' ``` The response has `data.id`, `data.graph` and `data.graph_content_hash`. Optional `variant_id` sets the default identity for new poses. GET /v1/canvases ```bash curl 'https://api.masko.ai/v1/canvases?project_id=PROJECT_ID' \ -H "Authorization: Bearer $MASKO_API_KEY" ``` ## Edit safely Read the draft, edit the complete graph and send its revision as `expected_graph_hash`. A concurrent edit returns 409, so read again before reapplying your change. GET /v1/canvases/:canvasId PATCH /v1/canvases/:canvasId ```javascript const base = 'https://api.masko.ai/v1'; const headers = { Authorization: `Bearer ${process.env.MASKO_API_KEY}` }; const read = await fetch(`${base}/canvases/${canvasId}`, { headers }); const result = await read.json(); if (!read.ok) throw new Error(result.error.message); const draft = result.data; const graph = structuredClone(draft.graph); // Edit graph.nodes, graph.edges and graph.inputs here. const saved = await fetch(`${base}/canvases/${canvasId}`, { method: 'PATCH', headers: { ...headers, 'Content-Type': 'application/json' }, body: JSON.stringify({ graph, expected_graph_hash: draft.graph_content_hash }) }); const receipt = await saved.json(); if (!saved.ok) throw new Error(receipt.error.message); ``` Graph fields are in [Build a canvas](/docs/canvas/build). ## Generate and validate Dry-run `POST /v1/canvases/CANVAS_ID/generate-all` to review the work and cost, then submit the same selection with `approved_plan_id` and `expected_graph_hash`. Track it with `GET /v1/canvases/CANVAS_ID/status` instead of resubmitting; see [canvas generation](/docs/canvas/generate-all). [Validation](/docs/canvas/build#validate-before-generating) and history reads are free. ## Checkpoints and releases GET /v1/canvases/:canvasId/history POST /v1/canvases/:canvasId/history ```json { "action": "checkpoint", "name": "Before adding a transition", "expected_graph_hash": "CURRENT_GRAPH_HASH" } ``` `GET /v1/canvases/CANVAS_ID/history` returns the draft, checkpoints and releases. POST one action to the same route: | Action | Additional fields | | ----------------- | ----------------------------------------------------- | | `checkpoint` | `name`, `expected_graph_hash` | | `restore` | `checkpoint_id`, `expected_graph_hash` | | `release` | `operation_id` (UUID), `notes`, `expected_graph_hash` | | `restore_release` | `release_id`, `expected_graph_hash` | | `copy_release` | `release_id`, `name`, `expected_graph_hash` | - Checkpoints can hold unfinished work. A release freezes a complete, playable graph with its exact dependencies, so it needs complete media and a fresh UUID `operation_id`; `notes` is optional. - A release the Masko player would not play is refused with 422 and the reasons: a condition it cannot read (an unknown comparison, a value that is not true, false or a number), an input without a type or starting value, a move to a pose that is not in the canvas, a speed of 0, or a pose without a picture. - Restore saves the current work in a checkpoint first. - No action regenerates media, spends credits, publishes content or activates a mascot on a Mac. An installed release stays pinned until explicitly replaced. ## Deliver a release Your backend fetches the release's delivery with your API key and your app plays it with the [Masko player](/docs/player), which runs every canvas rule: [Play a canvas](/docs/player/canvas) has the backend route, the targets and the `set` and `fire` calls. `target=desktop` returns the full media set, for Masko for Mac. The delivery holds temporary signed playback links, not a credential. Never put an API key in a website or a shipped app. ## Templates and export To start from a ready-made graph, save a [canvas template](/docs/canvas/templates) into an empty draft. [Export](/docs/canvas/export) reads an editable graph with delivery URLs; use a release when playback must keep exact media. --- ---url: /docs/canvas/build--- Source: https://masko.ai/docs/canvas/build # 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](/docs/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](/docs/canvas)), then PATCH its graph with the returned hash: PATCH /v1/canvases/:canvasId ```bash 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. PATCH /v1/canvases/:canvasId ## Delete a Canvas Removes the graph; its items and generated assets stay in the mascot. A canvas with checkpoints or releases returns `409 conflict`. DELETE /v1/canvases/:canvasId ## Example: Sleep/Wake Canvas Two states: the mascot sleeps by default and wakes on `behavior::interact`, which the desktop runtime resolves from direct interaction. ```bash 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" } }' ``` ```text { "data": { "id": "canvas_abc123", "name": "Sleep Wake", "mascot_id": "MASCOT_ID", "graph": { "nodes": [...], "edges": [...], "viewport": { "x": 0, "y": 0, "zoom": 0.8 }, "inputs": [ { "name": "behavior::interact", "type": "boolean", "default": false }, { "name": "behavior::rest", "type": "boolean", "default": true } ] }, "created_at": "2026-03-28T10:00:00Z", "updated_at": "2026-03-28T10:00:00Z" } } ``` Next, [generate-all](/docs/canvas/generate-all) creates every transition in one call. Reverse transitions are free. ## Validate before generating GET /v1/canvases/:canvasId/validate ```bash 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](/docs/canvas/generate-all#poll-progress) to know whether the character can play. --- ---url: /docs/canvas/templates--- Source: https://masko.ai/docs/canvas/templates # Canvas templates A template is a ready-made canvas graph (poses with image prompts, transitions with conditions and durations, and inputs) with no media. Copy it into a canvas, then [generate](/docs/canvas/generate-all) the media for your mascot. ## List templates Returns built-in and saved templates. Filter with `source=builtin` or `source=mine`. GET /v1/canvas-templates ```bash # All templates (built-in and yours) curl https://api.masko.ai/v1/canvas-templates \ -H "Authorization: Bearer $MASKO_API_KEY" # Only built-in templates curl "https://api.masko.ai/v1/canvas-templates?source=builtin" \ -H "Authorization: Bearer $MASKO_API_KEY" ``` ```text { "data": [ { "id": "claude-code-4state", "object": "canvas_template", "name": "Expressive Mascot", "description": "4 animated poses with seamless transitions: resting, coding, deep thinking, and asking for ...", "source": "builtin", "nodes": [ { "key": "idle", "name": "Idle", "imagePrompt": "...", "position": { "x": 0, "y": 0 } }, ... ], "edges": [ { "source": "idle", "target": "working", "duration": 4, "description": "...", "conditions": [ ... ] }, ... ], "inputs": [ ... ] } ] } ``` Built-in templates are written for a coding-agent companion, with inputs such as `claudeCode::isWorking`; pick one that matches your app's states. GET /v1/canvas-templates/:id ## Apply a template to a canvas Create an empty canvas with `POST /v1/canvases`, then save the template's graph with `PATCH /v1/canvases/:canvasId`, which is free. Nodes keep their `imagePrompt`, which generate-all uses to draw the poses before animating the transitions. ```javascript const api = 'https://api.masko.ai/v1'; const headers = { Authorization: `Bearer ${process.env.MASKO_API_KEY}`, 'Content-Type': 'application/json' }; async function call(path, init = {}) { const res = await fetch(`${api}${path}`, { headers, ...init }); const body = await res.json(); if (!res.ok) throw new Error(body.error?.message || `HTTP ${res.status}`); return body.data; } // 1. Read the template. const template = await call('/canvas-templates/claude-code-4state'); // 2. Create an empty canvas. const canvas = await call('/canvases', { method: 'POST', body: JSON.stringify({ project_id: 'PROJECT_ID', mascot_id: 'MASCOT_ID', name: 'Main canvas' }) }); // 3. Turn the template into a canvas graph. const edgeId = (edge) => edge.key ?? `${edge.source}->${edge.target}`; const forwardOf = (edge) => template.edges.find( (e) => !e.reverse && e.source === edge.target && e.target === edge.source ); const graph = { nodes: template.nodes.map((node) => ({ id: node.key, itemName: node.name, imagePrompt: node.imagePrompt, x: node.position?.x ?? node.x ?? 0, y: node.position?.y ?? node.y ?? 0 })), edges: template.edges.map(({ key, reverseOfEdgeKey, reverseOfEdge, ...edge }) => ({ ...edge, // Any State (source "*") routing edges have no video of their own. duration: edge.duration ?? 0, id: key ?? `${edge.source}->${edge.target}`, reverseOfEdgeId: edge.reverse ? (reverseOfEdgeKey ?? reverseOfEdge ?? edgeId(forwardOf(edge))) : null })), inputs: template.inputs ?? [], viewport: { x: 0, y: 0, zoom: 0.8 } }; // 4. Save it into the canvas. await call(`/canvases/${canvas.id}`, { method: 'PATCH', body: JSON.stringify({ graph, expected_graph_hash: canvas.graph_content_hash }) }); ``` To customize before generating, edit `graph` before step 4: a node's `itemName` or `imagePrompt`, or an edge's `description` (its animation prompt). Then dry-run `POST /v1/canvases/CANVAS_ID/generate-all` with `animation_model: "standard"` and `duration: 5`. Each pose image costs 1 credit and each transition 2 credits per second; reverse edges are free. Submit the approved plan as in [generate canvas assets](/docs/canvas/generate-all). ## Save your own Save from one of your canvases or from a raw graph. `public: true` shares it with other users. POST /v1/canvas-templates ```bash curl -X POST https://api.masko.ai/v1/canvas-templates \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Masko-API-Version: 2026-09-26" \ -H "Content-Type: application/json" \ -d '{ "name": "My widget template", "description": "3-state sidebar widget with shared behavior inputs", "canvas_id": "CANVAS_ID", "mascot_id": "MASCOT_ID", "public": false }' ``` ```bash curl -X POST https://api.masko.ai/v1/canvas-templates \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Simple 2-state", "description": "Minimal idle and attention toggle", "template": { "nodes": [ { "key": "idle", "name": "Idle", "imagePrompt": "standing relaxed", "x": 0, "y": 0 }, { "key": "active", "name": "Active", "imagePrompt": "alert and ready", "x": 300, "y": 0 } ], "edges": [ { "source": "idle", "target": "active", "duration": 5, "description": "becoming alert", "conditions": [{ "input": "behavior::attention", "op": "==", "value": true }] }, { "source": "active", "target": "idle", "duration": 5, "description": "relaxing back", "conditions": [{ "input": "behavior::rest", "op": "==", "value": true }], "reverse": true, "reverseOfEdge": "idle->active" } ], "inputs": [ { "name": "behavior::attention", "type": "boolean", "default": false }, { "name": "behavior::rest", "type": "boolean", "default": true } ] }, "public": true }' ``` ```json { "data": { "id": "tmpl_abc123", "name": "Simple 2-state", "source": "user", "public": true, "created_at": "2026-03-28T12:00:00Z" } } ``` `/v1/canvas-templates` is shared by both API versions. When you send `mascot_id`, also send `Masko-API-Version: 2026-09-26`, as in the first example. ## Update a template Change a saved template's name, description or visibility. Built-in templates are read-only. PATCH /v1/canvas-templates/:id ```bash curl -X PATCH https://api.masko.ai/v1/canvas-templates/tmpl_abc123 \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "My widget v2", "description": "Updated description", "public": true }' ``` --- ---url: /docs/canvas/generate-all--- Source: https://masko.ai/docs/canvas/generate-all # Generate canvas assets Generate a canvas's missing pose images and transition videos in one call. Plan with `dry_run: true`, then execute with the returned `plan_id` and `graph_content_hash` so a changed graph is rejected before credits are spent. Use a write key for the mascot's workspace. Dry runs check the same permissions as generation, so a personal key cannot plan an organization canvas. ## Read-only estimates `GET /v1/canvases/{canvasId}/generation-estimate` returns the same plan and cost preview with a read-only key, without changing anything or spending credits. Pass the options as URL-encoded JSON in `options`: ```javascript 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; ``` - Defaults: Standard, 5 seconds, all missing media, no extra stickers. Completed media is always skipped. - `node_ids` and `edge_ids` select specific work; `include_stickers` adds sticker costs. - `dry_run`, `approved_plan_id`, `expected_graph_hash` and `max_credits` are rejected here. To execute, POST the same options, with the explicit model and duration, to `generate-all` with a write key, `dry_run: false`, `approved_plan_id: plan.plan_id`, `expected_graph_hash: plan.graph_content_hash` and an approved `max_credits`. For a graph with several character forms, set `generationVariantId` on each new node that should use an approved variant of the same mascot; untagged new nodes use the canvas identity. Existing poses keep their identity, a conflicting variant is rejected, and variant access is checked in the dry run. To finish an unfinished section with its stickers, send `targets: "all"`, `skip_completed: true` and `include_stickers: true`. POST /v1/canvases/:canvasId/generate-all ```bash 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 }' ``` ```javascript const res = await fetch( `https://api.masko.ai/v1/canvases/${canvasId}/generate-all`, { method: 'POST', headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ dry_run: true, targets: 'animations', edge_ids: ['wave-entry', 'wave-loop'], skip_completed: true, animation_model: 'standard', duration: 5, }), } ); const { data } = await res.json(); console.log(`Planned ${data.total_jobs} jobs for ${data.would_charge_credits} credits`); console.log(`${data.skipped} edges skipped`); await fetch( `https://api.masko.ai/v1/canvases/${canvasId}/generate-all`, { method: 'POST', headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ targets: 'animations', edge_ids: ['wave-entry', 'wave-loop'], animation_model: 'standard', duration: 5, approved_plan_id: data.plan_id, expected_graph_hash: data.graph_content_hash, }), } ); ``` ## How It Works `targets` picks node images, edge videos or both. `skip_completed` (default `true`) skips parts that already have assigned assets, so a second run never duplicates in-flight or completed work. `node_ids` and `edge_ids` limit the selection, and unknown IDs are rejected before any job or charge. A changed graph, prompt, option or selection returns `409`. Response fields: - `dry_run`: true when nothing was created or charged. - `plan_id`: identity of the exact graph, prompts, scope and options. - `graph_content_hash`: canvas revision the plan was made from. - `planned_jobs`: node, sticker and edge work with prompts. Animation and sticker jobs include signed source and target URLs when those images exist; media the run would create shows only its prompt and cost. - `generated`: newly queued node or edge jobs. - `skipped`: number of skipped edges. `skipped_items` lists every part not queued, filtered out by `targets` or already assigned. - `reverse_free`: reverse edges this run makes at 0 credits, by playing their forward clip backwards. - `already_complete`: parts whose assigned assets are completed. - `estimated_cost`, `actual_cost`, `would_charge_credits`: cost before dispatch, credits deducted, and the dry-run estimate. Execution returns `200 OK` with the queued jobs; generation runs asynchronously. ## Edge Classification | Type | Condition | Cost | | --- | --- | --- | | Loop | source == target | Paid | | Forward | source != target | Paid | | Reverse | Reverse of a forward edge | 0 credits | | Any State | source = "*" | Skipped: resolved at runtime, no video | ## Cost Calculation Only loops and forward transitions cost credits: `(loops + forwards) x credits per second x duration`. `animation_model: "standard"` costs 2 credits/sec for 2–15 s and `"premium"` 6 credits/sec for 4–30 s, also for single-edge generation. Changing the model or duration after the dry run needs a new plan. Always send `animation_model` and `duration`. A `generate-all` request without a model uses the older price of 5 credits per second, with a 4-second default. For example, 4 loops and 3 forwards on Standard at 5 seconds: ```text 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 credits ``` Without enough credits, the request returns 402 with the required amount. When a forward transition completes, Masko fills its reverse edge with the reversed video at 0 credits, with no separate request. Only reverse edges declared in the graph are filled, so declare each return path you want. With `edge_ids`, listing either edge of a pair is enough: a forward edge brings its reverse, and a reverse whose forward clip is already finished is made from that clip. Until the forward edge has a finished clip, its reverse is listed in `skipped_items` with `forward_clip_not_ready`. A forward clip that cannot be reversed is listed with `forward_clip_not_reversible`; generate the forward edge again to make its reverse. ## Poll Progress Poll `GET /v1/canvases/CANVAS_ID/status` every 15 to 30 seconds for one summary of the whole canvas; read-only keys can call it. To follow one job, use the `poll_url` returned with it in `jobs`. `status.generation.nodes` and `.edges` count total, completed, pending (neither finished nor failed) and failed parts. `status.failed_nodes` and `status.failed_edges` give each failed part, its job ID and a customer-safe error. A retry is a new paid attempt and should require approval. GET /v1/canvases/:canvasId/status ```bash 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": [ … ] # } # } # } ``` ```javascript async function waitForCanvas(canvasId) { while (true) { const res = await fetch( `https://api.masko.ai/v1/canvases/${canvasId}/status`, { headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY' }, } ); const { data } = await res.json(); const { generation, preview } = data.status; console.log(`Progress: ${generation.edges.completed}/${generation.edges.total} edges`); if (generation.nodes.failed > 0 || generation.edges.failed > 0) { throw new Error('Canvas generation failed. Inspect failed_nodes and failed_edges before retrying.'); } if (generation.ready) { if (preview.ready) { console.log('Canvas is playable in the editor.'); return data; } if (preview.repairable) { console.log('Preview files are missing. Dry-run canvas repair first.'); return data; } console.log('Preview files are still being prepared.'); } await new Promise((r) => setTimeout(r, 15000)); } } ``` `status.generation.ready` is `true` when every node image and non-Any-State edge video has a completed parent asset; assigned pending IDs do not count. For editor playback, wait for `status.preview.ready`: keep polling while `status.preview.waiting_edges > 0`, and dry-run preview-media repair if `status.preview.repairable` is true. ## Regenerate a Single Edge Re-run one edge after a failure or a prompt edit: POST /v1/canvases/:canvasId/edges/:edgeId/generate ```bash 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 }' ``` It returns `202 Accepted` with a `job_id`. Reverse edges cannot be generated here: list one in `edge_ids` on `generate-all` to make it from its forward clip. --- ---url: /docs/canvas/export--- Source: https://masko.ai/docs/canvas/export # Export a canvas Apps play a mascot with the [Masko player](/docs/player), from a [release delivery](/docs/player/canvas#2-fetch-the-delivery-on-your-backend). The `MaskoAnimationConfig` JSON this endpoint returns by default and the `MaskoAnimation` React component that plays it are deprecated. Both are still served, and will be turned off only once no customer calls them. Export a fully generated canvas as JSON: its nodes, edges, video URLs and inputs, as a `MaskoAnimationConfig` by default. GET /v1/canvases/:canvasId/export ```bash curl https://api.masko.ai/v1/canvases/CANVAS_ID/export \ -H "Authorization: Bearer masko_YOUR_API_KEY" ``` ```javascript const res = await fetch( `https://api.masko.ai/v1/canvases/${canvasId}/export`, { headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY' }, } ); const { data: config } = await res.json(); // config is the MaskoAnimationConfig JSON ``` ## Before exporting Turn on asset hosting with **Get Links** in the Masko app or with `POST /v1/mascots/MASCOT_ID/publish` ([turn on hosting](/docs/generate/cdn#turn-on-hosting)), then wait for `status.media.generation.ready: true` on the canvas: every node image and edge video completed, not just assigned. Otherwise the export returns HTTP 400. ## The MaskoAnimationConfig A versioned JSON format (currently `2.0`) describing the state machine. All media URLs use the public CDN. ## Response Structure ```json { "version": "2.0", "name": "My Mascot", "initialNode": "idle", "autoPlay": true, "clickEffect": "none", "nodes": [ { "id": "idle", "name": "Idle", "transparentThumbnailUrl": "https://assets.masko.ai/57fa7e3f-4fdb-556a-8167-968b2b3d8f4b/idle.png" }, { "id": "waving", "name": "Waving", "transparentThumbnailUrl": "https://assets.masko.ai/57fa7e3f-4fdb-556a-8167-968b2b3d8f4b/waving.png" }, { "id": "thinking", "name": "Thinking", "transparentThumbnailUrl": "https://assets.masko.ai/57fa7e3f-4fdb-556a-8167-968b2b3d8f4b/thinking.png" } ], "edges": [ { "id": "idle-loop", "source": "idle", "target": "idle", "isLoop": true, "duration": 4, "videos": { "webm": "https://assets.masko.ai/57fa7e3f-4fdb-556a-8167-968b2b3d8f4b/idle-loop.webm", "hevc": "https://assets.masko.ai/57fa7e3f-4fdb-556a-8167-968b2b3d8f4b/idle-loop.mp4" } }, { "id": "idle-to-waving", "source": "idle", "target": "waving", "isLoop": false, "duration": 4, "videos": { "webm": "https://assets.masko.ai/57fa7e3f-4fdb-556a-8167-968b2b3d8f4b/idle-to-waving.webm", "hevc": "https://assets.masko.ai/57fa7e3f-4fdb-556a-8167-968b2b3d8f4b/idle-to-waving.mp4" }, "conditions": [{ "input": "behavior::interact", "op": "==", "value": true }] }, { "id": "waving-to-idle", "source": "waving", "target": "idle", "isLoop": false, "duration": 4, "videos": { "webm": "https://assets.masko.ai/57fa7e3f-4fdb-556a-8167-968b2b3d8f4b/waving-to-idle.webm", "hevc": "https://assets.masko.ai/57fa7e3f-4fdb-556a-8167-968b2b3d8f4b/waving-to-idle.mp4" } } ], "inputs": [ { "name": "behavior::interact", "type": "boolean", "default": false } ] } ``` ## Config Fields | Field | Type | Description | | --- | --- | --- | | `version` | string | Config format version. Currently "2.0". | | `name` | string | Display name of the canvas. | | `initialNode` | string | ID of the node to display on load. | | `autoPlay` | boolean | Start playing the initial loop automatically. | | `clickEffect` | string | Optional click effect, currently `"ripple"` or `"none"`. | | `nodes[]` | array | Mascot poses. Each has `id`, `name`, and optional `transparentThumbnailUrl`. | | `edges[]` | array | Transitions between nodes. Each has source, target, `isLoop`, duration, videos, and optional conditions. A node can have several `isLoop` edges: the first without conditions is its loop, and the others play while their conditions hold, or now and then when they have none. | | `inputs[]` | array | Programmatic inputs. Each has `name`, `type`, and `default`. | ## Play it Apps play a release, not this config: create one through `/v1/canvases/CANVAS_ID/history`, fetch its signed delivery through your backend and play it with the [Masko player](/docs/player/canvas). Never pass a raw draft export or an API key to a visitor. In the config, each edge's `webm` URL is for Chrome, Firefox and Edge, and its `hevc` (MP4) URL for Safari. ## Targeted delivery `?delivery=1&target=macos` returns the runtime delivery envelope under `data` instead of the config. `target` also accepts `android` (stacked MP4, for Android players), `apple` (HEVC, for iPhone, iPad and Mac players), `web`, `windows` or `full`, which select media formats; the desktop runtime itself runs on macOS only. `mode=selected` is the default and `mode=manifest` requests manifest delivery. An optional positive integer `size` selects a variant. See the [endpoint reference](/docs/reference) for the envelope fields. --- ---url: /docs/player--- Source: https://masko.ai/docs/player # Masko player The Masko player puts your mascot in a website or an app, with its transparency, on the web, React, iPhone, iPad, Mac, React Native, Flutter and Android. Give it one animation and it loops it. Give it a released [canvas](/docs/canvas) and it switches clips by itself: your app only says what is happening, with `set` and `fire`. This is Spike, playing in the Masko player. Hover him, grab him or press a button: the calls your app would send to the player show under him. %% player-demo %% ## Two ways to play - [Play one animation](/docs/player/one-animation) - Loop one animation from its two hosted files. No backend and no API key, about 2 minutes. - [Play a canvas](/docs/player/canvas) - A mascot that reacts to your app: your backend fetches the release, your app calls set and fire. ## Install | Platform | Install | | --- | --- | | Web, any framework | `npm install @masko/player` | | React 17 or later | `npm install @masko/player`, then import from `@masko/player/react` | | Web page without a build step | `` | | iPhone, iPad, Mac and visionOS | Swift package `https://github.com/masko-ai/masko-player` from `0.1.9`, product `MaskoPlayer` | | React Native and Expo | `npm install @masko/player-react-native` | | Flutter | `flutter pub add masko_player` | | Android | `implementation("ai.masko:player:0.1.11")` from Maven Central | ### Web - It runs in Chrome and Edge 87, Firefox 79 and Safari 15 (iOS 15) or later. - The element sets the mascot's size. Without a height, the mascot is square and as wide as the element. - The script tag defines `window.MaskoPlayer`; it is a classic script, not a module. - It runs in Electron apps too, including in a transparent window as a desktop mascot: see the [Electron example](https://github.com/masko-ai/masko-player/tree/main/examples/electron). - With a Content Security Policy, allow `'wasm-unsafe-eval'` in `script-src`, your clip host in `connect-src`, and your clip host and `blob:` in `media-src`. The [web README](https://github.com/masko-ai/masko-player/blob/main/web/README.md) lists every rule and the Vite setting. ### iPhone, iPad and Mac In Xcode, choose **File > Add Package Dependencies**, enter `https://github.com/masko-ai/masko-player`, choose **Up to Next Major Version** from `0.1.9` and add the `MaskoPlayer` product to your app target. It runs on iOS 15, macOS 13, Mac Catalyst 15 and visionOS 1 or later. Show it with `MaskoView` in SwiftUI or `MaskoPlayerView` in UIKit and AppKit. The [Apple README](https://github.com/masko-ai/masko-player/blob/main/apple/README.md) has every call. ### React Native and Expo ```bash npm install @masko/player-react-native npx expo run:ios # or: npx expo run:android ``` The package is an Expo module with the Swift and Android players inside, for Expo SDK 54 (React Native 0.81, new architecture), iOS 15.1 and Android 7 (API 24) or later. It has native code, so build the app after installing it. A bare React Native app installs Expo modules first (`npx install-expo-modules`) and runs `pod install` in `ios/`. On Android, keep the ABIs the player is built for with `reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86_64` in `android/gradle.properties`. The [React Native README](https://github.com/masko-ai/masko-player/blob/main/react-native/README.md) has every prop and call. ### Flutter ```bash flutter pub add masko_player ``` The package carries the Swift and Android players, for Flutter 3.24 or later, iOS 15 and Android 7 (API 24) or later. iOS apps build it with Swift Package Manager or CocoaPods: set the Runner's iOS deployment target to 15.0, and `platform :ios, '15.0'` in `ios/Podfile` when the app uses CocoaPods. Show the mascot with `MaskoView` and drive it with a `MaskoController`. The [Flutter README](https://github.com/masko-ai/masko-player/blob/main/flutter/README.md) has every option and call. ### Android ```kotlin // settings.gradle.kts: repositories { google(); mavenCentral() } dependencies { implementation("ai.masko:player:0.1.11") } ``` `ai.masko:player` is on Maven Central, for Android 7 (API 24) and Java 17 or later. Keep the ABIs it is built for with `abiFilters += listOf("arm64-v8a", "armeabi-v7a", "x86_64")` in `defaultConfig.ndk`. Show it with `MaskoPlayerView`. The [Android README](https://github.com/masko-ai/masko-player/blob/main/android/README.md) has every call. ## What it handles - **Transparency everywhere.** It plays the file each platform shows with transparency: HEVC on Safari and Apple devices, WebM in other browsers and the stacked MP4 on Android. Apps that ask for it get the stacked AV1 file, two to three times lighter ([file sizes](/docs/generate/file-sizes)). - **No flicker.** The next clip is shown only once its first frame is ready, and a move waits for the end of the loop on screen, so it starts from the same frame. - **The page or app around it.** It preloads the next clips, pauses while the page is hidden or the app is in the background, and shows a tap-to-play button when the browser blocks autoplay. - **Sound.** A clip's own voice is heard by default; `setSound(false)` on the web or `sound` set to `false` on Apple, Android, React Native and Flutter mutes it. Browsers allow sound only after the person's first click or key press on the page; until then the player plays muted, and the voice is heard from the next clip or loop. The player is open source (MIT) at [github.com/masko-ai/masko-player](https://github.com/masko-ai/masko-player): one Rust engine runs the canvas rules, with a player for each platform. The [rulebook](https://github.com/masko-ai/masko-player/blob/main/spec/rules.md) gives every rule. --- ---url: /docs/player/one-animation--- Source: https://masko.ai/docs/player/one-animation # Play one animation Give the [Masko player](/docs/player) one animation's files and it loops it with its transparency, with no flash between loops and with the animation's own voice when it has one. Hosted links do not expire, so this needs no backend and no API key. [Install the player](/docs/player#install) for your platform first. ## Get the files Turn on [hosting](/docs/generate/cdn#turn-on-hosting) for the mascot, then copy the animation's links from **Get Links** in Studio or from the [Get Links JSON](/docs/generate/cdn#export-the-get-links-json). [Put your mascot on your website](/docs/tutorials/mascot-on-your-website) shows each step. | Platform | Files | | --- | --- | | Web and React | `transparent_video_webm` and `transparent_video_mov` (HEVC) | | iPhone, iPad and Mac | `transparent_video_av1` (stacked AV1), or `transparent_video_mov` (HEVC) | | Android | `transparent_video_av1` (stacked AV1) on Android 10 and later, else `transparent_video_android` (the stacked MP4) | | React Native and Flutter | `transparent_video_mov` and `transparent_video_android` | The stacked AV1 file is two to three times lighter than HEVC or the stacked MP4: pass its link in place of the HEVC file in Swift, or of the stacked MP4 in Kotlin. Phones before Android 10 have no AV1 decoder, so they need the stacked MP4. The **Mobile** filter in **Get Links** lists only the AV1 links. Animations made before October 27, 2026 have no AV1 file yet: the **Settings** tab shows how many files each size is missing, and **Sync** makes them. ## Play it ```js import { mountMascot } from '@masko/player'; mountMascot(document.getElementById('mascot'), { clip: { webm: 'https://assets.masko.ai/.../wave.webm', hevc: 'https://assets.masko.ai/.../wave.mov' } }); ``` ```tsx import { MaskoPlayer } from '@masko/player/react'; ; ``` ```html
``` ```swift MaskoView(player: MaskoPlayer(clip: URL(string: "https://assets.masko.ai/.../wave.mov")!)) ``` ```tsx ``` ```dart SizedBox( width: 200, height: 200, child: MaskoView(clip: MaskoClipFiles(hevc: hevc, stacked: stacked)), ) ``` ```kotlin val player = view.loadClip("https://assets.masko.ai/.../wave-stacked.mp4") ``` The player reads the file's length and loops it. A clip whose length cannot be read plays nothing and reports the `error` event with `clip_failed`. For visitors who turn off animations, pass `autoPlay: !matchMedia('(prefers-reduced-motion: reduce)').matches` on the web: the player then shows the first frame and waits. ## Switch between animations A new `clip` starts a new player, so the mascot can blink between two animations. To switch between animations, or to make the mascot react to your app, put them in a [canvas](/docs/canvas) and play its release: [Play a canvas](/docs/player/canvas). --- ---url: /docs/player/canvas--- Source: https://masko.ai/docs/player/canvas # Play a canvas A released [canvas](/docs/canvas) holds your mascot's poses, the moves between them and the rules that pick them. The [Masko player](/docs/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](/docs/player#install) for your platform first. ## 1. Release the canvas Release the canvas ([checkpoints and releases](/docs/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 /v1/canvases/:canvasId/releases/:releaseId/delivery ```javascript // 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 or Flutter 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 and Flutter). 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](/docs/generate/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](https://masko.ai/schemas/mascot-release-delivery.schema.json) describes the whole delivery. ## 3. Mount the player ```javascript 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); ``` ```tsx import { useRef } from 'react'; import { MaskoPlayer, type MaskoHandle } from '@masko/player/react'; export function Mascot({ delivery }) { const mascot = useRef(null); // Elsewhere: mascot.current?.fire('celebrate'); return ( fetch('/api/mascot-delivery').then((r) => r.json())} style={{ width: 240, height: 240 }} /> ); } ``` ```html
``` ```swift import MaskoPlayer let player = MaskoPlayer( delivery: deliveryData, // the delivery JSON, as Data getDelivery: { try await api.mascotDelivery() } // your backend call, returning Data ) player.onEvent = { event in print(event) } player.set("working", true) player.fire("celebrate") MaskoView(player: player) // SwiftUI MaskoPlayerView(player: player) // UIKit (a UIView) or AppKit (an NSView) ``` ```tsx import { useRef } from 'react'; import { Platform } from 'react-native'; import { MaskoView, maskoFormats, type MaskoHandle } from '@masko/player-react-native'; const target = Platform.OS === 'ios' ? 'apple' : 'android'; // Your backend adds formats=${maskoFormats()} to the delivery request. const formats = maskoFormats(); export function Mascot({ delivery }) { const mascot = useRef(null); // Elsewhere: mascot.current?.fire('celebrate'); return ( api.mascotDelivery(target, formats)} /> ); } ``` ```dart import 'dart:io'; import 'package:masko_player/masko_player.dart'; final target = Platform.isIOS ? 'apple' : 'android'; // Your backend adds formats=$formats to the delivery request. final formats = await maskoFormats(); final mascot = MaskoController(); // Elsewhere: mascot.fire('celebrate'); SizedBox( width: 200, height: 200, child: MaskoView( controller: mascot, delivery: delivery, getDelivery: () => api.mascotDelivery(target, formats), ), ); ``` ```kotlin val view = MaskoPlayerView(context) // add it to your layout val player = view.load(deliveryJson, MaskoPlayer.Options( getDelivery = { reply -> api.fetchDelivery { json -> reply(json) } } )) player.set("working", true) player.fire("celebrate") // player.destroy() in Activity.onDestroy or Fragment.onDestroyView ``` In React Native, events are props: `onPose`, `onClip`, `onReactionDone`, `onMovement` and `onError`. In Flutter they are callbacks of `MaskoView` with the same names, and the calls go through its `MaskoController`. ## 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, Android and Flutter) 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, React Native and Flutter, pass the new `delivery`) 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. --- ---url: /docs/generate/cdn--- Source: https://masko.ai/docs/generate/cdn # Hosting and file formats Pick the file your platform plays with transparency, then serve it from Masko's CDN or your own. ## File formats | Type | File | Use it for | | --- | --- | --- | | `webm` | WebM (VP9 with alpha) | Transparent video in Chrome, Firefox and Edge | | `hevc` | MOV (HEVC with alpha) | Transparent video in Safari, on iPhone, iPad and Mac | | `stacked_video` | MP4, color and alpha stacked vertically | Android apps, with the Masko player | | `stacked_video_av1` | MP4 in AV1, color and alpha stacked vertically | The Masko player in iPhone, iPad, Mac and Android apps, two to three times lighter than HEVC or the stacked MP4 at the same look | | `video` | MP4 (H.264) | Anywhere, but with an opaque background | | `transparent_image` | PNG | A still pose with a transparent background | Apple browsers need HEVC for transparency, others WebM, and Android apps the stacked MP4. The [Masko player](/docs/player/one-animation) picks the right file and loops it: ```html
``` A [talking animation](/docs/generate/talking) plays its own voice. Browsers allow sound only after the person's first click or key press on the page: until then it plays muted, and its voice is heard from the next loop. Pass `sound: false` to keep it silent. ## Hosted CDN URLs With hosting on, generation returns CDN URLs before the files exist. Each serves a branded placeholder until the real file replaces it at the same URL. Keep a loading state until the job and the formats you need are complete. Signed `file_url` links expire, so keep asset IDs and request fresh links. ### URL format ```text https://assets.masko.ai/{user_prefix}/{mascot_slug}/{item_slug}-{hash}.{ext} Example: https://assets.masko.ai/fda8417d/felix-the-fox/waving-hello-a1b2c3d4.png ``` `user_prefix` is your account prefix, `mascot_slug` comes from the mascot's `slug` field, `item_slug` from the item name at creation, and `ext` from the asset type (`png`, `mp4`, `webm` or `mov`). ## Size variants Free [size variants](/docs/generate/animations#size-variants) add the size before the extension. The [file size calculator](/docs/generate/file-sizes) shows what each size weighs. URLs look like this: ```text # Original (full resolution) https://assets.masko.ai/u/felix-the-fox/waving-a1b2.webm # 480px variant https://assets.masko.ai/u/felix-the-fox/waving-c3d4-480.webm # 360px variant https://assets.masko.ai/u/felix-the-fox/waving-e5f6-360.webm ``` ### Get variant URLs instantly Pass `sizes` when generating to get the variant URLs in the response: ```json { "type": "animation", "name": "wave", "image_prompt": "standing and waving", "animation_prompt": "waving hello", "animation_model": "standard", "duration": 5, "sizes": [480, 360] } ``` `urls` then adds keys such as `webm_480` for sizes enabled in `animation_sizes`, serving placeholders until ready; poll the job's `size_variants.status`. ## Check CDN status `GET /v1/mascots/:id` returns `cdn_status`, with each published asset's URL, size and status. GET /v1/mascots/:id ```bash curl https://api.masko.ai/v1/mascots/MASCOT_ID \ -H "Authorization: Bearer masko_YOUR_API_KEY" ``` ```json { "data": { "id": "MASCOT_ID", "name": "Felix the Fox", "slug": "felix-the-fox", "cdn_status": [ { "asset_id": "3c8ce568-35ce-59c2-b3f8-9fb7e4a16e52", "item_name": "Waving hello", "type": "image", "cdn_url": "https://assets.masko.ai/fda8417d/felix-the-fox/waving-hello-a1b2.png", "status": "completed", "file_size": 245832 }, { "asset_id": "d8c65d05-f77f-5925-8453-c9be7d7f14be", "item_name": "Idle breathing", "type": "webm", "cdn_url": "https://assets.masko.ai/fda8417d/felix-the-fox/idle-breathing-c3d4.webm", "status": "completed", "file_size": 1548200 } ] } } ``` ## List mascot assets Each asset has `cdn_url` when hosted, otherwise a signed `file_url`, and `file_size` in bytes once it is completed. Add `?include_file_urls=false` to skip signing for metadata-only lists. GET /v1/mascots/:id/assets ```bash curl https://api.masko.ai/v1/mascots/MASCOT_ID/assets \ -H "Authorization: Bearer masko_YOUR_API_KEY" ``` ```json { "data": [ { "id": "3c8ce568-35ce-59c2-b3f8-9fb7e4a16e52", "type": "image", "status": "completed", "file_url": "https://storage.googleapis.com/...", "cdn_url": "https://assets.masko.ai/fda8417d/felix-the-fox/waving-hello-a1b2.png", "item_id": "7ca22f89-1dd6-5193-8739-31cd665598af" } ], "meta": { "pagination": { "total": 1, "limit": 50, "offset": 0, "has_more": false } } } ``` ## Turn on hosting Mascots created with the API start with hosting on. Mascots made in Studio or through the [MCP server](/docs/mcp) start with it off. Turn it on with the **Get Links** switch in Studio, or with this call. Every finished image and video gets a permanent public URL, and files finished later are hosted automatically. Anyone with a URL can open the file, so an agent should ask the mascot's owner before it calls this. POST /v1/mascots/:id/publish ```bash curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/publish \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ```json { "data": { "success": true, "mascot_id": "MASCOT_ID", "public_slug": "felix-the-fox-a1b2", "user_prefix": "fda841", "cdn_asset_count": 12, "published": true, "changed": true, "public_urls": { "base": "https://assets.masko.ai/fda841/felix-the-fox-a1b2", "items": { "idle": { "webm": "https://assets.masko.ai/fda841/felix-the-fox-a1b2/idle-g7h8.webm", "hevc": "https://assets.masko.ai/fda841/felix-the-fox-a1b2/idle-k1l2.mov" } } } } } ``` Calling it again on a hosted mascot changes nothing and returns `changed: false`. Send `{"force": true}` to host finished files that are missing. It returns `400` when the mascot has no finished file yet. Hosting costs no credits up to 50,000 views a month, then 1 credit per 1,000 views. ## Export the Get Links JSON `GET /v1/mascots/:id/cdn-export` returns the **Get Links** JSON of the mascot page: images, transparent images, videos, size variants, logos and stickers by item name. `animated_stickers` have `telegram`, `whatsapp`, `wechat` and `discord` keys in `formats`. Only ready, hosted versions appear. While hosting is off, or before any file is hosted, it returns `409 cdn_export_not_ready` with a message that says which one: [turn on hosting](#turn-on-hosting), or wait for the jobs to finish. GET /v1/mascots/:id/cdn-export ```bash curl https://api.masko.ai/v1/mascots/MASCOT_ID/cdn-export \ -H "Authorization: Bearer masko_YOUR_API_KEY" ``` ```json { "mascot": "Felix the Fox", "items": [ { "name": "idle", "image": "https://assets.masko.ai/fda8417d/felix-the-fox/idle-a1b2.png", "transparent_image": "https://assets.masko.ai/fda8417d/felix-the-fox/idle-c3d4.png", "animations": [ { "video": "https://assets.masko.ai/fda8417d/felix-the-fox/idle-e5f6.mp4", "transparent_video_webm": "https://assets.masko.ai/fda8417d/felix-the-fox/idle-g7h8.webm", "transparent_video_android_360": "https://assets.masko.ai/fda8417d/felix-the-fox/idle-i9j0-360.mp4", "transparent_video_av1_360": "https://assets.masko.ai/fda8417d/felix-the-fox/idle-k1l2-360.mp4" } ], "animated_stickers": [ { "id": "22222222-2222-4222-8222-222222222222", "source_asset_id": "33333333-3333-4333-8333-333333333333", "formats": { "telegram": "https://assets.masko.ai/fda8417d/felix-the-fox/idle-sticker.webm", "whatsapp": "https://assets.masko.ai/fda8417d/felix-the-fox/idle-sticker.webp", "wechat": "https://assets.masko.ai/fda8417d/felix-the-fox/idle-sticker.gif", "discord": "https://assets.masko.ai/fda8417d/felix-the-fox/idle-sticker.png" } } ] } ] } ``` ## Change the slug A new `slug` changes only future asset paths. Slugs are 2 to 50 lowercase letters, digits and dashes, unique across all mascots. PATCH /v1/mascots/:id ```bash curl -X PATCH https://api.masko.ai/v1/mascots/MASCOT_ID \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "slug": "felix" }' ``` ```json { "data": { "id": "MASCOT_ID", "slug": "felix" } } ``` ## Turn off hosting Set `settings.cdn_enabled: false` when creating the mascot. Assets are still generated, with signed storage URLs from the jobs endpoint. --- ---url: /docs/generate/file-sizes--- Source: https://masko.ai/docs/generate/file-sizes # Animation file sizes Every animation comes in several formats, and each place plays one of them. Pick where your mascot lives, how many animations it has, their length and their size, and see what a visitor loads or how much your app grows. ## Which file each place uses | Where | File | 5 s at 360 px | 5 s at 720 px | | --- | --- | --- | --- | | Website in Chrome, Firefox, Edge | WebM with transparency | 0.48 MB | 0.94 MB | | Website in Safari, iPhone, iPad and Mac apps | HEVC .mov with transparency | 0.88 MB | 2.2 MB | | Android app | Stacked MP4 | 0.43 MB | 2.4 MB | | iPhone, iPad, Mac and Android apps with the Masko player | Stacked AV1 | 0.14 MB | 0.24 MB | Measured on 40 Masko animations of 5 seconds, and the stacked AV1 file on the 75 animations made on October 27, 2026, the day every animation started getting one: it is made at a lighter setting that keeps the look at mascot size, in a format that compresses better. Sizes include the sound. Size grows in line with the length, and busy motion is heavier. The opaque MP4 is about 2 MB for 5 seconds at full size; use it only where you do not need transparency. ## Bundle or load from the CDN - **On a website**, files load when the mascot appears. Each visitor downloads one format, the one their browser plays, and the browser caches it. - **In an app**, you choose. Bundle the files and your app grows by the total above, but the mascot works offline from the first launch. Or load them from Masko's CDN on first use: the app stays small, and each animation downloads once, then plays from the cache. - **In both apps**, the Masko player plays the same stacked AV1 file. Without it, each app carries its own format: HEVC on iPhone and iPad, the stacked MP4 on Android. ## Talking animations A talking animation weighs about the same per second as any other, with its sound included. A 30-second talk at 720 px is about **6 MB** in WebM, **15 MB** in HEVC and **1.4 MB** in stacked AV1 with the Masko player. Load long talks only when the visitor presses play. ## Make your files lighter - **Use the Masko player in apps.** It asks for the stacked AV1 file (`formats=av1`): about a tenth of HEVC or the stacked MP4 at 720 px, and a third to a sixth at 360 px. See [play a canvas](/docs/player/canvas). - **Use a size variant.** At 360 px a clip is about half its full-size WebM and 40 percent of its full-size HEVC. For a mascot shown at 240 px or smaller, turn on [size variants](/docs/generate/animations#size-variants) once; they are free. - **Load one format.** Pick WebM or HEVC from the browser, as in [hosting and file formats](/docs/generate/cdn#file-formats). - **Load talks on demand.** Give long talking clips `preload="none"` and a poster image, and start them on a tap. - **Check real sizes.** Every asset has `file_size` in bytes, and a finished job gives `file_sizes` next to its links, see [get job detail](/docs/manage/jobs#get-job-detail). `GET /v1/mascots/:id` gives each hosted file's size too, see [check CDN status](/docs/generate/cdn#check-cdn-status). --- ---url: /docs/integrations/desktop--- Source: https://masko.ai/docs/integrations/desktop # Float a mascot on your Mac Masko Desktop shows a mascot as a floating overlay on your Mac. The mascot stays above your windows and reacts to the state it is in. Desktop places and shows the mascot. The API and the [command line](/docs/terminal) manage its content. ## Install a mascot 1. Install Masko Desktop for macOS 14 or later from [masko.ai/download](https://masko.ai/download). 2. Open a mascot's marketplace page on [masko.ai](https://masko.ai/marketplace) and install it. The browser opens Desktop through a `masko://install/marketplace` link, and Desktop adds the mascot to your library. 3. Desktop floats the mascot. Ko is the default mascot, so a new install shows him until you choose another. ## Make your own mascot Create and animate a mascot with the [quickstart](/docs/quickstart) or the [command line](/docs/terminal). To install it in Desktop, publish it to the marketplace. Listings are reviewed before others can install them. ## What the API controls and what Desktop controls | Task | Done with | | --- | --- | | Create the character and its poses and animations | API, Studio or the command line | | Download the finished files | API or the command line | | Install a mascot and choose which one floats | Masko Desktop | | Place, drag, resize and hide the mascot on screen | Masko Desktop | No public API call shows a mascot on your Mac today. Desktop's local interface is for Desktop itself and uses a per-user token, so apps should not rely on it. --- ---url: /docs/manage/jobs--- Source: https://masko.ai/docs/manage/jobs # Jobs and polling Every generation creates a job. Poll it, long-poll until it finishes, or get notified with [webhooks](/docs/manage/webhooks). Send `Masko-API-Version: 2026-09-26` (or `?api_version=2026-09-26`) on every `/v1/jobs` request. The route is shared with an older API version and otherwise returns older field names such as `collection_id`. The `poll_url` from `/v1/mascots` routes already includes it. `generation_context`, `input_data` and `output_data` are receipts saved when the job ran, and keep their original field names. ## Job lifecycle A job is `pending` when accepted and `processing` once a worker picks it up, then ends `completed` with asset URLs or `failed` with an error. ```text pending --> processing --> completed \--> failed ``` ## List jobs GET /v1/jobs ```bash # List all jobs curl https://api.masko.ai/v1/jobs \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Masko-API-Version: 2026-09-26" # Filter by status and mascot curl "https://api.masko.ai/v1/jobs?status=completed&mascot_id=MASCOT_ID" \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Masko-API-Version: 2026-09-26" # Filter by type curl "https://api.masko.ai/v1/jobs?type=animation" \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Masko-API-Version: 2026-09-26" ``` ```javascript const params = new URLSearchParams({ status: 'completed', mascot_id: mascotId, }); const res = await fetch( `https://api.masko.ai/v1/jobs?${params}`, { headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY', 'Masko-API-Version': '2026-09-26' }, } ); const { data: jobs } = await res.json(); // jobs: [{ id, status, type, created_at, ... }, ...] ``` Filters: `status` (`pending`, `processing`, `completed`, `failed`), `mascot_id`, and `type` (`item_generation`, `image_generation`, `logo`, `animation`, `reverse`, `size_variant`, `size_variant_batch`, `export_animation`, `background_generation`). Variant filters are in [trace the variant used](#trace-the-variant-used). ## Get job detail Completed jobs include output asset IDs and CDN URLs, and in `file_sizes` each file's size in bytes, under the same key as its link in `urls` (null when the file cannot be read). GET /v1/jobs/:id ```bash curl https://api.masko.ai/v1/jobs/JOB_ID \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Masko-API-Version: 2026-09-26" # Completed job response: # { # "data": { # "id": "0818df65-d092-5901-a506-3b2e64fb88ef", # "status": "completed", # "type": "item_generation", # "mascot_id": "c244e7b1-5655-5540-89a8-516b09bd4985", # "cost_credits": 21, # "created_at": "2026-03-28T10:00:00Z", # "updated_at": "2026-03-28T10:01:32Z", # "item_id": "e1a868c7-82b1-5d17-947b-ab4eeebd6fc3", # "item_name": "Wave", # "urls": { # "image": "https://assets.masko.ai/c244e7b1-5655-5540-89a8-516b09bd4985/image.png", # "webm": "https://assets.masko.ai/c244e7b1-5655-5540-89a8-516b09bd4985/video.webm" # }, # "file_sizes": { # "image": 412833, # "webm": 1370412 # } # } # } ``` ```javascript const res = await fetch( `https://api.masko.ai/v1/jobs/${jobId}`, { headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY', 'Masko-API-Version': '2026-09-26' }, } ); const { data: job } = await res.json(); if (job.status === 'completed') { console.log('Image URL:', job.urls.image); console.log('Video URL:', job.urls.webm); } else if (job.status === 'failed') { console.error('Job failed:', job.error); } ``` ## Long polling Add `?wait=true` to hold the request open until the job finishes or `timeout` (default 120 seconds) passes. A timeout returns the current status and does not cancel the job. GET /v1/jobs/:id?wait=true&timeout=120 ```bash # Wait up to 120 seconds for the job to complete curl "https://api.masko.ai/v1/jobs/JOB_ID?wait=true&timeout=120" \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Masko-API-Version: 2026-09-26" # Returns immediately if already completed. # Returns the job object when it completes. # Returns with current status if timeout is reached. ``` ```javascript async function waitForJob(jobId, timeoutMs = 10 * 60 * 1000) { const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { const res = await fetch( `https://api.masko.ai/v1/jobs/${jobId}?wait=true&timeout=30`, { headers: { Authorization: `Bearer ${process.env.MASKO_API_KEY}`, 'Masko-API-Version': '2026-09-26' }, signal: AbortSignal.timeout(Math.min(35000, deadline - Date.now())) } ); const body = await res.json(); if (!res.ok) throw new Error(body.error?.message || `HTTP ${res.status}`); const job = body.data; if (job.status === 'completed') return job; if (job.status === 'failed') throw new Error(JSON.stringify(job.error)); } throw new Error('Stopped waiting. Keep the job ID and check again; work may continue.'); } const job = await waitForJob('0818df65-d092-5901-a506-3b2e64fb88ef'); console.log('Done:', job.urls); ``` ## When to poll and when to use webhooks - **Long-polling**: simplest, for scripts, CLI tools and prototypes. - **Webhooks**: for production. Your server is notified, with no open connections. - **Both**: webhooks for background work, long-polling where a user waits for the result. ## Media readiness A job and its derived formats can finish at different times, and a returned CDN URL may still serve a placeholder. Check the required asset type and `size_variants` before playback. For a canvas use `status.media.preview.ready`; for a developer run, wait for its runtime readiness and version reference. ## Trace the variant used `GET /v1/jobs/{id}` returns `variant_id` and, when recorded, `generation_context`: the frozen context the job used (variant ID and name, engine hash, mascot prompt and style, resolved reference asset IDs and asset inputs). It never reflects the currently active variant, and internal execution details are never returned. Original-context jobs, cross-variant transitions and older untagged jobs have a null `variant_id`. - `GET /v1/jobs?mascot_id=MASCOT_UUID&variant_id=VARIANT_UUID` lists work for one variant. Generated assets keep their `job_id`, so you can trace them back. - `from_variant_id` and `to_variant_id` filter transitions by role and accept named variant UUIDs. Original endpoints have null IDs, so find them by inspecting the mascot's jobs. ```text GET /v1/jobs?mascot_id=MASCOT_UUID&to_variant_id=VARIANT_UUID ``` For transitions, `generation_context.transition.before` and `.after` each record `asset_id`, `collection_id`, `variant_id`, `variant_name`, `context_source` and the saved generation context. A reverse job swaps the roles without a new writer receipt. Older jobs without a recorded transition are not backfilled. A video edit creates a `video_edit` job. For a transition edit, it keeps `transition.before` and `.after` and adds `generation_context.transition_edit` with the source video ID and requested edit. Repeated edits keep the endpoint identities. [Cursor followers](/docs/generate/cursor-follower) create `interactive_generation` jobs; get their nine image URLs from `GET /v1/mascots/{id}/cdn-export`. --- ---url: /docs/manage/webhooks--- Source: https://masko.ai/docs/manage/webhooks # Webhooks Register a URL and Masko POSTs job results to it when a job completes or fails, so you do not have to poll. ## Create a Webhook POST /v1/webhooks ```bash curl -X POST https://api.masko.ai/v1/webhooks \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-app.com/api/masko-webhook", "events": ["job.completed", "job.failed"] }' # Response: # { # "data": { # "id": "wh_abc123", # "url": "https://your-app.com/api/masko-webhook", # "events": ["job.completed", "job.failed"], # "secret": "whsec_k7x9m2p4q8r1...", # "created_at": "2026-03-28T10:00:00Z" # } # } ``` ```javascript const res = await fetch('https://api.masko.ai/v1/webhooks', { method: 'POST', headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ url: 'https://your-app.com/api/masko-webhook', events: ['job.completed', 'job.failed'], }), }); const { data: webhook } = await res.json(); // Store webhook.secret securely - it is only shown once ``` The `secret` is shown only once. If you lose it, delete the webhook and create a new one. ## Webhook Payload ```json { "id": "5f1c2a7e-8b0d-4c3e-9a51-2d7f6b8e4c19", "object": "event", "event": "job.completed", "job_id": "0818df65-d092-5901-a506-3b2e64fb88ef", "type": "animation", "mascot_id": "c244e7b1-5655-5540-89a8-516b09bd4985", "asset_ids": { "image": "3c8ce568-35ce-59c2-b3f8-9fb7e4a16e52", "transparent_image": "477db326-3fb1-53aa-aa09-d8377daa1ffa", "video": "1b6520a1-a890-5a1c-ac87-15e9b14c4852", "webm": "3db2d3be-a85a-5e9e-85fc-02100b19d8cb", "hevc": "267afbae-2a4b-51c9-a172-47bb3017c32f" }, "urls": { "image": "https://assets.masko.ai/c244e7b1-5655-5540-89a8-516b09bd4985/image.png", "transparent_image": "https://assets.masko.ai/c244e7b1-5655-5540-89a8-516b09bd4985/transparent.png", "video": "https://assets.masko.ai/c244e7b1-5655-5540-89a8-516b09bd4985/video.mp4", "webm": "https://assets.masko.ai/c244e7b1-5655-5540-89a8-516b09bd4985/video.webm", "hevc": "https://assets.masko.ai/c244e7b1-5655-5540-89a8-516b09bd4985/video_hevc.mp4" }, "file_sizes": { "image": 412833, "transparent_image": 298120, "video": 2620007, "webm": 1370194, "hevc": 2397850 }, "timestamp": "2026-03-28T10:01:32Z" } ``` ```json { "id": "b7e0d4a2-1f6c-4e8b-a3d9-6c2e5f0a7b81", "object": "event", "event": "job.failed", "job_id": "0818df65-d092-5901-a506-3b2e64fb88ef", "type": "animation", "mascot_id": "c244e7b1-5655-5540-89a8-516b09bd4985", "error": { "code": "generation_failed", "message": "Video generation timed out after 5 minutes" }, "timestamp": "2026-03-28T10:05:00Z" } ``` `id` is the event's UUID. Retries resend the same `id` and body, so `timestamp` is when the event was created. Each request also carries these headers: | Header | Value | | --- | --- | | `Masko-Signature` | `t=,v1=.">` | | `Masko-Event-Id` | The event ID, equal to `id` in the body | | `X-Masko-Signature` | `sha256=` followed by the hex HMAC-SHA256 of the raw body | | `X-Masko-Timestamp` | The same unix seconds as `t` | | `X-Masko-Event` | The event name, such as `job.completed` | ## Verify Signatures Verify `Masko-Signature`, which signs the send time with the body so a captured request cannot be replayed. Compute the HMAC-SHA256 of `.` with your secret, compare it with `v1` in constant time, and reject a `t` more than 5 minutes from your clock. Use the exact raw body, never re-serialized JSON. ```javascript import express from 'express'; import crypto from 'node:crypto'; const app = express(); const secret = process.env.MASKO_WEBHOOK_SECRET; if (!secret) throw new Error('Set MASKO_WEBHOOK_SECRET'); const TOLERANCE_SECONDS = 5 * 60; function verifyWebhook(rawBody, header) { const match = /^t=(\d+),v1=([a-f0-9]{64})$/.exec(header || ''); if (!match) return false; const [, t, v1] = match; if (Math.abs(Date.now() / 1000 - Number(t)) > TOLERANCE_SECONDS) return false; const expected = crypto .createHmac('sha256', secret) .update(`${t}.`) .update(rawBody) .digest(); const actual = Buffer.from(v1, 'hex'); return actual.length === expected.length && crypto.timingSafeEqual(actual, expected); } // Register before any app.use(express.json()) middleware. app.post('/api/masko-webhook', express.raw({ type: 'application/json' }), async (req, res) => { if (!verifyWebhook(req.body, req.get('Masko-Signature'))) { return res.sendStatus(401); } const event = JSON.parse(req.body.toString('utf8')); // Store event.id with a unique constraint and skip events you have already seen. // Acknowledge only after your application has accepted the event durably. return res.sendStatus(200); }); ``` ```python import hashlib import hmac import os import re import time from flask import Flask, request app = Flask(__name__) TOLERANCE_SECONDS = 5 * 60 def verify_webhook(body: bytes, header: str, secret: str) -> bool: match = re.fullmatch(r't=(\d+),v1=([a-f0-9]{64})', header or '') if not match: return False t, v1 = match.groups() if abs(time.time() - int(t)) > TOLERANCE_SECONDS: return False expected = hmac.new( secret.encode('utf-8'), t.encode('utf-8') + b'.' + body, hashlib.sha256, ).hexdigest() return hmac.compare_digest(v1, expected) @app.route('/api/masko-webhook', methods=['POST']) def handle_webhook(): is_valid = verify_webhook( request.get_data(), request.headers.get('Masko-Signature', ''), os.environ['MASKO_WEBHOOK_SECRET'], ) if not is_valid: return 'Invalid signature', 401 data = request.get_json() print(f"Event {data['id']}: job {data['job_id']} {data['event']}") return 'OK', 200 ``` `X-Masko-Signature` signs only the body, so it does not protect against replays; prefer `Masko-Signature`. ## Delivery and retries A 2xx response within 10 seconds counts as delivered. Otherwise Masko retries the same event 6 more times, after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours (about 21 hours in total), stopping on success or when the webhook is deleted or disabled. An event can arrive more than once, for example when your response was lost. Deduplicate by event `id` (also in `Masko-Event-Id`), acknowledge promptly after durable acceptance, and reconcile through the [job endpoint](/docs/manage/jobs) if events stop. Each failed attempt adds one to `consecutive_failures` and a success resets it to 0. At 100, Masko disables the webhook (`active: false`); delete it and create a new one to resume. Only jobs started with an API key send webhooks, so [poll](/docs/manage/jobs) jobs started elsewhere, such as in Studio. Payload fields depend on the workflow: rely on `id`, `object`, `event` and `job_id`, and fetch the job for details. ## Manage Webhooks GET /v1/webhooks ```bash curl https://api.masko.ai/v1/webhooks \ -H "Authorization: Bearer masko_YOUR_API_KEY" # Response: # { # "data": [ # { # "id": "wh_abc123", # "url": "https://your-app.com/api/masko-webhook", # "events": ["job.completed", "job.failed"], # "active": true, # "consecutive_failures": 0, # "created_at": "2026-03-28T10:00:00Z" # } # ] # } ``` DELETE /v1/webhooks/:id ```bash curl -X DELETE https://api.masko.ai/v1/webhooks/wh_abc123 \ -H "Authorization: Bearer masko_YOUR_API_KEY" # Response: 204 No Content ``` --- ---url: /docs/manage/collections--- Source: https://masko.ai/docs/manage/collections # Manage mascots and assets Find project, mascot, item and asset IDs, check generation status and get CDN URLs. Create variants and publish a mascot to the Marketplace. `/v1/assets` is shared by both API versions. Send `Masko-API-Version: 2026-09-26`, as in the examples below, to get current field names such as `mascot_id`. ## List projects A project holds one or more mascots. GET /v1/projects ```bash curl https://api.masko.ai/v1/projects \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Masko-API-Version: 2026-09-26" # Response: # { # "data": [ # { # "id": "04dae799-dd9a-4296-ba86-9e3913c2f8d1", # "name": "My SaaS", # "organization_id": null, # "created_at": "2026-01-15T08:00:00Z", # "updated_at": "2026-01-15T08:00:00Z" # } # ], # "meta": { "pagination": { "total": 1, "limit": 50, "offset": 0, "has_more": false } } # } ``` ```javascript const res = await fetch('https://api.masko.ai/v1/projects', { headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY' }, }); const { data: projects } = await res.json(); console.log(projects.map(p => p.name)); ``` ## List mascots Filter by project with the optional `project_id`. GET /v1/mascots ```bash # All mascots curl https://api.masko.ai/v1/mascots \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Masko-API-Version: 2026-09-26" # Filter by project curl "https://api.masko.ai/v1/mascots?project_id=75270a54-f2f9-58e5-83c0-8575c720ca86" \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Masko-API-Version: 2026-09-26" # Response: # { # "data": [ # { # "id": "f9503022-a991-46ea-bf8c-d628c921b6b0", # "project_id": "04dae799-dd9a-4296-ba86-9e3913c2f8d1", # "name": "Fox Mascot", # "type": "mascot", # "is_published": true, # "public_slug": "fox-mascot-a1b2c3d4", # "user_prefix": "fda8417d", # "created_at": "2026-02-01T10:00:00Z" # } # ], # "meta": { "pagination": { "total": 1, "limit": 50, "offset": 0, "has_more": false } } # } ``` ```javascript const params = new URLSearchParams({ project_id: '75270a54-f2f9-58e5-83c0-8575c720ca86', }); const res = await fetch( `https://api.masko.ai/v1/mascots?${params}`, { headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY' }, } ); const { data: mascots } = await res.json(); ``` ## Mascot detail Returns the mascot with its `config`: prompt, reference asset IDs, style card and caution list. GET /v1/mascots/:id ```bash curl https://api.masko.ai/v1/mascots/8bf14263-eabc-58a4-80f6-be9e4f7bceeb \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Masko-API-Version: 2026-09-26" # Response: # { # "data": { # "id": "f9503022-a991-46ea-bf8c-d628c921b6b0", # "name": "Fox Mascot", # "type": "mascot", # "project_id": "04dae799-dd9a-4296-ba86-9e3913c2f8d1", # "slug": "fox-mascot-a1b2c3d4", # "config": { # "prompt": "A friendly fox character for our SaaS", # "reference_asset_ids": ["e906ebb5-deb1-4010-82f9-f0182a3812e0"], # "style_card": null, # "caution_list": [] # }, # "room_for_movement": true, # "cdn_status": [], # "created_at": "2026-02-01T10:00:00Z", # "updated_at": "2026-03-15T16:20:00Z" # } # } ``` ### Room for movement With `room_for_movement` on, new animations are made from the mascot's poses shrunk to 80% of their picture, so jumps, wings and raised arms stay inside the video instead of being cut off at the edge. The mascot looks 20% smaller in each file: show it in a box about 25% bigger to keep the same size. New mascots start with it on; mascots made before it existed start with it off, so their animations keep their size. Change it with `PATCH /v1/mascots/:id`: ```bash curl -X PATCH https://api.masko.ai/v1/mascots/8bf14263-eabc-58a4-80f6-be9e4f7bceeb \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Masko-API-Version: 2026-09-26" \ -H "Content-Type: application/json" \ -d '{ "room_for_movement": true }' ``` A canvas keeps one size: a new move matches the clips already on it, and only an empty canvas follows the setting. An edit keeps the size of the clip it edits. ## List items An item is one pose or asset in a mascot. It can hold several asset types (image, animation, logo). GET /v1/mascots/:id/items ```bash curl https://api.masko.ai/v1/mascots/8bf14263-eabc-58a4-80f6-be9e4f7bceeb/items \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Masko-API-Version: 2026-09-26" # Response: # { # "data": [ # { # "id": "b646b856-5832-49f4-90bf-9c41a50515b3", # "name": "Idle", # "type": "image", # "prompt": "idle pose", # "public_slug": "idle", # "created_at": "2026-02-01T10:05:00Z" # } # ], # "meta": { "pagination": { "total": 1, "limit": 50, "offset": 0, "has_more": false } } # } ``` ```javascript const res = await fetch( `https://api.masko.ai/v1/mascots/${mascotId}/items`, { headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY' }, } ); const { data: items } = await res.json(); const images = items.filter(i => i.type === 'image'); const animations = items.filter(i => i.type === 'animation'); ``` ## Check asset status Lists every asset in a mascot with its `status`, to see which generations are still running. GET /v1/mascots/:id/assets ```bash curl https://api.masko.ai/v1/mascots/8bf14263-eabc-58a4-80f6-be9e4f7bceeb/assets \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Masko-API-Version: 2026-09-26" # Response: # { # "data": [ # { # "id": "e906ebb5-deb1-4010-82f9-f0182a3812e0", # "type": "image", # "status": "completed", # "file_url": "https://storage.googleapis.com/...", # "cdn_url": "https://assets.masko.ai/fda8417d/fox-mascot/idle.png", # "file_size": 245832, # "item_id": "b646b856-5832-49f4-90bf-9c41a50515b3", # "mascot_id": "f9503022-a991-46ea-bf8c-d628c921b6b0", # "created_at": "2026-02-01T10:05:00Z" # } # ], # "meta": { "pagination": { "total": 1, "limit": 50, "offset": 0, "has_more": false } } # } ``` Published assets include `cdn_url`; others include a signed `file_url` when available. `file_size` is the file's size in bytes once it is completed. Add `?include_file_urls=false` to skip signed URLs for metadata-only polling (`cdn_url` stays). ## CDN export JSON Returns the hosted-link JSON shown in **Get Links → Export JSON**. It is built from published CDN assets, so it includes pose items with attached video loops even when the item is not typed as an animation. GET /v1/mascots/:id/cdn-export ```bash curl https://api.masko.ai/v1/mascots/8bf14263-eabc-58a4-80f6-be9e4f7bceeb/cdn-export \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Masko-API-Version: 2026-09-26" # Response: # { # "mascot": "Fox Mascot", # "items": [ # { # "name": "card-sit", # "image": "https://assets.masko.ai/fda8417d/fox-mascot/card-sit.png", # "transparent_image": "https://assets.masko.ai/fda8417d/fox-mascot/card-sit-transparent.png", # "animations": [ # { # "video": "https://assets.masko.ai/fda8417d/fox-mascot/card-sit.mp4", # "transparent_video_webm": "https://assets.masko.ai/fda8417d/fox-mascot/card-sit.webm", # "transparent_video_mov": "https://assets.masko.ai/fda8417d/fox-mascot/card-sit.mov", # "transparent_video_android": "https://assets.masko.ai/fda8417d/fox-mascot/card-sit-android.mp4" # } # ] # } # ] # } ``` The export also has `stickers` (transparent PNG URLs) and `interactions` (complete hosted [cursor followers](/docs/generate/cursor-follower), each with nine image URLs). For IDs, prompts and status, use the items and assets endpoints instead. It returns `409 cdn_export_not_ready` when asset hosting is off or no file is hosted yet. Turn hosting on with **Get Links** or `POST /v1/mascots/:id/publish` ([turn on hosting](/docs/generate/cdn#turn-on-hosting)), or wait for the jobs to finish, then retry. ## Items, assets and deletion - `PATCH /v1/mascots/:id/items/:itemId` - body `{ name?, prompt? }` - `DELETE /v1/mascots/:id/items/:itemId` - `GET /v1/assets` - list your assets with pagination; add `include_file_urls=false` for metadata-only lists - `GET /v1/assets/:id` - get a single asset - `DELETE /v1/assets/:id` - archive an asset (soft delete) ## Mascot variants A variant is a named generation context for one mascot (a different look, personality or habits) with its own approved reference image. Manage them in the mascot page's **Context & References** panel or with these endpoints. Permissions follow the mascot's project: owners, admins and members with project access can manage a teammate's variants. API keys must belong to the mascot's workspace, and generation on a team mascot charges the organization balance. GET /v1/mascots/{id}/variants The paginated response includes `id`, `name`, `description`, `approved_at` (null for a draft), `reference_asset_id` and `canvas_id`. Editing a context affects future generations only. Pass an approved `variant_id` to `POST /v1/mascots/{id}/generate`: ```json { "type": "image", "name": "A curious detective", "variant_id": "VARIANT_UUID", "image_prompt": "Leaning forward with a curious expression." } ``` - Images, animations, edits, logos, backgrounds, and action suggestions accept `variant_id`. Each `/generate-batch` request can pick its own. - Omitted on a new pose, it means Original. Omitted with a source image or video, it means that asset's saved context. It never follows the active playback variant. - A draft variant returns 409 and another mascot's variant returns 404. An ordinary animation whose source does not match the selected variant returns 409 before charging. **Transitions.** Supply `source_image_asset_id` and `end_image_asset_id`; the poses may belong to different variants. The prompt writer uses each pose's saved context, and an upload without a generation receipt uses its associated context (ambiguous ones are rejected). `generation_context.transition` records both endpoints, the writer output and the final prompt. A cross-variant transition has `variant_id: null`, and reversing swaps the roles without a new writer call. **Transition edits.** A `type: "edit"` with `source_video_asset_id` and `edit_instructions` keeps the clip's before and after identities; an explicit `variant_id` must match one of them. The request is recorded under `generation_context.transition_edit`. Video edits take 2 to 15 second sources, keep duration and aspect ratio, open and end on the poses the clip was made between, and cost 5 credits per source second, rounded up. **Canvases.** Use the variant's `canvas_id` with the canvas endpoints. A canvas can mix Original and variant poses from the same mascot; its variant only sets the default generation context. Generating never activates a variant: activate it in the web app once its media is complete. ### Create a variant through v1 1. `POST /v1/mascots/{id}/variants` with a client-generated `id`, `name`, `description` and optional `source_variant_id` (omit for Original). The source context and references are frozen into the draft. Creating a draft is free. 2. `POST /v1/mascots/{id}/variants/{variantId}/reference` with `{ "operation_id": "UUID" }`. Costs 1 credit and returns `job_id` and `candidate_id`. Reuse the operation ID to retry the same job. 3. Poll `GET /v1/jobs/{jobId}`, then read `GET /v1/mascots/{id}/variants/{variantId}` for candidate previews. 4. `POST /v1/mascots/{id}/variants/{variantId}/approve` with `{ "candidate_id": "UUID" }`. Approval does not activate playback or generate media. 5. Pass the `variant_id` to `/generate`. The approved variant also has a `canvas_id`. To skip steps 2 to 4, add `reference_asset_id` in step 1: a completed image from this mascot or your unassigned upload from `POST /v1/upload`, which gets attached to this mascot. The variant is approved at once, at no credit cost. Archived, unfinished and other-mascot images are rejected. Reuse the creation `id` only with the same inputs. Only the draft's creator can generate and approve its candidates. ## Publish to the Marketplace Submit an exact selection of a mascot's identities and assets for review. Once approved, it appears on the Marketplace or in a customer library. 1. `POST /v1/mascots/MASCOT_ID/publication` with `{action:"start"}` creates a listing or starts editing its approved version. Viewing a mascot never creates one. 2. Send `{action:"preview",selection:{asset_ids:[],identity_ids:["original"]}}` to the same endpoint. It returns `plan_id` and an exact preview. 3. Send `action:"submit"` with the same selection and `plan_id` within ten minutes. Changed content returns 409; repeating the same plan returns its existing submission. Admins approve the frozen content; read entitled content with `GET /v1/mascots/MASCOT_ID/published-library?project_id=PROJECT_ID`. For a Marketplace listing, add an offer to the selection: ```json { "offer": { "price_credits": 0, "description": "A sleepy cloud mascot", "accept_creator_terms": true, "cover_asset_id": "SELECTED_ASSET_UUID" } } ``` - Include at least one image and at least 5 animations. The mascot needs at least one completed generated image. - `description` is optional. - The cover must be an included image, not an animation or delivery export. Without `cover_asset_id`, an included generated image is chosen, else the first included image. Changing the cover needs a new preview. ### Mac version Add `"player_canvas_id": "CANVAS_UUID"` to the selection to choose the canvas of this mascot that buyers play on Masko for Mac. Preview and submit freeze its graph as `player` (`canvas_id`, `name`, `graph`, `graph_hash`), and every image and animation it uses is included automatically. The graph must pass validation and use generated animations, not exported formats. - A mascot's first Marketplace offer needs a Mac version. - Leave `player_canvas_id` out to keep the approved Mac version. Removing any media it plays returns 409 `Your Mac version uses media you removed`; select that media again or choose a canvas. - `GET /v1/mascots/MASCOT_ID/publication` returns `player`: the approved Mac version, whether one is `required`, and the mascot's `canvases` with their state and animation counts and any `issue` that blocks them. `GET /v1/mascots/MASCOT_ID/publication` returns gallery assets with transparent image, WebM and HEVC URLs when available, plus `has_generated_image`; formats are not separate selectable assets. Its `listing_preview` is the offer in review, else the approved listing, else `null`. Send `{"action": "withdraw"}` to pull a submission back to draft; any published version stays live. Library publication is separate from public CDN hosting. Approved content stays live while a later submission is in review, and new assets stay private until selected and approved. The Marketplace sells identities and assets, not canvas releases. Buyers get a read-only shared context. Duplicating an identity creates an independent personal variant with copied context and permitted references. ### Listing visibility and reviewed changes `GET /v1/mascots/{id}/publication` returns `state: null` before you start a listing. After that, `state.visibility` is `unpublished`, `published` or `archived`, and `state.changes_status` is independently `draft`, `pending`, `approved` or `rejected`. | Action | Effect | | --- | --- | | `save_draft` with `draft_id` (current draft UUID) and `selection` | Saves work and never publishes. Descriptions may be empty. | | `submit` | Waits for review, admins included. The draft is locked while pending; rejection returns it with reviewer feedback. | | `archive` | Hides the approved listing and stops new acquisitions. Buyers keep access. | | `restore` | Restores only the approved version. Draft or pending changes stay private. | | `remove` with `kind:"asset"` and `resource_id` | Stages removal in a draft. It affects new buyers only after review. | Approval replaces the live selection, name, description, cover and price together, and makes the listing public unless it was archived during review. Existing buyers keep what they acquired. A mascot rename reaches the listing on the next approved submission. Submitting, approval and restore require a creator handle and a public profile. Archive all published mascot libraries before making the profile private or unlisted. ### Personal and organization purchases Buy as Personal or as an organization: each workspace owns its own license, and team credits never fall back to personal credits. Any team member can buy for the organization, ownership stays with it when the buyer leaves, and a repeated team purchase returns the existing ownership without charging. `published-library?project_id=PROJECT_ID` returns purchased content only for that project's workspace. ### Publish as an organization A mascot in an organization project publishes under the organization's profile (name, handle, logo, bio). Owners and admins can edit that profile, manage the listing and submit updates, and sales go to the team's credit wallet. `GET /v1/mascots/{id}/publication` returns `seller.user_id` or `seller.organization_id` (exactly one is non-null) and `seller.can_publish`. The project decides the seller, and submitted or published mascots cannot move to another workspace. --- ---url: /docs/reference/conventions--- Source: https://masko.ai/docs/reference/conventions # Requests and responses Rules shared by every REST endpoint. The [endpoint reference](/docs/reference) has each operation's exact fields and exceptions. ## Base URL The base URL is `https://api.masko.ai/v1`. Reference paths already include `/v1`, so append them to `https://api.masko.ai` without doubling it. Send `Authorization: Bearer $MASKO_API_KEY` on protected requests and `Content-Type: application/json` for JSON bodies (multipart uploads use their documented type). See [API keys and workspaces](/docs/authentication). ## Response envelopes Successful JSON responses put the result in `data`: ```json { "data": { "id": "RESOURCE_ID", "name": "My mascot" } } ``` Errors return `error.code`, `error.message` and optional `error.details`, so check the HTTP status before reading `data` ([errors and recovery](/docs/reference/errors)). A `204` has no body, `/v1/openapi.json` returns the specification directly, and `/v1/docs` returns documentation text. ## Request IDs and versions Every response carries a `Request-Id` header such as `req_3f9c...` (repeated as `error.request_id`; include it when you contact support) and the `Masko-API-Version` it used. Routes shared with the older API version, such as `/v1/jobs`, `/v1/assets` and `/v1/canvas-templates`, return older field names unless you send `Masko-API-Version: 2026-09-26` or `?api_version=2026-09-26`. ## Pagination `limit` is 1 to 100 (default 50). While `meta.pagination.has_more` is true, pass `meta.pagination.next_cursor` back exactly as `cursor`. `offset` also works; send either `cursor` or `offset`, not both. ```json { "data": [], "meta": { "pagination": { "total": 120, "limit": 50, "offset": 0, "has_more": true, "next_cursor": "c1_eyJvIjo1MH0" } } } ``` ## Generation receipts Generation requests normally return `202 Accepted` with a job receipt: ```json { "data": { "job_id": "JOB_ID", "poll_url": "/v1/jobs/JOB_ID" } } ``` Save the receipt, then poll `GET /v1/jobs/JOB_ID` until `completed` or `failed`, or use a [webhook](/docs/manage/webhooks). Some `poll_url` values are masko.ai paths (`/api/v1/...`); on `https://api.masko.ai`, build `https://api.masko.ai/v1/jobs/JOB_ID?api_version=2026-09-26` from the ID, as in the [quickstart](/docs/quickstart). Batch responses contain several jobs, as documented per endpoint. `?wait=true&timeout=120` long-polls up to 120 seconds and returns the current state on timeout without cancelling. A file URL can exist before its file is ready, and derived formats can finish after the parent job, so check the required assets before playback; see [jobs and polling](/docs/manage/jobs). ## Costs and retries Always send `animation_model`: `"standard"` costs 2 credits per second and `"premium"` 6, while leaving it out bills the older rate of 5 ([credits and costs](/docs/credits)). A client timeout does not cancel accepted generation. Authenticated resource `POST` endpoints accept `Idempotency-Key`, except `/v1/auth/*` and `/v1/admin/*`. Send a unique value of up to 255 characters, ideally a UUID, and save it with the request before sending: ```bash curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/generate \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Idempotency-Key: 5f2c0b1e-8d3a-4c6f-9b2e-1a7d4e9c0f11" \ -H "Content-Type: application/json" \ -d '{"type": "image", "name": "Wave", "image_prompt": "waving hello"}' ``` - A key belongs to your account and binds the credential workspace, API version, URL, content type and exact body bytes, including multipart boundaries. Reusing it with different inputs returns `409 idempotency_key_reused`. - Once execution starts, its response (`5xx` included) is stored for 24 hours after completion. A retry returns it with `Idempotent-Replayed: true`, without running or charging again; each attempt still gets its own `Request-Id`. - Replays need a valid write credential and current access to the original resources, or return `403`. - A running request returns `409 idempotency_request_in_progress`. If its outcome cannot be saved, the first response is `503 idempotency_persistence_failed`, later ones may be that `409`, and the key stays blocked even after 24 hours. Retry with the same key; check the saved job or contact support if it persists. - A stored `500` does not prove nothing was written, so check the outcome before switching keys. After a completed receipt expires, reusing the key starts a new request. Without the header, every `POST` runs as a new request. --- ---url: /docs/reference/errors--- Source: https://masko.ai/docs/reference/errors # Errors and recovery Every error returns an `error` object with a stable `code`. Find the code below to see how to fix the request or retry safely. ## Error Response Format - `code`: machine-readable and stable. Switch on this. - `message`: human-readable. It may change between versions, so use it for logs only. - `param`: the request field that caused the error, when there is one. - `details`: extra context, such as `issues` for validation or `required` and `balance` for credits. - `doc_url`: this page, at the section for the code. - `request_id`: the same value as the `Request-Id` header. Include it when you contact support. ```json { "error": { "code": "validation_failed", "message": "duration: Number must be less than or equal to 15", "param": "duration", "details": { "issues": [{ "param": "duration", "message": "Number must be less than or equal to 15" }] }, "doc_url": "https://masko.ai/docs/reference/errors#validation-failed", "request_id": "req_3f9c2a71d0b84e6a9c1f5e22" } } ``` Successful responses use `{ "data": ..., "meta"?: ... }` instead; see [requests and responses](/docs/reference/conventions). ## Error Codes Some operations return their own codes: a 409 for an operation-specific conflict, and a 422 when a proposed graph is invalid. Correct the graph rather than retrying generation blindly. ### Validation failed `validation_failed` (400). The body or query is invalid. Fix the fields named in `param` and `details.issues`, then resend. ### Unauthorized `unauthorized` (401). The API key is missing, invalid or revoked. Send `Authorization: Bearer masko_...` with an active key. ### Insufficient credits `insufficient_credits` (402). `details.required` and `details.balance` give the numbers. Top up at [masko.ai/billing](https://masko.ai/billing) or reduce the request. ### Forbidden `forbidden` (403). The key lacks permission, for example a read-only key sending a `POST`. Use a key with the right permission. ### Not found `not_found` (404). The resource does not exist or is outside the credential's workspace. Check the ID with a list endpoint. ### Conflict `conflict` (409). A revision, readiness or active-run conflict. Read the resource and `details` again, then retry with fresh values. With `details.reason: "voice_required"`, the mascot needs a voice for a talking animation: set one with `PUT /v1/mascots/{id}/voice`. ### Idempotency key reused `idempotency_key_reused` (409). This account already used the key with a different workspace, API version, URL, content type or body. Restore the original inputs to retry, and use a new key only for an intentionally new operation. ### Idempotency request in progress `idempotency_request_in_progress` (409). A request with this key is running or its outcome could not be confirmed. Wait a few seconds and retry with the same key, since a new key could duplicate a committed write. Unconfirmed operations stay blocked after 24 hours; check the saved job or contact support. ### Idempotency persistence failed `idempotency_persistence_failed` (503). The operation ran and may have committed, but its replay receipt could not be saved. Retry only with the same key, and inspect the saved job or contact support before starting a new operation. ### Invalid idempotency key `invalid_idempotency_key` (400). The `Idempotency-Key` header must be 1 to 255 characters. ### Rate limited `rate_limited` (429). Too many requests. Back off before retrying, as shown in [rate limit recovery](#rate-limit-recovery). ### Upstream error `upstream_error` (502, 503, 504), or `suggestion_failed` (502). A provider Masko depends on failed. Accepted jobs keep running: recover the job, or retry later through a supported idempotent replay. ### Internal `internal` (500). Something failed on our side, and a write may have committed. Retry with the original idempotency key (a stored error replays unchanged) and check the outcome before starting a new request. Contact [support](mailto:paul@masko.ai) with `request_id` if it persists. ## Handling Insufficient Credits Show the user how many credits are missing: ```javascript // HTTP 402 { "error": { "code": "insufficient_credits", "message": "Not enough credits. Required: 140, balance: 50.", "details": { "required": 140, "balance": 50 } } } ``` ```javascript const res = await fetch( 'https://api.masko.ai/v1/mascots/MASCOT_ID/generate', { method: 'POST', headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY', 'Masko-API-Version': '2026-09-26', 'Content-Type': 'application/json', }, body: JSON.stringify({ type: 'animation', name: 'Dancing', image_prompt: 'dancing pose', animation_prompt: 'dancing', animation_model: 'standard', duration: 5, }), } ); if (res.status === 402) { const { error } = await res.json(); const needed = error.details.required - error.details.balance; console.log(`Need ${needed} more credits. Top up at masko.ai/billing`); return; } ``` ## Rate Limit Recovery Back off exponentially, and honor the `Retry-After` header when present. ```javascript async function fetchWithRetry(url, options, maxRetries = 3) { for (let attempt = 0; attempt <= maxRetries; attempt++) { const res = await fetch(url, options); if (res.status !== 429) { return res; } if (attempt === maxRetries) { throw new Error('Rate limited after max retries'); } // Use Retry-After header, or exponential backoff const retryAfter = res.headers.get('Retry-After'); const seconds = retryAfter === null ? NaN : Number(retryAfter); const dateDelay = retryAfter ? Date.parse(retryAfter) - Date.now() : NaN; const delay = Number.isFinite(seconds) ? Math.max(0, seconds * 1000) : Number.isFinite(dateDelay) ? Math.max(0, dateDelay) : Math.pow(2, attempt) * 1000; console.log(`Rate limited. Retrying in ${delay / 1000}s...`); await new Promise((r) => setTimeout(r, delay)); } } // Usage: const res = await fetchWithRetry( 'https://api.masko.ai/v1/mascots/MASCOT_ID/generate', { method: 'POST', headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY', 'Masko-API-Version': '2026-09-26', 'Content-Type': 'application/json', }, body: JSON.stringify({ type: 'image', name: 'Hello', image_prompt: 'waving hello', }), } ); ``` A rejected 429 is safe to retry. A timed-out paid write is not: check the saved job or run first, and replay only through endpoints that support `Idempotency-Key`. --- ---url: /docs/terminal/commands--- Source: https://masko.ai/docs/terminal/commands # Command line reference Run `masko help`, `masko help commands` or `masko COMMAND --help` for the same text offline. Every command accepts `--json`. ## Account | Command | Result | | --- | --- | | `masko login` | Sign in through your browser. Options: `--no-browser`, `--device`, `--api-key`, `--api-key-stdin`, `--scope read\|write`, `--name NAME`, `--timeout SECS`. See [CLI login](/docs/authentication/cli). | | `masko whoami` | Show your account, workspace name, access (read or write), sign-in method and credits. | | `masko credits` | Show total, subscription and top-up credits. | | `masko feedback "TEXT"` | Send feedback to the Masko team. Pipe longer notes in: `cat notes.txt \| masko feedback`. The first line is the summary (up to 500 characters), 8,000 characters in all; 5 a minute and 30 a day. Read-only logins work. | | `masko logout` | Sign out on this Mac and revoke the session. If Masko cannot be reached, the login is still removed here; revoke it under **Signed-in computers**. An API key set in `MASKO_API_KEY` still applies. | `masko login --api-key` asks for a key at a hidden prompt; `--api-key-stdin` reads it from stdin, for automation. Neither creates a key: get one at [API keys](https://masko.ai/api-keys). ## Masko API ```bash masko studio METHOD PATH [--file FILE] [--idempotency-key KEY] [--json] ``` | Part | Use | | --- | --- | | `METHOD` | `GET`, `POST`, `PUT`, `PATCH` or `DELETE`. | | `PATH` | A `/v1` path from the [API reference](/docs/reference), such as `mascots`, `jobs/JOB_ID?wait=true` or `canvases/CANVAS_ID/generate-all`. `/v1/...` and full `https://api.masko.ai/v1/...` links, such as a job's `poll_url`, work too. Quote paths with `?` or `&`. Sign-in (`auth`), `admin` and the old `collections` paths are refused. | | `--file FILE` | The JSON request body, or `-` to read it from stdin. For `upload`, an image or video file instead: PNG, JPEG, WebP, GIF, SVG, MP4, MOV or WebM. | | `--idempotency-key KEY` | Every POST sends one. Reuse the key a failed or interrupted POST printed, and Masko never runs or charges it twice. | Requests send `Masko-API-Version: 2026-09-26`, so field names match the docs and the MCP server (`mascot_id`, not `collection_id`). The output is the API's response: `data`, plus `meta.pagination` (`total`, `has_more`, `next_cursor`) for lists. A `DELETE` that returns nothing prints `Done.` on stderr. To see a canvas's cost before generating, call `GET canvases/CANVAS_ID/generation-estimate`, or `generate-all` with `"dry_run": true`, then pass `max_credits`. Talking animations take `"dry_run": true` too; see [talking](/docs/generate/talking). ## Assets | Command | Result | | --- | --- | | `masko assets show ASSET_ID` | Show one asset. | | `masko assets download ASSET_ID --output PATH` | Save one asset as the new file `PATH`, in a folder that exists. `--format` selects `original` or the asset type. Existing files are never overwritten. | To list a mascot's assets, use `masko studio GET 'assets?mascot_id=MASCOT_ID'`. ## Output and errors | Exit code | Meaning | | --- | --- | | `0` | Done. | | `1` | An error. With `--json`, it is printed on stdout as `{"error": {"code", "message", ...}}`; otherwise on stderr. | | `124` | Timed out. | | `130` | Stopped with Control+C. Work Masko already accepted may continue; a POST prints its idempotency key. | Errors keep the API's fields: `status`, `param` (the field that was wrong), `request_id` (give it to support), `doc_url`, `retry_after` and, for a POST, `idempotency_key`. Unknown commands suggest the closest one. See [errors](/docs/reference/errors). ## Environment and global options | Setting | Use | | --- | --- | | `--api-url URL` or `MASKO_API_URL` | Select the backend, for local development. | | `MASKO_API_KEY` | Use an API key. It overrides the saved login. An empty value counts as unset. | | `MASKO_NO_UPDATE_CHECK=1` | Turn off the daily notice that a newer version is out. It is also off in `CI` and with `--json`. | | `--json` | Print JSON on stdout. | | `--help`, `-h` | Read help without connecting or changing anything. | | `--version`, `-v`, `masko version` | Print the CLI version. | --- ---url: /docs/sdk/typescript--- Source: https://masko.ai/docs/sdk/typescript # TypeScript SDK Call the Masko API from Node.js or Electron main on macOS. The SDK runs the `masko` command for sign-in and requests, so it uses your saved login. It does not display mascots. ## Install ```bash npm install @masko/sdk ``` [`@masko/sdk`](https://www.npmjs.com/package/@masko/sdk) 0.3.0 needs Node.js 20+, an Apple Silicon Mac with macOS 14+, and the `masko` command 0.4.0 or later. Install it with the [terminal setup](/docs/terminal), or bundle it with your app. With an older command, calls fail with `sdk_update_required` and the install command to run. ## Use ```typescript import { Masko } from '@masko/sdk'; const client = Masko.client({ client: 'my-app/1.0' }); // Reuses your saved login. const credits = await client.credits(); const { data: mascots, meta } = await client.studio('GET', 'mascots?limit=50'); const job = await client.studio('POST', 'mascots/MASCOT_ID/generate', { type: 'animation', item_id: 'ITEM_ID', animation_model: 'standard', // 2 credits a second animation_prompt: 'waves hello' }); ``` ## Methods | Call | Result | | --- | --- | | `Masko.client({ apiURL?, apiKey?, runtimePath?, client? })` | Creates a client. An `apiKey` stays in memory. `client` names your app to Masko. | | `client.login({ noBrowser?, device?, apiKey? })` | Signs in with a browser or device code, or validates and saves an API key. | | `client.logout()` | Signs out on this Mac and revokes the saved session. | | `client.whoami()` | Returns the account, workspace name, access and credits. | | `client.credits()` | Returns the credit balance: total, subscription and top-up. | | `client.studio(method, path, body?, { idempotencyKey? })` | Calls a Masko API path with GET, POST, PUT, PATCH or DELETE. Returns `{ data, meta? }`. | | `client.feedback(text)` | Sends feedback to the Masko team. The first line is the summary. | | `client.assets.show(id)` | Returns one asset. | | `client.assets.download(id, { output })` | Saves one asset as the new file `output`, in a folder that exists. It never overwrites a file. | `studio()` takes the API's `/v1` paths, such as `mascots`, `jobs/JOB_ID?wait=true` or `credit-checkouts`; a job's `poll_url` works too. Sign-in, admin and the old `collections` paths are rejected before any request is sent. It returns the API's response: `data`, plus `meta.pagination` for lists, where `next_cursor` gets the next page. See the [API reference](/docs/reference). Every POST sends an idempotency key. To retry a failed POST without running or charging it twice, pass the key from its error as `idempotencyKey`. ## Keys and errors Use this SDK on a server, or in trusted tooling. Never put an API key in a shipped app or a public repository. The SDK sends an in-memory key to the runtime over stdin, never as a command argument. Errors are `MaskoError` objects with `code`, `message` and the API's `details` (for example `required` and `available` credits). When the API answered they also carry `status`, `requestId`, `docUrl` and `param`; a failed POST carries its `idempotencyKey`. Account and API calls need the macOS runtime, so call them from Electron main or Node.js, never from a browser page or renderer. ## Upgrading - **From 0.2.0:** `studio()` returns `{ data, meta }` instead of `data` alone, so read `.data`. Assets and jobs use `mascot_id` instead of `collection_id`. Update the `masko` command to 0.4.0 at the same time. - **From 0.1.0:** version 0.2.0 removed the on-screen API (`Masko.mascot`, `Masko.connect`, `Masko.playback`, and `show`, `state`, `play`, `position`, `resize`). Floating mascots run in [Masko for Mac](/docs/integrations/desktop). ## Next steps Read the [quickstart](/docs/quickstart) for the REST calls behind these operations, and the [authentication guide](/docs/authentication) for API keys and workspaces. --- ---url: /docs/reference/changelog--- Source: https://masko.ai/docs/reference/changelog # Changelog Changes to the Masko API are recorded here, newest first. Breaking changes are flagged clearly with a migration note. Read the notes for any version that is newer than when you last integrated, then update your REST client against the current OpenAPI specification. --- ## Animated WebP exports and short sticker profiles (October 30, 2026, additive) `POST /v1/assets/{id}/exports` with `{"format":"animated_webp","size":512}` makes a transparent, looping, lossy animated WebP at any width from 32 to 1920, with no file-size cap. Before, `animated_webp` was refused without a sticker profile. `sticker_profile` now picks the chat app and accepts `telegram`, `whatsapp`, `wechat` and `discord`; a name without `-v1` means the current version. `format` can be left out with a profile, or must be its file type, such as `{"format":"animated_webp","sticker_profile":"whatsapp"}`. A wrong pair now names the right one, for example `telegram stickers are webm, not animated_webp; use sticker_profile whatsapp or no sticker_profile for a plain animated_webp`. Every request that worked before gives the same result. See [media exports](/docs/manage/media-exports). ## Scenes become backgrounds (October 30, 2026, breaking) `type: "scene"` on `POST /v1/mascots/{id}/generate` is replaced by `type: "background"`: an empty place in the mascot's own style, with no mascot in it, calm areas for your title and button, and a spot where its animation plays on top. `purpose` says what it is for, a preset such as `splash` or `paywall` or your own words, and sets the shape; `place` overrides the shape (`phone_screen`, `phone_header`, `square`, `web_hero` or `banner`) and `description` optionally describes the place. `GET /v1/mascots/{id}/backgrounds/suggestions?purpose=...` returns five free place ideas from the mascot's world. To change a background, send its `source_image_asset_id` with another `place`, `edit_instructions`, or both. It costs 3 credits, like a scene. `type: "scene"` now returns `400` with a pointer to `background`, and `GET /v1/mascots/{id}/scenes/suggestions` is gone. Jobs are `background_generation`. The MCP server adds `suggest_backgrounds` and `make_background`. Scenes made before stay as pictures of your mascot. Migration: send `type: "background"` with a `place`; to show your mascot in the picture, play its animation or place its pose on top. See [backgrounds](/docs/generate/design-assets#background). ## masko CLI 0.4.0 and TypeScript SDK 0.3.0 (October 28, 2026, breaking for the CLI and SDK) `masko studio` now matches the REST API and the MCP server. It sends `Masko-API-Version: 2026-09-26`, so assets and jobs answer with `mascot_id` instead of `collection_id`. It prints the API's whole response, `{data, meta}`, so lists show `meta.pagination` instead of stopping at 50 (read `.data` where you read the array; `studio()` in the SDK returns the same). Every public path works except sign-in, admin and `collections`, `PUT` is allowed, and a job's `poll_url` works as a path. `masko studio POST upload --file image.png` uploads a local file. Every POST sends an idempotency key, which `--idempotency-key` (SDK: `idempotencyKey`) reuses to retry without paying twice. Errors keep `request_id`, `doc_url` and `param`. `masko whoami` shows the workspace name, and `masko feedback` sends feedback. SDK 0.3.0 needs the 0.4.0 command and says how to update an older one. The installer adds `~/.local/bin` to your PATH. See the [terminal quickstart](/docs/terminal) and the [command reference](/docs/terminal/commands). ## Workspace names, Claude Code sign-in and MCP voices (October 28, 2026, additive) `GET /v1/auth/session` adds `organization_name`, the team workspace name, or null for a personal workspace. Claude Code and opencode can sign in to the MCP server through the browser, without an API key. The MCP server adds `get_account`, and voices and talking animations: `get_mascot_voice`, `suggest_mascot_voices`, `sample_mascot_voices`, `keep_mascot_voice` and `make_talking_animation`. `get_release_delivery` takes `apple` and `android` targets and `formats: av1`. See the [MCP server](/docs/mcp). ## Size variant backfills add only missing files, AV1 included (October 27, 2026, behavior change) `POST /v1/mascots/{id}/size-variants` with `{}` now makes every file an animation lacks, format by format: WebM, HEVC, stacked MP4 and stacked AV1, at the animation's own width and at each size. Before, it skipped a size once its WebM and HEVC files existed, so animations made before AV1 never got their AV1 files. Files that exist are never remade and keep their links. `force: true` still remakes every sized file. ## Send feedback to the Masko team (October 27, 2026, additive) `POST /v1/feedback` and the MCP tool `send_feedback` send feedback to the Masko team about a Masko feature, the MCP server, the REST API or the docs. Only `summary` is required; `feedback_type`, `sentiment`, `friction_points`, `suggested_improvement`, `details`, `task_completed`, `resource_id` and `agent` are optional. Read-only keys and connections can send it. Each account can send 5 a minute and 30 a day; more returns `429 rate_limited`. The MCP server now also sends instructions to every app that connects, telling agents when to use it. See [send feedback to Masko](/docs/ai-agents#send-feedback-to-masko). ## File sizes in job results, assets and webhooks (October 27, 2026, additive) `GET /v1/jobs/{id}` and the `job.completed` webhook add `file_sizes`: each file's size in bytes, under the same key as its link in `urls`, or null when the file cannot be read. Every asset (`GET /v1/assets`, `GET /v1/mascots/{id}/assets`, `GET /v1/assets/{id}`) adds `file_size`, in bytes once the file is completed. It gives a quick idea of what a mascot weighs before you download anything. ## AV1 files for app players (October 27, 2026, additive) Every animation made from now on also gets a stacked AV1 file (`stacked_video_av1`): colour above, transparency below, like the stacked MP4, two to three times lighter than HEVC or the stacked MP4 at the same look. `GET /v1/canvases/{canvasId}/releases/{releaseId}/delivery` takes `formats=av1`: with `target=apple` or `target=android`, each clip that has the file comes as `videos.av1` in place of `hevc` or `stacked`. Without `formats`, and with other targets, deliveries are unchanged. Masko player 0.1.6 plays it on Apple devices (with the device's AV1 chip or dav1d inside the player) and on Android phones with an AV1 decoder, and gives the value to send as `MaskoPlayer.formats`; React Native gets it as `maskoFormats()` in 0.1.7. The CDN export lists the file as `transparent_video_av1`. Animations made earlier keep their files. ## The Masko player plays clips' own voices by default (October 26, 2026, behavior change) Masko player 0.1.3 plays a clip's own voice (`hasAudio`) unless your app turns sound off: `sound` is now on by default, and `sound: false` (`setSound(false)` on the web) mutes it. Browsers allow sound only after the person's first click or key press on the page; until then the player plays muted, and the voice is heard from the next clip or loop. One animation played with `clip` plays its file's own voice too. To keep 0.1.2's silence, pass `sound: false`. ## The Masko player plays one animation from its files (October 25, 2026, additive) Masko player 0.1.2 plays one looping animation without a canvas: give it the animation's hosted files in place of a delivery, `clip: { webm, hevc }` on the web and in React, `MaskoPlayer(clip:)` with the HEVC file on Apple, `loadClip` with the stacked MP4 on Android, and `clip: { hevc, stacked }` in React Native. It plays the file each platform shows with transparency and loops it. See [play one animation](/docs/player/one-animation). Releases play as before. ## Release ids and clip voices in deliveries (October 25, 2026, additive) `GET /v1/canvases/{canvasId}/releases/{releaseId}/delivery` and `GET /v1/canvases/{canvasId}/export?delivery=1` write `release.id`, a fingerprint of the canvas that plays, without its links: a fresh delivery with the same id only renews the links. A clip with its own voice carries `hasAudio: true`, so a player with sound on unmutes it; a release compiled earlier gets it the next time its mascot version is compiled. Empty condition groups and sounds without a link are left out, and manifest size variants list only the formats of the requested `target`. Players of contract 1 need no change. ## Turn on hosting with the API (October 23, 2026, documentation) `POST /v1/mascots/{id}/publish` is now in the OpenAPI specification and the docs: it turns on asset hosting, the same switch as **Get Links** in Studio, so a mascot made in Studio or through the MCP server gets permanent public links. See [turn on hosting](/docs/generate/cdn#turn-on-hosting). The call itself is unchanged. The `409 cdn_export_not_ready` message from `GET /v1/mascots/{id}/cdn-export` now says whether hosting is off or no file is hosted yet, and names this call. The status, code and `details` are unchanged. ## .mkpk exports for ESP32 boards (October 23, 2026, additive) `POST /v1/assets/{id}/exports` accepts `format: "mkpk"`: one animation as a `.mkpk` pack, the format the ESP32 Masko SDK plays. `size` is required and is the exact frame width, a multiple of 16 from 32 to 1024. The new optional `mkpk` object sets `screen_width` and `screen_height` (together; the frames are centred on that screen), `fps` (default 24), `quality` (default 75) and `background_color` (default `#000000`). Export receipts for this format include the complete settings under `mkpk`. Mascot settings accept `publish_params.mkpk_exports` to give every future forward animation one pack with the same settings. Existing formats and fields are unchanged. ## Release delivery for players (October 23, 2026, additive) `GET /v1/canvases/{canvasId}/releases/{releaseId}/delivery` and `GET /v1/canvases/{canvasId}/export?delivery=1` accept `target=apple`, which returns only the HEVC links an iPhone, iPad or Mac app plays, and `target=android`, which returns only the stacked MP4 links (colour above, transparency below) an Android app plays. Releases made from now on carry the stacked files; an Android delivery of an older release has no clip links, so release the canvas again. An edge can set `speedInput`, the name of a `number` input its clip speed follows (speed times the value the app sets, kept between 0.2 and 4), through `PATCH /v1/canvases/{canvasId}/edges/{edgeId}`; releases carry it. Every delivery now carries `playerContract` (the version of the playback rules, `1`) and `requires` (the graph features it uses: `any-state`, `condition-groups`). A player refuses a delivery whose `playerContract` it does not know or whose `requires` names a feature it does not implement. Existing targets and fields are unchanged. The [JSON Schema](https://masko.ai/schemas/mascot-release-delivery.schema.json) describes the whole envelope. ## Silent Standard animations (October 21, 2026) Standard animations no longer carry a soundtrack. The video model invented one for every clip, so the video, WebM, HEVC and stacked files played sounds nobody asked for. New animations have no audio track, and every picture frame is unchanged. Talking animations keep their voice. Animations made before this date keep their sound. ## Applications removed (October 21, 2026, breaking) `GET /v1/applications`, `POST /v1/applications`, `GET /v1/applications/{id}` and `POST /v1/applications/{id}/keys` are removed and return `404`. Application secret keys (`masko_sk_`) have had no endpoint that accepts them since playback sessions were removed on September 27, 2026, and no account served a mascot through one. **Migration:** call the REST API from your backend with an [API key](/docs/authentication), and connect AI apps through the [MCP server](/docs/mcp). Your app plays exported files or [hosted links](/docs/generate/cdn) and needs no Masko credential. ## Shorter Standard animations (October 18, 2026, additive) Under API version `2026-09-26`, `animation_model: "standard"` accepts 2 to 15 whole seconds instead of 5 to 15, still at 2 credits per second, so a 3 second animation costs 6 credits. Requests that worked before are unchanged. ## Voices for every account (October 10, 2026) Under API version `2026-09-26`, every account can add a voice. The voice calls (`suggestions`, `adjust`, `samples` and keeping a voice with `PUT`) no longer return `403` with `details.reason: "voice_locked"`; request and response shapes are unchanged. ## Mascots from a brand book (October 9, 2026, additive) Under API version `2026-09-26`. - New `POST /v1/mascots/from-brand-book`: send a brand book as public links (a PDF up to 50 MB, or page images) or uploads, at most 30 pages, and one job reads it for free, draws every mascot view and prop for 1 credit each (at most 17), and creates the mascot with its references, description, colors, style, don'ts and props. Optional `max_credits` caps the spend. Nothing is charged when the brand book has no mascot, costs more than the cap, or the balance is short. - Jobs of type `mascot_brand_book` report `step` while running and `result` with `mascot_id` once completed. - `POST /v1/upload` accepts JSON `{ "content_type": "application/pdf", "size": BYTES }` and returns a signed `upload_url` to PUT a PDF of up to 50 MB straight to storage. The returned `asset_id` works in `files` of `POST /v1/mascots/from-brand-book`. ## Talking animation price reduction (October 6, 2026) Under API version `2026-09-26`, talking animations cost 2 credits per second instead of 3. One minute costs 120 credits. Estimates and refunds use the reduced rate; request and response shapes are unchanged. ## Talking animations up to 5 minutes (additive) Under API version `2026-09-26`. - A talking animation now holds a talk of up to 5 minutes of video, instead of 2 minutes. A talk longer than 5 minutes returns `400`. - `speech.script` accepts up to 15,000 characters and `speech.text` up to 7,500. ## Talking animations up to 2 minutes (additive) Under API version `2026-09-26`. - A talking animation now holds a talk of up to 2 minutes of video, instead of about 14 seconds of speech. A talk longer than 15 seconds is recorded in one go and cut in its real pauses into takes that start and end on the start image, then joined into one video. The voice stays as recorded; the video model can lengthen the pause at a cut by a fraction of a second, and the transcript's times include it. - `speech.script` accepts up to 6,000 characters and `speech.text` up to 3,000. A talk longer than 2 minutes returns `400`. - Movements are performed in place: a move toward the viewer, such as leaning in close, no longer changes the framing, and spoken words are never drawn on screen. ## Talking animations (additive) Under API version `2026-09-26`. Adding a voice unlocks once the account has spent $50 on Masko; until then the voice design calls return `403` with `details.reason: "voice_locked"`. - `POST /v1/mascots/{id}/generate` accepts `speech` with `type: "animation"`: the mascot says a line in its voice, with movements written in square brackets. Costs 3 credits per second; the request charges the longest the line could need and refunds unused seconds once the voice is recorded. `dry_run: true` returns the estimate for free. The receipt adds `estimate`, `script`, and `audio` and `transcript` asset IDs. - New voice endpoints under `/v1/mascots/{id}/voice` and `/v1/mascots/{id}/variants/{variantId}/voice`: get, keep (`PUT`), remove, `suggestions`, `adjust`, and `samples` (1 credit for three). - New asset types `audio` (MP3) and `transcript` (JSON). Video metadata of talking animations reports `has_audio: true`. See [talking animations](/docs/generate/talking). ## Generation response privacy Public job and asset responses now omit internal generation execution details for all API versions and credentials. Customer-authored mascot descriptions, styles, references, requested edits and public generation tiers remain available. Stored generation records are unchanged. **Migration:** remove the former `include=prompts` query option; it no longer changes the response. Do not depend on raw execution fields in job `input_data`, `output_data` or asset metadata. Mask-refinement quality is now returned as `original` or `pro`; older input values are still accepted. Use job IDs and request IDs when contacting support about failures. ## Direct image animation Available in the production OpenAPI specification as of September 28, 2026, under API version `2026-09-26`. - New `POST /v1/animations` animates an uploaded image under an existing `mascot_id`, or explicitly creates a mascot via `create_mascot: {project_id, name?}` in the same request. An omitted mascot name is supplied by AI; no character description is generated. Returns the mascot ID for reuse alongside the job receipt. Defaults to Standard, 5 seconds, 10 credits. Supports `Idempotency-Key` across mascot creation and generation. - **Input-framing behavior change:** existing horizontal and vertical image sources are now center-cropped to square **before** animation, including item-only sources and transition end frames. Previously the full image reached the video model and only the resulting animation was cropped. Square inputs are unchanged. Originals remain intact and cropping adds no credits. To retain a specific framing, upload a prepared square image or set the new `source_image_crop` / `end_image_crop` pixel rectangles. See [image framing](/docs/generate/animations#image-framing). - Animate an unattached image from `/v1/upload` directly under an existing mascot, without generating another pose or changing mascot references. The source import preserves the original file and selected variant context. Only animation credits are charged. ## How versions work The API path stays `/v1`. Each set of changes is named by a date, and the current version is **2026-09-26**. Send it as the `Masko-API-Version` header (or `api_version` query parameter); every response returns the version it used in the same header. Additive changes, such as new fields, endpoints or optional headers, ship inside the current version. A change that could break a working client gets a new date and a migration note here. The OpenAPI specification's `info.version` always equals the current version. ## Media exports (additive) Available in the production OpenAPI specification as of September 28, 2026, under API version `2026-09-26`. - New POST/GET `/v1/assets/{id}/exports` create and list image/video exports, with reusable jobs, signed downloads, resizing and optional regeneration. - Mascot settings add `publish_params.image_exports` for PNG/WebP versions and sizes. New POST `/v1/mascots/{id}/exports` explicitly backfills saved settings. - POST `/v1/projects/{id}/export-settings` applies settings to current project/folder mascots, with optional backfill. Existing video routes and settings remain compatible. - Image exports preserve source files, transparency and aspect ratio, without upscaling or generation credits. See [media exports](/docs/manage/media-exports). ## 2026-09-26 Deployed on 2026-09-27. The API contract date remains `2026-09-26`. ### Mascots and canvases - Canonical routes `/v1/mascots` and `/v1/canvases` return `mascot_id`. Existing `/v1/collections` routes keep working with their legacy fields and are marked deprecated. See the [migration guide](/docs/manage/studio-migration). - New `GET /v1/canvases/{canvasId}/status`: generation progress with `generation` (ready plus node and edge counts), `preview`, `variants`, `failed_nodes` and `failed_edges`. - Canvas responses contain only public fields: `object`, `id`, `project_id`, `mascot_id`, `name`, `graph`, `graph_content_hash`, `created_at`, `updated_at`. - `GET /v1/canvases` pages like other lists when you send `limit`, `offset` or `cursor`, and returns `400` with `param: "project_id"` when `project_id` is missing. - `generate-all` returns `poll_url` on each job, and `409 appearance_unavailable` when a mascot has no published appearance to generate with. - **Breaking in this version:** the hosted character registry and its plans, runs, versions and playback issuer are retired. Existing credentials and installed releases are preserved. See [terminal setup](/docs/terminal). ### Conventions (additive) - Every response has a `Request-Id` header and the `Masko-API-Version` it used. - Errors add `doc_url`, `request_id` and, when a field is at fault, `param`. Validation errors list every problem in `details.issues`. Existing `code` values are unchanged. See [error codes](/docs/reference/errors). - Standard authenticated resource `POST` endpoints accept `Idempotency-Key` (`/v1/auth/*` and `/v1/admin/*` are excluded). Completed results, including `500` errors, replay for 24 hours with the same workspace, API version and exact request bytes. See [conventions](/docs/reference/conventions). - Lists return `meta.pagination.next_cursor`; pass it back as `cursor`. `offset` keeps working. - Resources carry an `object` field naming their type, for example `"object": "mascot"` or `"object": "job"`. - Error messages on mascot routes say "Mascot not found" instead of "Collection not found". The `not_found` code is unchanged. ### Retry fixes - Both canonical and legacy collection canvas DELETE return `409 conflict` when checkpoints or releases retain history. Generated assets remain stored. - Canonical canvas operations accept idempotency keys through their nested handlers without rejecting their own reservation. - Replays recheck resource access and preserve the original API representation. A key cannot retrieve a response from another credential workspace or API version. - Multipart uploads preserve binary bytes. Reusing an expired completed key no longer fails on a consumed body, and concurrent retries cannot remove a replacement reservation. - Errors after execution retain their keys, preventing a retry from repeating committed writes. If the receipt cannot be saved, the API returns `503 idempotency_persistence_failed` and keeps the reservation blocked. Retry with the same key and check the operation outcome before starting another request. ### Jobs - `GET /v1/jobs/{id}` and `GET /v1/jobs` initially omitted internal execution details by default. The generation response privacy change above makes that omission unconditional. Customer-authored mascot prompts, styles, references, variants and transition endpoints remain available. ### Webhooks (additive) - Deliveries add `id` (stable across retries) and `object: "event"`, plus `Masko-Event-Id` and a timestamp-bound `Masko-Signature: t=...,v1=...` header next to the existing signature headers. - Failed deliveries retry for about 21 hours with increasing delays. A webhook is disabled after 100 consecutive failures. Deleting a webhook also deletes its delivery history. See [webhooks](/docs/manage/webhooks). ### Marketplace listings - Organization mascots publish under a team profile with a shared handle, name, logo and bio. Team owners/admins manage publishing, existing creator eligibility and review still apply, and earnings go to the team wallet. Publication state now includes the seller workspace and management permission. - Marketplace purchases can belong to Personal or the selected organization. Organization purchases spend team credits and are shared with current team members inside that workspace. Personal purchases stay personal. Library API reads and canvas creation require a license in the credential/project workspace. - Publication supports `withdraw`, returns `listing_preview` and `sales` to the owner, makes the description optional, and requires at least 5 finished animations and a creator handle before submitting. - Studio Marketplace prices and purchase amounts use credits from your existing balance. The catalog returns `price_credits`. Existing purchases and ownership remain unchanged; older Desktop versions retain their compatible price fields. ## 1.14.0 - 2026-08-20 ### Added - `GET /v1/collections/:id/canvases/:canvasId` now returns `status.nodes.failed` and `status.failed_nodes`. Failed node and edge entries include the related job ID and a customer-safe error message. ### Compatibility - `status.nodes.pending` still counts all incomplete nodes, including failed nodes. Existing polling clients keep their previous behavior and can adopt `status.nodes.failed` when ready. ## 1.6.0 - 2026-05-23 ### Added - `GET /v1/collections/:id/cdn-export` returns the same published CDN JSON shape as the collection page Get Links export modal. Use this for copy-paste app manifests; use `/items` and `/assets` for raw IDs, prompts, metadata, and status checks. ### Changed - CDN export requests now fail clearly with `409 cdn_export_not_ready` when asset hosting is disabled, the collection is not published, or no CDN assets have been published yet. ## 1.5.0 - 2026-05-16 ### Added - `GET /v1/assets` and `GET /v1/collections/:id/assets` now accept optional `include_file_urls=false` for faster metadata-only lists. Defaults are unchanged, so existing callers still receive signed `file_url` values. ## 1.2.0 - 2026-04-19 API cleanup pass. Applies one consistent convention across every endpoint: plural resources, verb endpoints only for one-shot actions, `{data, meta?}` response envelope, generation responses with `data.poll_url`, and path-parameter IDs instead of bodies. ### Added - `POST /v1/analyze` - unified analysis endpoint. Body: `{type: "image" | "url", image_url?, url?}`. Replaces the two legacy endpoints. - `GET /v1/collections/:id/suggestions` - AI-suggested action names for a mascot collection. Read-only, no credits. - `GET /v1/assets` and `GET /v1/assets/:id` - first-class access to assets. Filter by `collection_id`, `type`, `is_archived`. - `DELETE /v1/assets/:id` - archive an asset (soft delete). - `PATCH /v1/collections/:id/items/:itemId` - update an item's name or prompt. - `DELETE /v1/collections/:id/items/:itemId` - archive an item. - `DELETE /v1/collections/:id/canvases/:canvasId` - delete a canvas. - `PATCH /v1/canvas-templates/:id` and `GET /v1/canvas-templates/:id`. - `DELETE /v1/collections/:id/references/:assetId` - remove a reference by path param. ### Changed (breaking) - **`POST /v1/collections/:id/generate-batch`** - returns `202 Accepted` with `data.jobs`, `data.total_cost`, and `data.poll_url`. - **`DELETE /v1/collections/:id/references`** - the asset ID moves from the body to the URL path. Old: `DELETE /references` with `{asset_id}` in body. New: `DELETE /references/:assetId`. - **`POST /v1/collections/:id/canvases`** - now accepts an optional `template_id` (plus `node_overrides`, `edge_overrides`). The legacy `/canvases/:canvasId/from-template` endpoint is removed - apply templates at creation time instead. - **`PUT /v1/collections/:id/canvases/:canvasId`** - now `PATCH /v1/collections/:id/canvases/:canvasId`. Body unchanged. - **`GET /v1/collections/:id/canvases/:canvasId/status`** - removed. The `status` object is now on the canvas GET response. - **`PATCH /v1/collections/:id/cdn-slug`** - removed. Set `slug` via `PATCH /v1/collections/:id` with `{slug}` in the body. - **`GET /v1/collections/:id/cdn-status`** - removed. The `cdn_status` array is now on `GET /v1/collections/:id`. - **`GET /v1/collections/:id/urls`** - removed. Use `GET /v1/collections/:id/assets` - URLs are already attached to each asset. - **Paginated responses** - pagination now lives under `meta.pagination` instead of top-level `pagination`. Shape is unchanged: `{total, limit, offset, has_more}`. - **Async responses** - `poll_url` now lives inside `data` (previously top-level). Clients that construct the poll URL from `job_id` are unaffected. ### Removed - `POST /v1/analyze-image` - use `POST /v1/analyze` with `{type: "image", image_url}`. - `POST /v1/analyze-url` - use `POST /v1/analyze` with `{type: "url", url}`. - `POST /v1/collections/:id/suggest-actions` - use `GET /v1/collections/:id/suggestions`. ### Not changed - `POST /v1/collections/:id/generate` - URL, body, and response are unchanged. - `POST /v1/upload` - unchanged. - `GET /v1/jobs/:id` - unchanged. Still supports `?wait=true&timeout=120`. - Authentication and credit costs - all unchanged. ### Migration checklist - **`POST /analyze-image` callers**: swap to `POST /analyze` with `{type: "image", image_url}`. - **`POST /analyze-url` callers**: swap to `POST /analyze` with `{type: "url", url}`. - **`POST /suggest-actions` callers**: swap to `GET /suggestions`. No body needed. - **`DELETE /references` body callers**: move `asset_id` from body to URL path. - **`cdn-slug` / `cdn-status` / `urls` callers**: read and write via the main collection endpoints. - **Canvas `from-template` callers**: pass `template_id` on `POST /canvases` instead. - **Canvas `PUT` callers**: change the method to `PATCH`. Most customers will not need code changes if they only use `POST /generate` and `GET /jobs/:id` with the documented request and response shapes. --- ## 1.1.0 - 2026-04-10 - Envelope enforcement: every v1 response now wraps data in `{data}` or `{error: {code, message}}`. Lists include `pagination` metadata. - Introduced the `api-key` permission levels: `read`, `write`, `admin`. New keys default to `write`. --- ## 1.0.0 - 2025-12-01 Initial public release of the Masko v1 API covering projects, collections, items, canvases, generation, jobs, webhooks, and credits. --- ## How to follow updates - Follow [@masko_ai on X](https://x.com/masko_ai) for breaking-change notifications. - The OpenAPI spec is published at [live OpenAPI specification](https://api.masko.ai/v1/openapi.json); its `info.version` identifies the deployed schema revision. --- ---url: /docs/manage/studio-migration--- Source: https://masko.ai/docs/manage/studio-migration # Migrate to mascots and canvases API version `2026-09-26` (deployed September 27, 2026) renamed collections to mascots and moved canvases to `/v1/canvases`. Existing IDs, API keys and application credentials keep working; new integrations start at the [quickstart](/docs/quickstart). ## Routes and fields | Before | Now | | --- | --- | | `/v1/collections/:id/...` | `/v1/mascots/:id/...` | | `/v1/collections/:id/canvases/:canvasId` | `/v1/canvases/:canvasId` | | `collection_id`, `collection_ids` | `mascot_id`, `mascot_ids` | | `collection_name` | `mascot_name` | | `source_collection_id`, `parent_collection_id` | `source_mascot_id`, `parent_mascot_id` | A canvas now belongs to a project. Create it with `POST /v1/canvases` (`project_id`, `mascot_id`, `name`; no `template_id`, see [canvas templates](/docs/canvas/templates)) and list with `GET /v1/canvases?project_id=PROJECT_ID`. Updates require `expected_graph_hash`, and generation, validation, history and export live under `/v1/canvases/CANVAS_ID`; see the [canvas guide](/docs/canvas). ## Shared routes need the version `/v1/jobs`, `/v1/assets` and `/v1/canvas-templates` serve both versions. Send `Masko-API-Version: 2026-09-26`, `?api_version=2026-09-26` or a `mascot_id` query filter to get current field names; otherwise the old names are kept. The `Masko-API-Version` response header states the version used, and `poll_url` from `/v1/mascots` routes already carries `api_version`. ## Old collection routes `/v1/collections` URLs always use the older payloads but call the same services; they are adapters, not redirects. The current version rejects `collection_id`, and mixing old and new identifier names returns 400 even when the values match. OpenAPI marks the old operations deprecated. They will be removed only after an announced cutoff, once no non-test calls have been seen for 30 consecutive days; none is announced yet. See the [changelog](/docs/reference/changelog). ## Stored records keep their format Graph documents, user metadata, frozen generation receipts and signed runtime delivery keep their stored format; only REST field names changed. Where an old record holds both an authoring collection ID and an older runtime mascot ID, the API now names them `mascot_id` and `runtime_binding_id`. ## Set the animation price explicitly Without `animation_model`, an animation bills the older rate of 5 credits per second, 4 to 10 seconds. Send `"animation_model": "standard"` (2 credits per second, 2 to 15 seconds) or `"premium"` (6 credits per second, 4 to 30 seconds); see [credits and costs](/docs/credits). --- ---url: /docs/ai-tools/lovable--- Source: https://masko.ai/docs/ai-tools/lovable # Masko in Lovable Lovable can create a mascot for your app through Masko's MCP server. The connection creates the mascot and its animations, and the finished files end up in your project. Your published app plays those files. The connection works while you build, and your published app never calls Masko. ## Connect Masko Lovable connects to Masko with an API key. Masko's sign-in works only in Claude, ChatGPT and Cursor for now, so use the key option below. 1. On masko.ai, open [Developers → API keys](/api-keys), choose **Create API key**, name it Lovable and keep **Read + Write**. Copy the key: it is shown once. 2. In Lovable, open **Connectors**, click **+** at the top right, then **MCP server**. 3. Name it Masko and enter the server URL `https://masko.ai/api/mcp`. 4. For authentication, choose **Bearer token or API key** and paste your Masko key. Click **Add server**. The key belongs to the workspace you were in when you created it, personal or team, and generation spends that workspace's credits. Only a team owner or admin can create a team key. If **MCP server** is greyed out in Lovable, an admin of your Lovable workspace has turned off custom MCP servers. Other apps connect the same way: see [MCP server](/docs/mcp). ## Try it - "Show my Masko credit balance. Don't generate anything." - "Add a friendly mascot to my habit tracker. It waits while I'm working and celebrates when I finish a habit." - "Make the mascot idle, then celebrate, then return to idle." ## What Lovable can do - Create a mascot, its poses and its animations. - Check a job until its files are ready. - Export and publish assets, and get their CDN links. - Read your credit balance and open a credit checkout link. ## Costs Generation uses credits from the Masko workspace your key belongs to. Ask for the estimated cost before generating. The estimate tools return it before anything is spent. ## Publishing the files New mascots made through the MCP server start with public CDN hosting off. To use the files in a public app, publish the mascot's assets and use the CDN links. Private links expire, so a production app that needs private files must fetch fresh links from its backend. ## Playback Play it with the [Masko player](/docs/player) (`npm install @masko/player`). A single loop needs only its two hosted files: `` ([one animation](/docs/player/one-animation)). A mascot that changes clips is a [canvas](/docs/canvas) release, which your app drives with `set` and `fire`. For a published mascot, ask Lovable to export the release once with public links (`GET /v1/canvases/CANVAS_ID/export?delivery=1&target=web`) and ship that delivery with your app. Private files need a backend of your own that fetches fresh links. ## Limits - The connection works while you build. Visitors to your published app cannot reach Masko through it. - Generation spends credits from the key's workspace. - The connection allows 120 requests per minute per key. ## Security - Lovable keeps the key you paste and sends it with each request to Masko. Create a key just for Lovable, so you can revoke it on its own. - The key lives in the Lovable connection, never in your app's code. Your published app only plays files and needs no Masko key. - To remove access, delete the server in Lovable and revoke its key in [Developers → API keys](/api-keys). --- ---url: /docs/ai-tools/muse--- Source: https://masko.ai/docs/ai-tools/muse # Masko for Muse Connect Masko to Muse to create mascots, animate your own artwork and use the assets in your app or website. Masko accepts Muse sign-ins; the connector appears in Muse once Meta finishes its review. ## Workflows | Workflow | Example prompt | | --- | --- | | Create a mascot from an idea or an existing character | "Create a friendly mascot for my fitness app." | | Generate consistent poses and animate an uploaded character | "Animate my uploaded mascot waving hello with a transparent background." | | Build a canvas with idle loops, reactions and transitions | "Make my mascot celebrate when someone completes a lesson, then return to idle." | | Get completed assets with HTML, React, Swift/SwiftUI or Android guidance | "Show me how to add my animations to React without flickering." | | Find projects and mascots, check credits and follow jobs | "Show my mascots and credit balance. Do not generate anything." | ## Account access and credits When you connect, Masko asks you to sign in, choose one personal or team workspace and approve what Muse can do. The connection uses that workspace's permissions and its own credit balance. Review the cost before generating, and check a pending job instead of starting another paid generation. Disconnect Muse at any time under [Connected apps](/api-keys?tab=mcp). Credits are bought on [Masko's billing page](/billing) through Stripe, not inside Muse; the MCP tools can create a checkout link and check delivery ([credits and costs](/docs/credits#buy-credits-from-an-api-or-agent)). ## Assets and integration New mascots start private, and public hosting needs an explicit request. Private preview links expire, so production needs public hosting or a backend that refreshes private delivery. Interactive mascots play with the [Masko player](/docs/player), which runs the canvas; your app sends its events to [canvas inputs](/docs/canvas/build) with `set` and `fire`. Single clips play in the Masko player from their hosted files ([play one animation](/docs/player/one-animation)). The connector does not deploy your app or activate Masko for Mac. ## Technical details for integration review | Setting | Value | | --- | --- | | Hosted MCP endpoint | `https://masko.ai/api/mcp` | | Transport | Streamable HTTP | | Authentication protocol | OAuth with PKCE, no client secret | | OAuth client ID | `https://masko.ai/oauth/clients/muse` | | Callback URLs | `https://muse.ai/api/hatch/oauth/callback`, `https://platform.muse.ai/oauth/credential-test/callback` | | Authorization scopes | `masko:read`, `masko:write` | | Muse connection status | Live on Masko; in Meta's connector review | The server exposes tools for projects, mascots, uploads, generation jobs, assets, canvases and integration guidance. Masko accepts the Muse client only with its two callback URLs; a read-only connection refuses every tool that changes data. A host that sends an `Authorization: Bearer` header can connect with a Masko API key instead; see [MCP server](/docs/mcp). See [Masko in ChatGPT](/docs/ai-tools/chatgpt) for the existing integration and [muse.ai/platform](https://muse.ai/platform) for Muse's connector program. ## Support Contact [paul@masko.ai](mailto:paul@masko.ai) for integration or reviewer access. Never send passwords, API keys, authorization codes or connection tokens. [Privacy policy](/privacy) · [Terms of service](/terms) --- ---url: /docs/ai-tools/skills--- Source: https://masko.ai/docs/ai-tools/skills # Masko skill for coding agents Any coding agent that can run commands and read web pages can use the Masko REST API. It needs an API key and a prompt that names masko.ai. ## Set your API key Create a key with **Read + Write** access in [API keys](https://masko.ai/api-keys) and copy it: it is shown once. Set it in the terminal where you start your agent, then check it with a free balance request: ```bash export MASKO_API_KEY=masko_YOUR_KEY curl https://api.masko.ai/v1/credits \ -H "Authorization: Bearer $MASKO_API_KEY" ``` Keep the key on your machine or your server. Never put it in browser code or commit it. ## Ask in plain words Name masko.ai in your prompt so the agent knows where to look. It reads [masko.ai/llms.txt](https://masko.ai/llms.txt), follows the workflows and the [OpenAPI specification](https://api.masko.ai/v1/openapi.json), and makes the calls with `MASKO_API_KEY`: ```text Create a friendly mascot for this app with Masko (masko.ai) and make it wave hello. Tell me the cost first. ``` "Tell me the cost first" makes it show you the credits before any paid request. In the same session, keep asking for more: - "Animate the mascot in our logo.png." It follows [Animate the mascot you already have](/docs/tutorials/animate-your-mascot). - "Make our mascot say: Welcome to Acme." It gives the mascot a voice and makes a [talking animation](/docs/generate/talking). - "Give our mascot idle, thinking and done states." It builds and generates a [canvas](/docs/canvas/build). ## Use Masko in every session If you use Masko often, add these lines to your project's agent instructions, such as `CLAUDE.md` or `AGENTS.md`, so you don't have to name masko.ai each time. Keep what is already there: ```text ## Masko Masko (masko.ai) makes our mascot images and animations. - Use the Masko MCP tools when they are connected. - Otherwise use the REST API at https://api.masko.ai/v1 with MASKO_API_KEY from the environment. Voices and talking always use the REST API. - Docs: https://masko.ai/llms.txt lists every guide, and https://api.masko.ai/v1/openapi.json has the exact requests. - Never print or commit MASKO_API_KEY, and never put it in browser code. - Tell me the credits before any paid generation and wait for my OK. - Keep the IDs you get back. Check existing jobs before generating again. ``` ## Add the MCP server With the same key, your agent can also use Masko's tools through the [MCP server](/docs/mcp). In Claude Code: ```bash claude mcp add --transport http --scope user masko https://masko.ai/api/mcp \ --header "Authorization: Bearer $MASKO_API_KEY" ``` In Codex: ```bash codex mcp add masko --url https://masko.ai/api/mcp \ --bearer-token-env-var MASKO_API_KEY ``` The [MCP server page](/docs/mcp) has the setup for Cursor, Claude, ChatGPT, VS Code and other apps. For four ready-made workflow skills in Cursor, see [Masko for Cursor](/docs/ai-tools/cursor). --- ---url: /docs/use-cases/web-onboarding--- Source: https://masko.ai/docs/use-cases/web-onboarding # Guide onboarding with a talking mascot Build a three-step onboarding flow: name a workspace, create the first project, then show the result. Nori explains the next action with a short talking clip and a visible caption. [Download the complete React source and media](/examples/website-mascot-demos.zip) · [Detailed integration tutorial](/docs/tutorials/web-onboarding-mascot) ## What to build Your website owns the forms, saved data and user actions. Masko v1 generates the character and transparent clips while you build. Published playback needs no API key and no generation call per visitor. The preview creates no real workspace or project. ## 1. Create the reference ![Nori: approved character reference and animation recipe](/website-use-cases/nori-recipe.svg) Copy this character prompt into [Studio](/mascots/new), or send it as `prompt` to Masko v1. Replace the name and brand details for your own product. ```text A friendly origami manta ray made of periwinkle and ivory folded paper, large dark eyes, a small apricot badge, soft tactile paper, calm smile, full character centered, no text or extra objects. ``` Use a write key from [API keys](/api-keys) in `MASKO_API_KEY`, and an existing `PROJECT_ID` from `GET /v1/projects`. Run generation in a terminal or on your server. POST /v1/mascots ```bash curl -X POST "https://api.masko.ai/v1/mascots" \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Masko-API-Version: 2026-09-26" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: nori-reference-001" \ -d '{ "project_id": "PROJECT_ID", "name": "Nori", "prompt": "A friendly origami manta ray made of periwinkle and ivory folded paper, large dark eyes, a small apricot badge, soft tactile paper, calm smile, full character centered, no text or extra objects.", "settings": { "cdn_enabled": true, "animation_sizes": [ 360 ] } }' ``` Save `data.id` as `MASCOT_ID` and the first `data.reference_asset_ids` entry as `REFERENCE_ASSET_ID`. This request generates a reference image. Inspect it before animating; [preview options first](/docs/create/from-text#step-2-generate-previews) if you want a choice. ## 2. Map the interaction states ![Nori: website states, triggers and selected clips](/website-use-cases/nori-flow.svg) Keep the form step separate from the clip. Failed validation or a failed save leaves the visitor on the current screen. In this demo, a talking clip plays once and holds its last frame until the next step; **Hear Nori** replays the current clip with sound. The idle loop is available in the source for a quieter resting state. This diagram describes the website’s state machine. You do not need to create a Masko canvas or send these UI events to the generation API. | Website state | Clip | Trigger | | ------------- | --------- | ------------------- | | Workspace | `welcome` | Enter setup | | First project | `guide` | Workspace saved | | Ready | `done` | First project saved | ## 3. Generate the animation set Start with a five-second Standard idle loop from the approved reference. The animation prompt describes movement; the reference supplies the character’s identity. ```text Gentle breathing, one friendly blink and a tiny paper-wing sway. Keep the full character centered and the camera still. End in the starting pose for a seamless loop. ``` POST /v1/mascots/:id/generate ```bash curl -X POST "https://api.masko.ai/v1/mascots/MASCOT_ID/generate" \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Masko-API-Version: 2026-09-26" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: nori-idle-001" \ -d '{ "type": "animation", "name": "Idle", "source_image_asset_id": "REFERENCE_ASSET_ID", "animation_model": "standard", "animation_prompt": "Gentle breathing, one friendly blink and a tiny paper-wing sway. Keep the full character centered and the camera still. End in the starting pose for a seamless loop.", "duration": 5, "loop": true }' ``` Save `data.job_id`, `data.item_id` and `data.asset_ids`. Each paid request needs its own idempotency key; retry an uncertain request with the same key and identical body. ### Choose Nori’s voice ```text A calm, clear adult voice. Warm, lightly playful, unhurried conversational English. ``` POST /v1/mascots/:id/voice/samples ```bash curl -X POST "https://api.masko.ai/v1/mascots/MASCOT_ID/voice/samples" \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Masko-API-Version: 2026-09-26" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: nori-voices-001" \ -d '{ "description": "A calm, clear adult voice. Warm, lightly playful, unhurried conversational English." }' ``` Listen to the returned `data.samples[].url` files, then keep the chosen sample’s `id`: ```bash curl -X PUT "https://api.masko.ai/v1/mascots/MASCOT_ID/voice" \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Masko-API-Version: 2026-09-26" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: nori-voice-001" \ -d '{ "sample_id": "SAMPLE_ID" }' ``` ### Copy the talking scripts Bracketed movements direct the performance and are not spoken. Keep the same words visible as text in the UI. **welcome** ```text [waves gently, warm] Hi, I’m Nori. Let’s make a little space for your next big idea. ``` **guide** ```text [gestures gently toward the form, encouraging] Give your first project a name. One small step, and you’re ready to go. ``` **done** ```text [small joyful lift, proud] Your first project is ready. You’ve got this! ``` ### Estimate, then generate a talking clip Save this as `nori-welcome.json`. `SOURCE_ITEM_ID` is the idle request’s `data.item_id`, which has the approved source image. Use each script above for its matching clip. ```json { "type": "animation", "item_id": "SOURCE_ITEM_ID", "speech": { "script": "[waves gently, warm] Hi, I’m Nori. Let’s make a little space for your next big idea." }, "dry_run": true } ``` ```bash curl -X POST "https://api.masko.ai/v1/mascots/MASCOT_ID/generate" \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Masko-API-Version: 2026-09-26" \ -H "Content-Type: application/json" \ --data-binary @nori-welcome.json ``` Review `data.estimate.credits`, `duration` and `too_long`. The estimate creates no video and charges no credits. When ready, remove `dry_run` and send the paid request: ```bash jq 'del(.dry_run)' nori-welcome.json > nori-welcome-generate.json curl -X POST "https://api.masko.ai/v1/mascots/MASCOT_ID/generate" \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Masko-API-Version: 2026-09-26" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: nori-welcome-001" \ --data-binary @nori-welcome-generate.json ``` Repeat for the other two scripts with distinct filenames, clip names in your app and idempotency keys. Talking controls its own duration and looping; omit `duration`, `loop` and `animation_prompt`. [Talking animation guide](/docs/tutorials/make-it-talk) covers the full contract. ## 4. Wait for ready files Poll each returned job ID without resending generation. If a wait times out, repeat the GET until `data.status` is `completed` or `failed`. GET /v1/jobs/:id ```bash curl "https://api.masko.ai/v1/jobs/JOB_ID?wait=true&timeout=30" \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Masko-API-Version: 2026-09-26" ``` Check the WebM and HEVC assets you intend to serve with `GET /v1/assets/ASSET_ID`; a finished MP4 alone is not enough. For hosted files, read the mascot’s export: GET /v1/mascots/:id/cdn-export ```bash curl "https://api.masko.ai/v1/mascots/MASCOT_ID/cdn-export" \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Masko-API-Version: 2026-09-26" ``` Use `data.items[].animations[].transparent_video_webm` for Chrome, Firefox and Edge, and `transparent_video_mov` for Safari. The export contains only ready hosted files; `cdn_export_not_ready` means hosting is off or no file is hosted yet; its message says which, and [turn on hosting](/docs/generate/cdn#turn-on-hosting) fixes the first. You can also download the files into your app’s `public/` directory. Do not ship expiring signed URLs. ## 5. Connect the player and product events Copy `src/` and `public/website-use-cases/` from [the source download](/examples/website-mascot-demos.zip), then render the complete example: ```tsx import { WebsiteMascotDemo } from './WebsiteMascotDemo'; export default function Example() { return ; } ``` Advance only after your backend confirms creation. Keep the workspace and project names, restore the saved step after a reload, and let existing users skip completed setup. The example plays each mascot with the [Masko player](/docs/player), from a small canvas it builds from these clips in `website-mascot-delivery.ts`. Replace media URLs in `website-mascots.ts` with your own files. In your app, put the clips in a canvas, release it and play the release, with your product events sent by `fire` ([Play a canvas](/docs/player/canvas)). [The integration tutorial](/docs/tutorials/web-onboarding-mascot#connect-it-to-your-web-app) shows the React event wiring. ## Copy this prompt into Lovable or another coding agent ```text Build a project planning website called Fold with a crisp white workspace, blue accents and a folded-paper backdrop. Use Masko v1 to create Nori, a periwinkle origami manta ray. Add three onboarding steps: workspace name, first project and ready. Generate an idle loop plus welcome, guide and done talking clips. Show the matching caption beside the form. Advance only after a successful save; keep validation errors on the current step. Follow https://masko.ai/docs/use-cases/web-onboarding. Use the REST API at https://api.masko.ai/v1 and inspect https://api.masko.ai/v1/openapi.json for request shapes. Read https://masko.ai/llms-full.txt for generation and playback guidance. Keep MASKO_API_KEY on the server and never print it. Show the generation costs before creating paid media. Wait for jobs and transparent formats, then download or publish the files. Use the finished files in the browser; do not generate on visitor actions. Keep captions and written answers usable without sound. ``` ## Verify and measure Check empty-name validation, back navigation, the finished workspace and project names, and a failed backend save. The guide must stay usable with sound off. Check keyboard use, mobile layout, reduced motion, WebM and Safari playback, plus a media failure with a visible still-image fallback. Compare setup completion and time to first project. Also check next-day return visits. These are goals to test, not a promise of higher conversion. Generation spends your account’s credits; published file playback adds no generation charge. Use [current pricing](/docs/credits) and the talking estimate for your own recipe. ## Continue building - [Detailed React integration](/docs/tutorials/web-onboarding-mascot) - [Download all three demos](/examples/website-mascot-demos.zip) - [All website use cases](/docs/use-cases#website) --- ---url: /docs/use-cases/web-progress-companion--- Source: https://masko.ai/docs/use-cases/web-progress-companion # Add a mascot that celebrates progress and welcomes users back Build a daily habit dashboard with saved progress and a short celebration after each new completion. Pip returns to idle when the animation ends. A later visit changes the greeting without generating another clip. [Download the complete React source and media](/examples/website-mascot-demos.zip) · [Detailed integration tutorial](/docs/tutorials/web-progress-companion) ## What to build Your website owns the forms, saved data and user actions. Masko v1 generates the character and transparent clips while you build. Published playback needs no API key and no generation call per visitor. The preview stores progress in this browser. ## 1. Create the reference ![Pip: approved character reference and animation recipe](/website-use-cases/pip-recipe.svg) Copy this character prompt into [Studio](/mascots/new), or send it as `prompt` to Masko v1. Replace the name and brand details for your own product. ```text A round lilac axolotl with coral gills, a mint scarf and a small yellow star badge, large dark eyes, short rounded limbs, soft clay-like 3D material, warm smile, full body centered, no text or extra props. ``` Use a write key from [API keys](/api-keys) in `MASKO_API_KEY`, and an existing `PROJECT_ID` from `GET /v1/projects`. Run generation in a terminal or on your server. POST /v1/mascots ```bash curl -X POST "https://api.masko.ai/v1/mascots" \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Masko-API-Version: 2026-09-26" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: pip-reference-001" \ -d '{ "project_id": "PROJECT_ID", "name": "Pip", "prompt": "A round lilac axolotl with coral gills, a mint scarf and a small yellow star badge, large dark eyes, short rounded limbs, soft clay-like 3D material, warm smile, full body centered, no text or extra props.", "settings": { "cdn_enabled": true, "animation_sizes": [ 360 ] } }' ``` Save `data.id` as `MASCOT_ID` and the first `data.reference_asset_ids` entry as `REFERENCE_ASSET_ID`. This request generates a reference image. Inspect it before animating; [preview options first](/docs/create/from-text#step-2-generate-previews) if you want a choice. ## 2. Map the interaction states ![Pip: website states, triggers and selected clips](/website-use-cases/pip-flow.svg) Use just two media states: `idle` and `celebrate`. When the celebration ends, return to the same idle clip. On a later visit, reuse idle with a new written greeting. Save each habit once per calendar day, then replay the celebration for a new completion. Increment a daily milestone only after all three distinct habits are complete. Returning visits preserve the history and change the text. This diagram describes the website’s state machine. You do not need to create a Masko canvas or send these UI events to the generation API. | Website state | Clip | Trigger | | ---------------------- | ----------- | ------------------ | | Ready for an activity | `idle` | Open dashboard | | Recognize a new win | `celebrate` | New habit saved | | Back to idle | `idle` | Animation ended | | Welcome the next visit | `idle` | Later calendar day | ## 3. Generate the animation set Start with a five-second Standard idle loop from the approved reference. The animation prompt describes movement; the reference supplies the character’s identity. ```text Subtle breathing, a friendly blink and a tiny relaxed sway. Keep the full body centered and the camera still. End in the starting pose for a seamless loop. ``` POST /v1/mascots/:id/generate ```bash curl -X POST "https://api.masko.ai/v1/mascots/MASCOT_ID/generate" \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Masko-API-Version: 2026-09-26" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: pip-idle-001" \ -d '{ "type": "animation", "name": "Idle", "source_image_asset_id": "REFERENCE_ASSET_ID", "animation_model": "standard", "animation_prompt": "Subtle breathing, a friendly blink and a tiny relaxed sway. Keep the full body centered and the camera still. End in the starting pose for a seamless loop.", "duration": 5, "loop": true }' ``` Save `data.job_id`, `data.item_id` and `data.asset_ids`. Each paid request needs its own idempotency key; retry an uncertain request with the same key and identical body. ### Create the one-shot celebration ```text Two small joyful hops, arms raised, then settle with a proud smile in the starting pose. Preserve the lilac body, coral gills, mint scarf and star badge. No camera movement, text or extra props. ``` ```bash curl -X POST "https://api.masko.ai/v1/mascots/MASCOT_ID/generate" \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Masko-API-Version: 2026-09-26" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: pip-celebrate-001" \ -d '{ "type": "animation", "name": "Celebrate", "source_image_asset_id": "REFERENCE_ASSET_ID", "animation_model": "standard", "animation_prompt": "Two small joyful hops, arms raised, then settle with a proud smile in the starting pose. Preserve the lilac body, coral gills, mint scarf and star badge. No camera movement, text or extra props.", "duration": 5, "loop": false }' ``` Pip uses no voice or talking request in this example. “Welcome back” is written text over the existing idle clip. ## 4. Wait for ready files Poll each returned job ID without resending generation. If a wait times out, repeat the GET until `data.status` is `completed` or `failed`. GET /v1/jobs/:id ```bash curl "https://api.masko.ai/v1/jobs/JOB_ID?wait=true&timeout=30" \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Masko-API-Version: 2026-09-26" ``` Check the WebM and HEVC assets you intend to serve with `GET /v1/assets/ASSET_ID`; a finished MP4 alone is not enough. For hosted files, read the mascot’s export: GET /v1/mascots/:id/cdn-export ```bash curl "https://api.masko.ai/v1/mascots/MASCOT_ID/cdn-export" \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Masko-API-Version: 2026-09-26" ``` Use `data.items[].animations[].transparent_video_webm` for Chrome, Firefox and Edge, and `transparent_video_mov` for Safari. The export contains only ready hosted files; `cdn_export_not_ready` means hosting is off or no file is hosted yet; its message says which, and [turn on hosting](/docs/generate/cdn#turn-on-hosting) fixes the first. You can also download the files into your app’s `public/` directory. Do not ship expiring signed URLs. ## 5. Connect the player and product events Copy `src/` and `public/website-use-cases/` from [the source download](/examples/website-mascot-demos.zip), then render the complete example: ```tsx import { WebsiteMascotDemo } from './WebsiteMascotDemo'; export default function Example() { return ; } ``` Save progress to the signed-in account. Use the user’s timezone for daily boundaries and idempotent writes for habit completion. The demo uses browser storage; **Come back tomorrow** previews the next date. The example plays each mascot with the [Masko player](/docs/player), from a small canvas it builds from these clips in `website-mascot-delivery.ts`. Replace media URLs in `website-mascots.ts` with your own files. In your app, put the clips in a canvas, release it and play the release, with your product events sent by `fire` ([Play a canvas](/docs/player/canvas)). [The integration tutorial](/docs/tutorials/web-progress-companion#connect-it-to-your-web-app) shows the React event wiring. ## Copy this prompt into Lovable or another coding agent ```text Build Little Wins, a daily habit website with cream and sage surfaces, lilac accents and a calm landscape banner. Use Masko v1 to create Pip, a lilac axolotl with coral gills and a mint scarf. Generate only an idle loop and a one-shot celebration. Save each habit once per day, celebrate new completions, count one milestone for a fully completed day, and return to idle on the video ended event. On a later visit, reuse idle with a welcoming written message. Preserve history and avoid guilt for missed days. Follow https://masko.ai/docs/use-cases/web-progress-companion. Use the REST API at https://api.masko.ai/v1 and inspect https://api.masko.ai/v1/openapi.json for request shapes. Read https://masko.ai/llms-full.txt for generation and playback guidance. Keep MASKO_API_KEY on the server and never print it. Show the generation costs before creating paid media. Wait for jobs and transparent formats, then download or publish the files. Use the finished files in the browser; do not generate on visitor actions. Keep captions and written answers usable without sound. ``` ## Verify and measure Complete a habit, reload and check the saved count. A completed habit must not award another win. Finish the day, preview tomorrow and check that the previous milestone remains. Check keyboard use, mobile layout, reduced motion, WebM and Safari playback, plus a media failure with a visible still-image fallback. Measure return visits, completed activities and continued use over several weeks. Compare with a version without the companion; do not treat mascot clicks as proof of improved retention. Generation spends your account’s credits; published file playback adds no generation charge. Use [current pricing](/docs/credits) and the talking estimate for your own recipe. ## Continue building - [Detailed React integration](/docs/tutorials/web-progress-companion) - [Download all three demos](/examples/website-mascot-demos.zip) - [All website use cases](/docs/use-cases#website) --- ---url: /docs/use-cases/web-support-mascot--- Source: https://masko.ai/docs/use-cases/web-support-mascot # Add a talking support mascot in the bottom right Build a bottom-right ecommerce support widget. Fern stays beside the launcher when closed and moves into the panel when opened. Visitors choose plant care, delivery or a message to the shop team. [Download the complete React source and media](/examples/website-mascot-demos.zip) · [Detailed integration tutorial](/docs/tutorials/web-support-mascot) ## What to build Your website owns the forms, saved data and user actions. Masko v1 generates the character and transparent clips while you build. Published playback needs no API key and no generation call per visitor. The preview creates no real orders or support messages. ## 1. Create the reference ![Fern: approved character reference and animation recipe](/website-use-cases/fern-recipe.svg) Copy this character prompt into [Studio](/mascots/new), or send it as `prompt` to Masko v1. Replace the name and brand details for your own product. ```text A friendly little terracotta plant pot with two jade leaves, large dark eyes, a small smiling mouth, short rounded arms and feet, warm clay texture, full character centered, soft 3D lighting, no text or extra props. ``` Use a write key from [API keys](/api-keys) in `MASKO_API_KEY`, and an existing `PROJECT_ID` from `GET /v1/projects`. Run generation in a terminal or on your server. POST /v1/mascots ```bash curl -X POST "https://api.masko.ai/v1/mascots" \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Masko-API-Version: 2026-09-26" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: fern-reference-001" \ -d '{ "project_id": "PROJECT_ID", "name": "Fern", "prompt": "A friendly little terracotta plant pot with two jade leaves, large dark eyes, a small smiling mouth, short rounded arms and feet, warm clay texture, full character centered, soft 3D lighting, no text or extra props.", "settings": { "cdn_enabled": true, "animation_sizes": [ 360 ] } }' ``` Save `data.id` as `MASCOT_ID` and the first `data.reference_asset_ids` entry as `REFERENCE_ASSET_ID`. This request generates a reference image. Inspect it before animating; [preview options first](/docs/create/from-text#step-2-generate-previews) if you want a choice. ## 2. Map the interaction states ![Fern: website states, triggers and selected clips](/website-use-cases/fern-flow.svg) Keep panel visibility, selected topic and team contact as website state. **All help topics** returns to the menu. Closing or Escape returns focus to the launcher. Topic clips play once; the demo holds the last frame, and **Hear Fern** replays the line. Team contact uses the idle clip and a message form. This diagram describes the website’s state machine. You do not need to create a Masko canvas or send these UI events to the generation API. | Website state | Clip | Trigger | | ---------------- | ----------------- | ---------------- | | Closed launcher | `idle` | Page load | | Help panel | `welcome` | Click Ask Fern | | Topic answer | `care / delivery` | Choose a topic | | Contact the team | `idle` | Talk to our team | ## 3. Generate the animation set Start with a five-second Standard idle loop from the approved reference. The animation prompt describes movement; the reference supplies the character’s identity. ```text A tiny relaxed sway and a friendly blink; the jade leaves move gently. Keep the pot, arms and feet consistent. Full character in frame, still camera, seamless loop. ``` POST /v1/mascots/:id/generate ```bash curl -X POST "https://api.masko.ai/v1/mascots/MASCOT_ID/generate" \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Masko-API-Version: 2026-09-26" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: fern-idle-001" \ -d '{ "type": "animation", "name": "Idle", "source_image_asset_id": "REFERENCE_ASSET_ID", "animation_model": "standard", "animation_prompt": "A tiny relaxed sway and a friendly blink; the jade leaves move gently. Keep the pot, arms and feet consistent. Full character in frame, still camera, seamless loop.", "duration": 5, "loop": true }' ``` Save `data.job_id`, `data.item_id` and `data.asset_ids`. Each paid request needs its own idempotency key; retry an uncertain request with the same key and identical body. ### Choose Fern’s voice ```text A warm, practical adult voice with a gentle British accent, relaxed conversational pace. ``` POST /v1/mascots/:id/voice/samples ```bash curl -X POST "https://api.masko.ai/v1/mascots/MASCOT_ID/voice/samples" \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Masko-API-Version: 2026-09-26" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: fern-voices-001" \ -d '{ "description": "A warm, practical adult voice with a gentle British accent, relaxed conversational pace." }' ``` Listen to the returned `data.samples[].url` files, then keep the chosen sample’s `id`: ```bash curl -X PUT "https://api.masko.ai/v1/mascots/MASCOT_ID/voice" \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Masko-API-Version: 2026-09-26" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: fern-voice-001" \ -d '{ "sample_id": "SAMPLE_ID" }' ``` ### Copy the talking scripts Bracketed movements direct the performance and are not spoken. Keep the same words visible as text in the UI. **welcome** ```text [waves gently, warm] Hi, I’m Fern. Looking for plant care or delivery help? ``` **care** ```text [gestures toward the plant, helpful] Give your plant bright, indirect light. Check the soil before watering. Each plant comes with its own care card. ``` **delivery** ```text [small reassuring nod, clear] Our example shop dispatches plants in two working days, packed with care. Your confirmation email has the tracking link. ``` ### Estimate, then generate a talking clip Save this as `fern-welcome.json`. `SOURCE_ITEM_ID` is the idle request’s `data.item_id`, which has the approved source image. Use each script above for its matching clip. ```json { "type": "animation", "item_id": "SOURCE_ITEM_ID", "speech": { "script": "[waves gently, warm] Hi, I’m Fern. Looking for plant care or delivery help?" }, "dry_run": true } ``` ```bash curl -X POST "https://api.masko.ai/v1/mascots/MASCOT_ID/generate" \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Masko-API-Version: 2026-09-26" \ -H "Content-Type: application/json" \ --data-binary @fern-welcome.json ``` Review `data.estimate.credits`, `duration` and `too_long`. The estimate creates no video and charges no credits. When ready, remove `dry_run` and send the paid request: ```bash jq 'del(.dry_run)' fern-welcome.json > fern-welcome-generate.json curl -X POST "https://api.masko.ai/v1/mascots/MASCOT_ID/generate" \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Masko-API-Version: 2026-09-26" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: fern-welcome-001" \ --data-binary @fern-welcome-generate.json ``` Repeat for the other two scripts with distinct filenames, clip names in your app and idempotency keys. Talking controls its own duration and looping; omit `duration`, `loop` and `animation_prompt`. [Talking animation guide](/docs/tutorials/make-it-talk) covers the full contract. ## 4. Wait for ready files Poll each returned job ID without resending generation. If a wait times out, repeat the GET until `data.status` is `completed` or `failed`. GET /v1/jobs/:id ```bash curl "https://api.masko.ai/v1/jobs/JOB_ID?wait=true&timeout=30" \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Masko-API-Version: 2026-09-26" ``` Check the WebM and HEVC assets you intend to serve with `GET /v1/assets/ASSET_ID`; a finished MP4 alone is not enough. For hosted files, read the mascot’s export: GET /v1/mascots/:id/cdn-export ```bash curl "https://api.masko.ai/v1/mascots/MASCOT_ID/cdn-export" \ -H "Authorization: Bearer $MASKO_API_KEY" \ -H "Masko-API-Version: 2026-09-26" ``` Use `data.items[].animations[].transparent_video_webm` for Chrome, Firefox and Edge, and `transparent_video_mov` for Safari. The export contains only ready hosted files; `cdn_export_not_ready` means hosting is off or no file is hosted yet; its message says which, and [turn on hosting](/docs/generate/cdn#turn-on-hosting) fixes the first. You can also download the files into your app’s `public/` directory. Do not ship expiring signed URLs. ## 5. Connect the player and product events Copy `src/` and `public/website-use-cases/` from [the source download](/examples/website-mascot-demos.zip), then render the complete example: ```tsx import { WebsiteMascotDemo } from './WebsiteMascotDemo'; export default function Example() { return ; } ``` Replace the example care and delivery answers with the shop’s real policies. Send the message through your support provider and show success only after it accepts the request. Keep product and purchase controls reachable; opening help is always the visitor’s choice. The example plays each mascot with the [Masko player](/docs/player), from a small canvas it builds from these clips in `website-mascot-delivery.ts`. Replace media URLs in `website-mascots.ts` with your own files. In your app, put the clips in a canvas, release it and play the release, with your product events sent by `fire` ([Play a canvas](/docs/player/canvas)). [The integration tutorial](/docs/tutorials/web-support-mascot#connect-it-to-your-web-app) shows the React event wiring. ## Copy this prompt into Lovable or another coding agent ```text Build a botanical ecommerce website called Leaf & Loom with forest green, warm cream and terracotta colors, plus a clean plant product cutout. Use Masko v1 to create Fern, a terracotta pot with jade leaves. Place a closed Ask Fern launcher in the bottom right. On click, open a compact support panel with Fern inside, topic cards for Plant care and Delivery & orders, and Talk to our team. Use written answers and optional welcome, care and delivery talking clips. Add a back link, close button, Escape handling and focus return. Connect team messages to our support service and keep checkout reachable. Follow https://masko.ai/docs/use-cases/web-support-mascot. Use the REST API at https://api.masko.ai/v1 and inspect https://api.masko.ai/v1/openapi.json for request shapes. Read https://masko.ai/llms-full.txt for generation and playback guidance. Keep MASKO_API_KEY on the server and never print it. Show the generation costs before creating paid media. Wait for jobs and transparent formats, then download or publish the files. Use the finished files in the browser; do not generate on visitor actions. Keep captions and written answers usable without sound. ``` ## Verify and measure Open with the keyboard, select each topic and return to the menu. Close with Escape and check focus returns to Ask Fern. Preview a team message and verify it clearly says the message was not sent. Check keyboard use, mobile layout, reduced motion, WebM and Safari playback, plus a media failure with a visible still-image fallback. Compare resolved questions, completed support handoffs and purchase completion. A mascot may support trust and conversion; accurate answers and a working support service remain essential. Generation spends your account’s credits; published file playback adds no generation charge. Use [current pricing](/docs/credits) and the talking estimate for your own recipe. ## Continue building - [Detailed React integration](/docs/tutorials/web-support-mascot) - [Download all three demos](/examples/website-mascot-demos.zip) - [All website use cases](/docs/use-cases#website) --- ## API operation index Generated from the same OpenAPI snapshot as /docs/reference (version 2026-09-26). For full request and response schemas and deployed availability, read https://api.masko.ai/v1/openapi.json. Deprecated operations are retained for existing integrations. - GET /v1/canvases: List independently owned project canvases - POST /v1/canvases: Create a project canvas with an initial mascot reference - GET /v1/canvases/{canvasId}: Read a canvas by its own project access - PATCH /v1/canvases/{canvasId}: Save a draft with optimistic concurrency - DELETE /v1/canvases/{canvasId}: Delete a canvas without retained history - GET /v1/canvases/{canvasId}/status: Read canvas generation progress - GET /v1/canvases/{canvasId}/history: Read draft, named checkpoints and immutable releases - POST /v1/canvases/{canvasId}/history: Save a checkpoint, restore as draft, or create a release - GET /v1/mascots/{id}/publication: Read selected customer publication and review status - POST /v1/mascots/{id}/publication: Start, edit, review, archive or restore a mascot listing - POST /v1/mascots/{id}/variants/duplicate: Copy Original or a variant without generation or a canvas - GET /v1/canvases/{canvasId}/releases/{releaseId}/delivery: Get signed playback media for one immutable release - GET /v1/mascots/{id}/published-library: Read approved content through a project in the credential workspace - POST /v1/auth/device: Start device authorization for Masko CLI (remote terminals) - POST /v1/auth/token: Exchange a PKCE authorization code, poll a device code, or rotate a CLI refresh token - POST /v1/auth/revoke: End a CLI browser session using its refresh token - GET /v1/auth/session: Identify the account, workspace, and credential in use - GET /v1/auth/config: Discover Masko CLI browser authorization - GET /v1/collections/{id}/canvases/{canvasId}/validate [deprecated]: Validate the stored canvas graph - POST /v1/collections/{id}/canvases/{canvasId}/validate [deprecated]: Validate the stored canvas graph - GET /v1/canvases/{canvasId}/generation-estimate: Estimate canvas generation without starting jobs - POST /v1/credit-checkouts: Create a hosted Stripe checkout for credits - GET /v1/credit-checkouts/{id}: Read payment and credit delivery status - POST /v1/credit-payments: Test a direct Shared Payment Token credit purchase (local sandbox only) - GET /v1/credit-payments/{id}: Read a sandbox token payment and credit delivery status - POST /v1/feedback: Send feedback to the Masko team - GET /v1/collections/{id}/variants [deprecated]: List named mascot variants - POST /v1/collections/{id}/variants [deprecated]: Create a named mascot variant - GET /v1/collections/{id}/variants/{variantId} [deprecated]: Read a variant and its reference candidates - POST /v1/collections/{id}/variants/{variantId}/reference [deprecated]: Generate a draft variant reference - POST /v1/collections/{id}/variants/{variantId}/approve [deprecated]: Approve a generated variant reference - POST /v1/collections/{id}/interactive [deprecated]: Generate a follow-cursor interaction - GET /v1/logo-styles: List logo style presets - GET /v1/collections/{id}/logos/suggestions [deprecated]: Suggest logo directions and styles - POST /v1/assets/{id}/svg: Generate an SVG from a selected image or logo - POST /v1/assets/{id}/exports: Export one image, video or chat sticker - GET /v1/assets/{id}/exports: List exports for one asset - POST /v1/mascots/{id}/exports: Generate missing image and video exports - POST /v1/projects/{id}/export-settings: Apply export settings to project or folder mascots - POST /v1/animations: Animate an uploaded image - POST /v1/mascots/from-brand-book: Create a mascot from a brand book - GET /v1/collections/{id}/voice [deprecated]: Get the voice - PUT /v1/collections/{id}/voice [deprecated]: Keep a voice - DELETE /v1/collections/{id}/voice [deprecated]: Remove the voice - GET /v1/collections/{id}/voice/suggestions [deprecated]: Suggest voices - POST /v1/collections/{id}/voice/adjust [deprecated]: Adjust a voice description - POST /v1/collections/{id}/voice/samples [deprecated]: Hear voice samples - GET /v1/collections/{id}/variants/{variantId}/voice [deprecated]: Get the voice of a variant - PUT /v1/collections/{id}/variants/{variantId}/voice [deprecated]: Keep a voice of a variant - DELETE /v1/collections/{id}/variants/{variantId}/voice [deprecated]: Remove the voice of a variant - GET /v1/collections/{id}/variants/{variantId}/voice/suggestions [deprecated]: Suggest voices of a variant - POST /v1/collections/{id}/variants/{variantId}/voice/adjust [deprecated]: Adjust a voice description of a variant - POST /v1/collections/{id}/variants/{variantId}/voice/samples [deprecated]: Hear voice samples of a variant - GET /v1/projects: List projects - POST /v1/projects: Create a project - GET /v1/collections [deprecated]: List collections - POST /v1/collections [deprecated]: Create a mascot (collection) - GET /v1/collections/{id} [deprecated]: Get collection details - PATCH /v1/collections/{id} [deprecated]: Update collection metadata - GET /v1/collections/{id}/items [deprecated]: List items in a collection - GET /v1/collections/{id}/items/{itemId} [deprecated]: Get item details - PATCH /v1/collections/{id}/items/{itemId} [deprecated]: Update item name or prompt - DELETE /v1/collections/{id}/items/{itemId} [deprecated]: Archive an item - GET /v1/collections/{id}/assets [deprecated]: List assets in a collection - GET /v1/collections/{id}/cdn-export [deprecated]: Export published CDN links - POST /v1/collections/{id}/publish [deprecated]: Turn on asset hosting - GET /v1/assets: List assets across all collections - GET /v1/assets/{id}: Get asset details - DELETE /v1/assets/{id}: Archive an asset - POST /v1/assets/{id}/sticker: Create a sticker from an image or animation - GET /v1/assets/{id}/sticker: Get an animated sticker and its platform files - POST /v1/assets/{id}/refine-mask: Refine a video asset mask - POST /v1/collections/{id}/references [deprecated]: Add a reference image - DELETE /v1/collections/{id}/references/{assetId} [deprecated]: Remove a reference image - POST /v1/collections/{id}/generate [deprecated]: Generate image or animation - POST /v1/collections/{id}/animations/reference-video [deprecated]: Animate a mascot from a reference video - POST /v1/collections/{id}/generate-batch [deprecated]: Batch-generate multiple items - GET /v1/collections/{id}/suggestions [deprecated]: Suggest action poses - GET /v1/collections/{id}/backgrounds/suggestions [deprecated]: Suggest backgrounds for a purpose - PATCH /v1/collections/{id}/settings [deprecated]: Update collection settings - POST /v1/collections/{id}/size-variants [deprecated]: Backfill animation size variants - GET /v1/collections/{id}/canvases [deprecated]: List canvases in a collection - POST /v1/collections/{id}/canvases [deprecated]: Create a canvas - GET /v1/collections/{id}/canvases/{canvasId} [deprecated]: Get canvas details - PATCH /v1/collections/{id}/canvases/{canvasId} [deprecated]: Replace a complete canvas graph - DELETE /v1/collections/{id}/canvases/{canvasId} [deprecated]: Delete a canvas - POST /v1/collections/{id}/canvases/{canvasId}/repair [deprecated]: Repair canvas preview media - POST /v1/collections/{id}/canvases/{canvasId}/presentation/detect-facing [deprecated]: Detect canvas natural facing - POST /v1/collections/{id}/canvases/{canvasId}/graph/extend [deprecated]: Legacy partial canvas graph extension - PATCH /v1/collections/{id}/canvases/{canvasId}/edges/{edgeId} [deprecated]: Patch a canvas edge - POST /v1/collections/{id}/canvases/{canvasId}/edges/{edgeId}/generate [deprecated]: Generate a single canvas edge - POST /v1/collections/{id}/canvases/{canvasId}/edges/{edgeId}/refine-mask [deprecated]: Refine a canvas edge video mask - POST /v1/collections/{id}/canvases/{canvasId}/generate-all [deprecated]: Generate canvas images, stickers, and animations - GET /v1/collections/{id}/canvases/{canvasId}/export [deprecated]: Export a canvas - GET /v1/canvas-templates: List canvas templates - POST /v1/canvas-templates: Create a canvas template - GET /v1/canvas-templates/{id}: Get canvas template details - PATCH /v1/canvas-templates/{id}: Update a canvas template - DELETE /v1/canvas-templates/{id}: Delete a canvas template - GET /v1/jobs: List generation jobs - GET /v1/jobs/{id}: Long-poll a job to completion - GET /v1/credits: Get credit balance - GET /v1/styles: List available style presets - POST /v1/upload: Upload an image, video or PDF - POST /v1/generate/preview: Preview an idea before creating a collection - POST /v1/analyze: Analyze an image or website URL - GET /v1/webhooks: List webhook subscriptions - POST /v1/webhooks: Create a webhook subscription - DELETE /v1/webhooks/{id}: Delete a webhook subscription - GET /v1/canvases/{canvasId}/validate: Validate the stored canvas graph - POST /v1/canvases/{canvasId}/validate: Validate the stored canvas graph - POST /v1/canvases/{canvasId}/repair: Repair canvas preview media - POST /v1/canvases/{canvasId}/presentation/detect-facing: Detect canvas natural facing - POST /v1/canvases/{canvasId}/graph/extend [deprecated]: Legacy partial canvas graph extension - PATCH /v1/canvases/{canvasId}/edges/{edgeId}: Patch a canvas edge - POST /v1/canvases/{canvasId}/edges/{edgeId}/generate: Generate a single canvas edge - POST /v1/canvases/{canvasId}/edges/{edgeId}/refine-mask: Refine a canvas edge video mask - POST /v1/canvases/{canvasId}/generate-all: Generate canvas images, stickers, and animations - GET /v1/canvases/{canvasId}/export: Export a canvas - GET /v1/mascots/{id}/variants: List named mascot variants - POST /v1/mascots/{id}/variants: Create a named mascot variant - GET /v1/mascots/{id}/variants/{variantId}: Read a variant and its reference candidates - POST /v1/mascots/{id}/variants/{variantId}/reference: Generate a draft variant reference - POST /v1/mascots/{id}/variants/{variantId}/approve: Approve a generated variant reference - POST /v1/mascots/{id}/interactive: Generate a follow-cursor interaction - GET /v1/mascots/{id}/logos/suggestions: Suggest logo directions and styles - GET /v1/mascots/{id}/voice: Get the voice - PUT /v1/mascots/{id}/voice: Keep a voice - DELETE /v1/mascots/{id}/voice: Remove the voice - GET /v1/mascots/{id}/voice/suggestions: Suggest voices - POST /v1/mascots/{id}/voice/adjust: Adjust a voice description - POST /v1/mascots/{id}/voice/samples: Hear voice samples - GET /v1/mascots/{id}/variants/{variantId}/voice: Get the voice of a variant - PUT /v1/mascots/{id}/variants/{variantId}/voice: Keep a voice of a variant - DELETE /v1/mascots/{id}/variants/{variantId}/voice: Remove the voice of a variant - GET /v1/mascots/{id}/variants/{variantId}/voice/suggestions: Suggest voices of a variant - POST /v1/mascots/{id}/variants/{variantId}/voice/adjust: Adjust a voice description of a variant - POST /v1/mascots/{id}/variants/{variantId}/voice/samples: Hear voice samples of a variant - GET /v1/mascots: List mascots - POST /v1/mascots: Create a mascot - GET /v1/mascots/{id}: Get mascot details - PATCH /v1/mascots/{id}: Update mascot metadata - GET /v1/mascots/{id}/items: List items in a mascot - GET /v1/mascots/{id}/items/{itemId}: Get item details - PATCH /v1/mascots/{id}/items/{itemId}: Update item name or prompt - DELETE /v1/mascots/{id}/items/{itemId}: Archive an item - GET /v1/mascots/{id}/assets: List assets in a mascot - GET /v1/mascots/{id}/cdn-export: Export published CDN links - POST /v1/mascots/{id}/publish: Turn on asset hosting - POST /v1/mascots/{id}/references: Add a reference image - DELETE /v1/mascots/{id}/references/{assetId}: Remove a reference image - POST /v1/mascots/{id}/generate: Generate image or animation - POST /v1/mascots/{id}/animations/reference-video: Animate a mascot from a reference video - POST /v1/mascots/{id}/generate-batch: Batch-generate multiple items - GET /v1/mascots/{id}/suggestions: Suggest action poses - GET /v1/mascots/{id}/backgrounds/suggestions: Suggest backgrounds for a purpose - PATCH /v1/mascots/{id}/settings: Update mascot settings - POST /v1/mascots/{id}/size-variants: Backfill animation size variants