# Masko API - Complete Documentation Base URL: https://api.masko.ai/v1 Auth: Authorization: Bearer masko_YOUR_API_KEY OpenAPI spec: https://api.masko.ai/v1/openapi.json --- ---url: /docs/quickstart--- # Quickstart %% animation https://assets.masko.ai/7fced6/spark-4735/waving-hello-54937ffb-360.webm https://assets.masko.ai/7fced6/spark-4735/waving-hello-0bf2a5b3-360.mov %% Create your first mascot and get CDN URLs in 5 minutes. Using an AI coding agent? Install the [Masko AI skill](/docs/ai-tools/skills) to give it the API contract and generation workflow. Prerequisites: You need an API key. Create one in the [Developer dashboard](/api-keys). ## Step 1: Create a Project Projects group related collections together. Create one to get started. ```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 App" }' ``` ```javascript const res = await fetch('https://api.masko.ai/v1/projects', { method: 'POST', headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'My App' }), }); const { data: project } = await res.json(); ``` ## Step 2: Create a Collection A collection represents a single mascot character. Give it a name and a prompt describing the character you want. If you create from text without reference images, Masko generates the first reference image for 1 credit. ```bash curl -X POST https://api.masko.ai/v1/collections \ -H "Authorization: Bearer masko_YOUR_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/collections', { method: 'POST', headers: { 'Authorization': 'Bearer masko_YOUR_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 { data: collection } = await res.json(); // Save collection.id for the next steps ``` ## Step 3: Generate an Animation Request a generation by specifying the type, item name, and prompts. The API returns a job ID you can poll for status. ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_ID/generate \ -H "Authorization: Bearer masko_YOUR_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", "duration": 4 }' ``` ```javascript const res = await fetch( 'https://api.masko.ai/v1/collections/COLLECTION_ID/generate', { method: 'POST', headers: { 'Authorization': 'Bearer masko_YOUR_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', duration: 4, }), } ); const { data } = await res.json(); const job_id = data.job_id; ``` ## Step 4: Check the Result Poll the job endpoint until the status is `completed`. The response includes download URLs for all generated assets. ```bash curl https://api.masko.ai/v1/jobs/JOB_ID \ -H "Authorization: Bearer masko_YOUR_API_KEY" # Response: # { # "data": { # "id": "JOB_ID", # "status": "completed", # "type": "item_generation", # "urls": { # "video": "https://...", # "webm": "https://..." # } # } # } ``` ```javascript async function pollJob(jobId) { while (true) { const res = await fetch( `https://api.masko.ai/v1/jobs/${jobId}`, { headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY', }, } ); const { data: job } = await res.json(); if (job.status === 'completed') { return job.urls; } if (job.status === 'failed') { throw new Error(job.error); } // Wait 2 seconds before polling again await new Promise((r) => setTimeout(r, 2000)); } } const urls = await pollJob(job_id); console.log(urls); ``` Once your assets are published to the CDN, the URLs are permanent and served from `assets.masko.ai` with global edge caching. ## Next Steps - [Generation](/docs/generate/images) - Deep dive into image, animation, and logo generation options. - [Canvas](/docs/canvas/build) - Build interactive state machines with your mascot. - [AI Agents](/docs/ai-agents) - Give a coding agent direct API guidance. --- ---url: /docs/authentication--- # Authentication %% animation https://assets.masko.ai/7fced6/spark-4735/holding-key-edbf1ecd-360.webm https://assets.masko.ai/7fced6/spark-4735/holding-key-4b043910-360.mov %% All API requests require a Bearer token in the `Authorization` header. Unauthenticated requests return a `401` error. ## API Keys API keys follow the format `masko_{64-hex-characters}`. When you create a key, only the SHA-256 hash is stored on our servers - the raw key is shown once and cannot be retrieved later. ```bash curl https://api.masko.ai/v1/credits \ -H "Authorization: Bearer masko_YOUR_API_KEY" ``` ```javascript const res = await fetch('https://api.masko.ai/v1/credits', { headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY', }, }); const { data: credits } = await res.json(); ``` Keep your API key secret. Do not expose it in client-side code or public repositories. If a key is compromised, revoke it immediately from the dashboard. ## Creating API Keys Create API keys from the [Developer dashboard](/api-keys). ### Personal vs Organization Keys API keys are scoped to a workspace: - **Personal keys** access your personal projects and deduct from your personal credits - **Organization keys** access the team's projects and deduct from the team's credit pool When you switch workspaces in the dashboard, the Developer page shows keys for that workspace. Organization keys can be created by admins and owners. Any team member can use an org key. ## Rate Limits The v1 API may return `429 Too Many Requests` when traffic is limited. Treat a `429` as retryable and back off before trying again. ## Credits Each generation consumes credits from your account balance. Check your balance at any time via the `/v1/credits` endpoint. | Type | Cost | | --- | --- | | Image | 1 credit | | Animation | 5 credits per second | | Logo | 5 credits | | Edit | 1 credit | ## Error Codes The API uses standard HTTP status codes for error responses. | Code | Meaning | | --- | --- | | `401` | Invalid or missing API key | | `402` | Insufficient credits - top up your balance to continue | | `403` | Access denied - you do not own this resource | | `429` | Rate limit exceeded - wait and retry | | `500` | Internal server error - contact support if persistent | --- ---url: /docs/ai-agents--- # AI Agent Integration AI coding agents can create mascots and generate assets through Masko's public REST API. This works in any agent that can make HTTP requests or run a script. ## Choose Your Integration | Method | Best For | Setup | |--------|----------|-------| | [**AI Skill**](/docs/ai-tools/skills) | Give an agent Masko-specific workflows and API guidance | 2 minutes | | **Direct API** | Scripts, applications, and custom agent tools | [Quickstart](/docs/quickstart) | | **OpenAPI** | Generate a typed client or inspect the complete contract | [OpenAPI spec](https://api.masko.ai/v1/openapi.json) | ## Quick Setup Create an API key in [Developer Settings](https://masko.ai/settings/developer), then pass it as a bearer token: ```bash curl https://api.masko.ai/v1/credits \ -H "Authorization: Bearer $MASKO_API_KEY" ``` ## What Agents Can Do - Create and manage mascot collections - Generate images, animations, edits, logos, and scenes - Poll generation jobs and retrieve CDN URLs - Build and update interactive canvases - Manage projects, assets, and references ## Agent Resources | Resource | URL | |----------|-----| | LLM-friendly docs | [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) | In the API, a mascot is represented by a collection with `type: "mascot"`. --- ---url: /docs/ai-tools/skills--- # AI Skills AI skills are instruction files that teach a coding agent how to call the Masko REST API and follow its generation workflows. ## Install for Your Tool Copy `packages/skill/masko/SKILL.md` into the instruction location supported by your agent: | Tool | Location | |------|----------| | Claude Code | `.claude/skills/masko/SKILL.md` | | Cursor | `.cursor/rules/masko.mdc` | | VS Code / Copilot | `.github/copilot-instructions.md` | | Codex | `AGENTS.md` or a local skill directory | | Gemini CLI | `GEMINI.md` | | Windsurf | `.windsurfrules` | ## API Setup Create an API key in [Developer Settings](https://masko.ai/settings/developer). The skill expects it in `MASKO_API_KEY` and calls the public API directly: ```bash export MASKO_API_KEY=masko_YOUR_KEY curl https://api.masko.ai/v1/credits \ -H "Authorization: Bearer $MASKO_API_KEY" ``` The complete contract is available in the [OpenAPI specification](https://api.masko.ai/v1/openapi.json), and the consolidated agent documentation is available at [masko.ai/llms-full.txt](https://masko.ai/llms-full.txt). --- ---url: /docs/how-mascots-work--- # How Mascots Work Understanding the data model behind Masko's API. ## The Hierarchy Every mascot in Masko follows a four-level hierarchy. Projects group your work, collections define characters, items represent poses or actions, and assets are the actual files. ```text Project └── Collection (= one mascot character) ├── 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" └── ... ``` ## Collections (= Mascots) A collection represents a single mascot character. In the API, mascots are stored as collections with `type: "mascot"` and managed through `/v1/collections`. Each collection holds the character's prompt (the text description used for generation), reference images (up to 6 examples of what the character looks like), and a style card (an auto-extracted summary of the character's visual traits). Collections also store settings like animation sizes, CDN configuration, and the caution list used to maintain consistency across generations. ## Items An item is a single pose or action for the mascot - like "Wave", "Idle", or "Thumbs Up". Each item has a name, a prompt describing the action, and a type (`image`, `animation`, or `logo`). When you generate an image for an item, the API combines the collection's character prompt with the item's action prompt to produce a consistent result. ## Assets An asset is a single generated file. Each item can have multiple assets of different types: | Type | Format | Description | | --- | --- | --- | | `image` | .png | Original generated image with background | | `transparent_image` | .png | Background removed, transparent PNG | | `video` | .mp4 | Animated version (H.264) | | `webm` | .webm | Web-optimized format with alpha channel | | `hevc` | .mov | Apple-compatible format with alpha channel | | `stacked_video` | .mp4 | Stacked layout for custom alpha compositing | ## Generation Graph Assets are connected through a generation graph. When you generate an animation, the API first creates an image, then removes the background, then animates it, then converts to web formats. Each step links back to its source via the `generation_links` table. ```text image (.png) └── transparent_image (.png) [role: source] └── video (.mp4) [role: source, end_frame] ├── webm (.webm) [role: source] └── hevc (.mov) [role: source] ``` The `role` field on each link indicates the relationship. `source` means "this asset was derived from that asset". `end_frame` is used for animations where a final pose image guides the motion. ## Reference Images & Style Cards Each collection can have up to 6 reference images. These are examples of what the mascot looks like - they guide every generation to maintain visual consistency. When you first generate an image, Masko automatically extracts a style card from the references. The style card is a structured summary of the character's visual traits (colors, proportions, line style, shading) that gets injected into every prompt. If you change the references, the style card is cleared and re-extracted on the next generation. The caution list accumulates notes from post-generation validation. If a generated image drifts from the style (wrong color, missing detail), the issue is logged and injected into future prompts to prevent recurrence. ## Size Variants Animations can be generated at multiple sizes simultaneously. Set `settings.animation_sizes` when creating a collection, or update `publish_params.animation_sizes` later, to define numeric pixel sizes such as `[720, 480, 360, 240]`. Resizing is free - you only pay credits for the base animation generation. --- ---url: /docs/credits--- # Credits & Pricing Understanding costs and optimizing your credit usage. ## Credit Costs | Operation | Credits | Notes | | ---------------- | ---------- | ------------------------------- | | Image generation | 1 | Per image | | Animation | 5 / second | 4-second animation = 20 credits | | Logo generation | 5 | Per logo | | Image edit | 1 | Inpainting or style transfer | | Reverse (undo) | 0 | Free | ## What's Free These operations cost zero credits: - `POST /v1/analyze` - AI analysis of an uploaded image (`type: "image"`) or website (`type: "url"`) - `GET /v1/collections/:id/suggestions` - AI-suggested poses for a collection - Style card extraction (automatic on first generation) - Background removal (included in generation pipeline) - Format conversion (webm, hevc from video) - Size variants (resizing animations to multiple resolutions) ## How Credits Work Credits are deducted upfront when a generation job starts. If the job fails, credits are automatically refunded to your account. Your account has two credit balances: subscription credits (replenished each billing cycle) and top-up credits (purchased separately, never expire). Subscription credits are used first. ## Checking Your Balance ```bash curl https://api.masko.ai/v1/credits \ -H "Authorization: Bearer masko_YOUR_API_KEY" ``` ```javascript const res = await fetch('https://api.masko.ai/v1/credits', { headers: { Authorization: 'Bearer masko_YOUR_API_KEY' } }); const { data: credits } = await res.json(); console.log(credits.total); ``` ```json { "data": { "subscription": 450, "topup": 100, "total": 550 } } ``` ## Common Workflow Costs | Workflow | Credits | | --------------------------------------------- | ------- | | 1 image (with bg removal + formats) | 1 | | 1 animation (4s, includes image + bg removal) | 21 | | 8 poses (images only) | 8 | | Full canvas (4 states, 16 animations at 4s) | ~400 | ## Insufficient Credits If you don't have enough credits, the API returns a `402 Payment Required` response: ```json { "error": "Insufficient credits", "required": 21, "available": 5 } ``` Logo generation requires a Pro plan or higher. If you're on the Free or Starter plan, the API returns a `403 Forbidden` response instead. --- ---url: /docs/create/from-text--- # Create from Text Describe your mascot and pick a style - the API generates the reference image. ## Two Approaches You can either preview multiple variations first (recommended) or create a collection directly in a single call. The preview approach costs 1 credit and gives you 4 options to choose from. Direct creation auto-generates one reference and builds the collection immediately. ## Step 1: Browse Styles GET /v1/styles Fetch the list of available art styles. Each style has a preset ID you can pass to the generation endpoint. ```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": "3d-render", "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 multiple preview images from your text description. This lets you pick the best variation before committing to a collection. ```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": "3d-render", "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: '3d-render', 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 Collection POST /v1/collections Take the preview URL you like best and use it as a reference image to create your collection. ```bash curl -X POST https://api.masko.ai/v1/collections \ -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": [ "https://api.masko.ai/v1/previews/preview_2.png" ] }' ``` ```javascript const res = await fetch('https://api.masko.ai/v1/collections', { 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[2].url] }) }); const { data: collection } = await res.json(); console.log(collection.id); // Use this for generation ``` ```json { "data": { "id": "col_abc123", "name": "Astro Fox", "slug": "astro-fox-a1b2c3d4", "type": "mascot", "reference_asset_ids": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"], "settings": { "cdn_enabled": true, "animation_sizes": [] } } } ``` CDN publishing is enabled by default on new collections. You can also set numeric `animation_sizes` at creation time to define which resolutions to generate, for example `[720, 480, 360]`. ## Shortcut: Direct Creation If you don't need to preview, you can create a collection in one call. The API auto-generates a reference image (1 credit) from your prompt. Optionally pass a `style` to apply a visual preset. ```bash curl -X POST https://api.masko.ai/v1/collections \ -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/collections', { 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' }) }); ``` ## Next Steps - [Generate Images](/docs/generation) - Create poses and actions for your mascot. - [Generate Animations](/docs/generation) - Bring your mascot to life with animated sequences. --- ---url: /docs/create/from-image--- # Create from Image Upload your existing mascot design and let the API auto-detect the character. ## Upload Your Image POST /v1/upload You can upload an image either as a multipart form upload or by providing a URL to an existing image. ### 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 ```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 Collection POST /v1/collections Pass the `asset_id` from the upload step as a reference. You don't need to provide a `prompt` - the image itself is the reference, and the API extracts the character description automatically. If you omit `name`, it's also auto-detected from the image. ```bash curl -X POST https://api.masko.ai/v1/collections \ -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/collections', { 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: collection } = await res.json(); // collection.name is auto-detected from the image // collection.reference_asset_ids contains the linked reference ``` You can also pass `reference_image_urls` with public URLs instead of uploading first. Both work - use `reference_asset_ids` for uploaded files, `reference_image_urls` for external URLs. ## How Auto-Analysis Works When you create a collection from an image, Masko uses AI vision to analyze the reference and extract a character name and detailed prompt. This analysis is free and happens automatically. The extracted prompt describes the character's visual traits so future generations stay consistent. ## Adding More References POST /v1/collections/:id/references You can add up to 6 reference images to a collection. More references means better consistency across generated poses. ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_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/collections/${collectionId}/references`, { method: 'POST', headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ asset_id: assetId // from POST /upload }) }); ``` You can also pass a `url` instead of `asset_id` if you have a public image URL. When references change, the style card is cleared and will be re-extracted on the next generation to reflect the updated visual direction. ## Best Practices - Use 3-4 reference images showing different angles for best consistency - Keep a consistent art style across all references - don't mix 3D and flat vector - Use high-resolution images (1024x1024 or larger recommended) - White or transparent backgrounds work best - the AI focuses on the character, not the scene --- ---url: /docs/create/from-website--- # Create from Website Analyze a website to get AI-generated mascot suggestions based on the brand. ## Analyze a URL POST /v1/analyze Send a website URL with `type: "url"` and the API will screenshot the page, extract page metadata, summarize the brand, and return any generated suggestions without base64 payloads. This endpoint is 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..." } ] } } ``` The same endpoint analyzes an image. Pass `type: "image"` and `image_url` to extract a character description and name from an existing mascot image. ## Use Suggestions Take a suggestion from the response and pass its prompt to create a collection. You can use the direct creation shortcut to do it in one call. ```bash curl -X POST https://api.masko.ai/v1/collections \ -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]; const res = await fetch('https://api.masko.ai/v1/collections', { 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 Choose the right creation method for your use case: - **From website** - You have a brand but no mascot idea yet. The AI analyzes the site and suggests characters that match the visual identity. - **From text** - You already know what you want. Describe the character, pick a style, and generate previews. - **From image** - You have existing artwork. Upload it and the API auto-detects the character for consistent new poses. --- ---url: /docs/generate/workflow--- # End-to-end generation flow This guide walks through the full path from zero to an animated mascot in four API calls: create a project and collection, add references, generate a pose image, then animate that pose. Each step feeds IDs into the next, so follow them in order. ## Step 1: Create a project and collection Projects are top-level containers. Each project holds one or more collections, and each collection represents one mascot character. Create the project first, then seed the collection with `reference_image_urls` so style extraction can kick off 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": "proj_abc123", "name": "My SaaS", "created_at": "2026-04-18T08:00:00Z" } } ``` POST /v1/collections ```bash curl -X POST https://api.masko.ai/v1/collections \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "project_id": "proj_abc123", "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": "col_xyz789", "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) If you want to add more reference images after creating the collection, use the references endpoint. Up to 6 references total. Adding or removing references clears the cached style card so the next generation re-extracts it. POST /v1/collections/:id/references ```bash curl -X POST https://api.masko.ai/v1/collections/col_xyz789/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" ] } } ``` See [Reference images and style consistency](/docs/manage/references) for more on how references work. ## Step 3: Generate a pose image Now generate a static image of the mascot in the pose you want to animate. The response returns `asset_ids.image`, which is the ID you will need in step 4. POST /v1/collections/:id/generate ```bash curl -X POST https://api.masko.ai/v1/collections/col_xyz789/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": "job_abc123", "status": "pending", "type": "image", "item_id": "item_def456", "item_name": "Waving hello", "estimated_cost": 1, "asset_ids": { "image": "ast_img_001", "transparent_image": "ast_img_002" }, "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/job_abc123" } ``` Poll `/v1/jobs/job_abc123?wait=true` until the job reaches `completed`. You can use the `asset_ids.image` returned by the generate response as `source_image_asset_id` in the next step. ## Step 4: Animate that pose Call the same `/generate` endpoint again with `type: "animation"` and set `source_image_asset_id` to the `asset_ids.image` value captured in step 3. The animation is built from that exact pose. POST /v1/collections/:id/generate ```bash curl -X POST https://api.masko.ai/v1/collections/col_xyz789/generate \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "animation", "item_id": "item_def456", "source_image_asset_id": "ast_img_001", "animation_prompt": "waving arm back and forth in a friendly greeting", "duration": 4 }' ``` ```json { "data": { "job_id": "job_anim456", "status": "pending", "type": "animation", "item_id": "item_def456", "estimated_cost": 15, "asset_ids": { "video": "ast_vid_003", "webm": "ast_webm_003", "hevc": "ast_hevc_003" } }, "poll_url": "/api/v1/jobs/job_anim456" } ``` `source_image_asset_id` MUST be the `asset_ids.image` value returned by step 3's generate response. It must not be: - A preview URL from `/v1/generate/preview` (previews are ephemeral and not persisted as assets) - A `transparent_image` asset ID (source must be a full image, not the background-removed variant) - A collection reference asset ID (references are style anchors, not poses to animate) ## 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--- # Images & Poses Generate static mascot images from text prompts. Each image costs 1 credit and produces both a full image and a transparent (background-removed) variant. POST /v1/collections/:id/generate ## Generate an Image Send a `type: "image"` request with a `name` for the item and an `image_prompt` describing the pose. A new item is created in your collection with pending image and transparent_image assets. ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_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": "job_abc123", "status": "pending", "type": "image", "item_id": "item_def456", "item_name": "Waving hello", "estimated_cost": 1, "asset_ids": { "image": "ast_img_001", "transparent_image": "ast_img_002" }, "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/job_abc123" } ``` ## Edit an Image Modify an existing image with natural language instructions. Pass `type: "edit"` along with `source_image_asset_id` and `edit_instructions`. The edit creates new assets on the same item. Costs **1 credit**. ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_ID/generate \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "edit", "item_id": "item_def456", "source_image_asset_id": "ast_img_001", "edit_instructions": "Add a party hat and confetti falling around" }' ``` ```json { "data": { "job_id": "job_edit789", "status": "pending", "type": "edit", "item_id": "item_def456", "item_name": "Waving hello", "estimated_cost": 1, "asset_ids": { "image": "ast_img_010", "transparent_image": "ast_img_011" }, "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/job_edit789" } ``` ## Regenerate To regenerate an existing item, pass the same `item_id` with a new `image_prompt`. The endpoint creates fresh assets under the same item each time. Previous assets remain accessible - nothing is overwritten. ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_ID/generate \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "image", "item_id": "item_def456", "image_prompt": "waving hello with both arms raised high" }' ``` ## Style Consistency Masko keeps your mascot looking consistent across all generated images using two mechanisms: - **Reference images** - Up to 6 images pinned as references in your collection. Every generation includes these as visual context so the AI matches the character's appearance. - **Style card** - Automatically extracted from your references on the first generation. The style card captures defining traits like color palette, proportions, and design style, then injects them into every prompt. You do not need to configure either of these manually. Add reference images via the [references endpoint](/docs/manage/references), and the style card is generated lazily when you first run a generation. If your character looks different across generations, add 2-3 reference images showing the character from different angles. The style card will recalculate automatically when references change. ## Image vs Animation Use **images** when you need a static pose - profile pictures, thumbnails, marketing assets, or any context where motion is not needed. Images cost 1 credit and generate in a few seconds. Use **animations** when you need movement - idle loops, transitions between states, reactions, or interactive behaviors. Animations start at 16 credits (3 seconds) and take longer to generate. See the [Animations guide](/docs/generate/animations) for details. --- ---url: /docs/generate/animations--- # Animations %% animation https://assets.masko.ai/7fced6/spark-4735/painting-7a826736-360.webm https://assets.masko.ai/7fced6/spark-4735/painting-76a264c3-360.mov %% Generate animated mascot videos from scratch or from existing images. All animation types use the same generate endpoint with `type: "animation"`. POST /v1/collections/:id/generate ## Image + Animation (New) Generate a new image and animate it in one request. Provide both an `image_prompt` (for the pose) and an `animation_prompt` (for the motion). This costs 1 credit for the image plus 5 credits per second of video - a 4-second animation costs **21 credits** total. ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_ID/generate \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "animation", "name": "Idle breathing", "image_prompt": "standing relaxed with arms at sides", "animation_prompt": "gentle breathing motion, subtle body sway", "duration": 4, "loop": true }' ``` ```json { "data": { "job_id": "job_anim_001", "status": "pending", "type": "animation", "item_id": "item_anim_100", "item_name": "Idle breathing", "estimated_cost": 21, "asset_ids": { "image": "ast_img_050", "transparent_image": "ast_img_051", "video": "ast_vid_052", "webm": "ast_vid_053", "hevc": "ast_vid_054" }, "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/job_anim_001" } ``` ## Animate Existing Image Animate an image you already have by passing `source_image_asset_id`. This skips image generation, so you only pay for the video - **20 credits** for 4 seconds. ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_ID/generate \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "animation", "name": "Waving animated", "source_image_asset_id": "ast_img_001", "animation_prompt": "waving hello with smooth arm motion", "duration": 4, "loop": true }' ``` ```json { "data": { "job_id": "job_anim_002", "status": "pending", "type": "animation", "item_id": "item_anim_101", "item_name": "Waving animated", "estimated_cost": 20, "asset_ids": { "video": "ast_vid_060", "webm": "ast_vid_061", "hevc": "ast_vid_062" }, "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/job_anim_002" } ``` ## Transitions Create a transition between two poses by providing both `source_image_asset_id` and `end_image_asset_id`. Transitions automatically set `loop: false` since they play once between two states. Costs **20 credits** for 4 seconds. ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_ID/generate \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "animation", "name": "Idle to Wave", "source_image_asset_id": "ast_img_idle", "end_image_asset_id": "ast_img_wave", "animation_prompt": "smoothly transitioning from idle stance to waving", "duration": 4 }' ``` ## Auto-Reverse When creating a transition, set `auto_reverse: true` to automatically generate the return transition (B to A) at **0 extra credits**. The response includes a `reverse_job` with its own job ID and asset IDs. ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_ID/generate \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "animation", "name": "Idle to Wave", "source_image_asset_id": "ast_img_idle", "end_image_asset_id": "ast_img_wave", "animation_prompt": "transitioning from idle to waving", "auto_reverse": true, "reverse_name": "Wave to Idle", "duration": 4 }' ``` ```json { "data": { "job_id": "job_fwd_001", "status": "pending", "type": "animation", "item_id": "item_fwd_100", "item_name": "Idle to Wave", "estimated_cost": 20, "asset_ids": { "video": "ast_vid_070", "webm": "ast_vid_071", "hevc": "ast_vid_072" }, "urls": { ... }, "reverse_job": { "job_id": "job_rev_002", "item_id": "item_rev_101", "status": "pending", "cost": 0, "asset_ids": { "video": "ast_vid_080", "webm": "ast_vid_081", "hevc": "ast_vid_082" } } }, "poll_url": "/api/v1/jobs/job_fwd_001" } ``` Auto-reverse is essential for canvas transitions. When building a state machine, every A-to-B transition needs a matching B-to-A. Use auto_reverse to get both for the price of one. ## Duration & Looping The `duration` field accepts values from **4 to 10 seconds**. Default is 4 seconds. The cost formula is `5 x duration` credits for the video portion. The `loop` field defaults to `true` for standard animations and is automatically set to `false` for transitions (when `end_image_asset_id` is provided). Looping animations seamlessly repeat; non-looping animations play once and hold the last frame. ## Output Formats Every animation produces multiple format variants optimized for different platforms: | Type | Format | Use Case | | --- | --- | --- | | `video` | MP4 (H.264) | Universal fallback, opaque background | | `webm` | WebM (VP9) | Transparent video for Chrome, Firefox, Edge | | `hevc` | MOV (HEVC + Alpha) | Transparent video for Safari, iOS, macOS | | `stacked_video` | MP4 (stacked) | Transparent video for Android (color + alpha stacked vertically) | ## Size Variants By default, animations are generated at full resolution. You can configure a collection to automatically produce smaller size variants for each new animation - useful for responsive layouts, thumbnails, or mobile-optimized assets. PATCH /v1/collections/:id/settings ```bash curl -X PATCH https://api.masko.ai/v1/collections/COL_ID/settings \ -H "Authorization: Bearer masko_..." \ -H "Content-Type: application/json" \ -d '{ "publish_params": { "animation_sizes": { "enabled": true, "sizes": [480, 360, 240] } } }' ``` Available sizes are `720`, `480`, `360`, and `240` pixels. When enabled, new animation outputs automatically generate resized variants alongside the original. To backfill missing size variants for existing completed animations, use the dedicated endpoint. Normal usage sends an empty body; it uses the collection's configured sizes, or `[360]` if no sizes are configured, and only creates missing variants. POST /v1/collections/:id/size-variants ```bash curl -X POST https://api.masko.ai/v1/collections/COL_ID/size-variants \ -H "Authorization: Bearer masko_..." \ -H "Content-Type: application/json" \ -d '{}' ``` Do not re-toggle `PATCH /v1/collections/:id/settings` to force a backfill. The `size-variants` endpoint is the idempotent repair path. `force: true` exists only for explicit recovery when you intentionally want to regenerate existing variants. ### Instant CDN URLs for Size Variants When generating an animation, pass the `sizes` array to get pre-allocated CDN URLs for specific size variants immediately in the response: ```bash curl -X POST https://api.masko.ai/v1/collections/COL_ID/generate \ -H "Authorization: Bearer masko_..." \ -H "Content-Type: application/json" \ -d '{ "type": "animation", "name": "wave", "image_prompt": "standing with a smile", "animation_prompt": "waving hello", "duration": 4, "sizes": [480] }' ``` The response includes both original and variant URLs: ```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" } } } ``` The `sizes` parameter is a filter - it only returns URLs for sizes that are enabled in the collection settings. If you request `sizes: [720]` but the collection only has `[480, 360]` configured, no 720 URLs are returned. All variant URLs serve placeholders immediately and are replaced with the real resized files when the size variant workflow completes. Poll the job to check `size_variants.status`. Since `sizes` returns CDN URLs directly in the generate response, you can embed them in your app immediately without waiting for the job to finish or making a second API call. This is the fastest way to wire up responsive animations. ## Cost Reference Summary of animation costs at the default 4-second duration: | Operation | Formula | 4s Cost | | --- | --- | --- | | New image + animation | 1 + (5 x duration) | 21 credits | | Animate existing image | 5 x duration | 20 credits | | Transition (A to B) | 5 x duration | 20 credits | | Auto-reverse (B to A) | Free | 0 credits | --- ---url: /docs/generate/batch--- # Batch Generation & AI Suggestions Generate multiple items in a single API call, and use AI vision to get smart action suggestions for your mascot. ## Batch Generation Send up to **10 generation requests** in a single call. Each item in the `requests` array follows the same format as the single generate endpoint. All requests run in parallel. The endpoint returns `202 Accepted` with the list of queued jobs - generation runs asynchronously. POST /v1/collections/:id/generate-batch ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_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", "duration": 4, "loop": true } ] }' ``` ```json { "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": 21, "asset_ids": { "image": "ast_005", "video": "ast_006", "webm": "ast_007", "hevc": "ast_008" }, "urls": { ... } } ], "total_cost": 23 } } ``` ## When to Batch vs Sequential Use **batch** when you know all the items upfront - for example, generating a full set of poses for a new mascot. All items are created and queued in parallel, which is faster than sending individual requests. Use **sequential requests** when each generation depends on the previous result - for example, generating an image first, then animating it with `source_image_asset_id`. You need the first job to complete before starting the second. ## AI Action Suggestions Ask the AI to suggest action poses for your mascot. The endpoint analyzes your mascot's reference images (or the first completed image in the collection) and returns 6-8 action names that suit the character. This is **free** - no credits are deducted. GET /v1/collections/:id/suggestions ```bash curl https://api.masko.ai/v1/collections/COLLECTION_ID/suggestions \ -H "Authorization: Bearer masko_YOUR_API_KEY" ``` ```json { "data": { "suggestions": [ "Wave Hello", "Thumbs Up", "Thinking", "Celebrate", "Point Right", "Sleeping", "Running", "Confused" ] } } ``` Suggestions automatically exclude items that already exist in the collection, so you can call it repeatedly as you build out your pose library. Use the suggestion names directly as `name` values in a batch generation request. The AI picks names that also work well as image prompts. --- ---url: /docs/generate/cdn--- # CDN URLs & Size Variants Every generation returns CDN URLs immediately, before the assets finish generating. You can embed these URLs in your app right away - they work from the moment you receive them. ## How Instant CDN URLs Work When you call the generate endpoint, Masko uploads a branded placeholder to the CDN path and returns the URL in the response. Your app can start using this URL immediately. Once generation completes, the real asset file replaces the placeholder at the same URL - seamlessly, with no URL change needed on your side. This means you can build your UI, set up image tags, and configure video players before any generation finishes. The placeholder is a lightweight branded image that signals "generating" to users. CDN URLs serve a branded Masko placeholder until generation completes. No broken images, no loading spinners - just a smooth transition from placeholder to final asset. ## URL Format CDN URLs follow this structure: ```text https://assets.masko.ai/{user_prefix}/{collection_slug}/{item_slug}-{hash}.{ext} Example: https://assets.masko.ai/fda8417d/felix-the-fox/waving-hello-a1b2c3d4.png ``` - **user_prefix** - Your account prefix, set automatically. - **collection_slug** - Derived from the collection name. Can be changed by patching the collection's `slug` field. - **item_slug** - Derived from the item name when created. - **hash** - Short unique hash to prevent collisions. - **ext** - File extension based on asset type (png, mp4, webm, mov). ## Size Variants Configure `animation_sizes` in the collection settings to generate pre-rendered size variants. Size variants are **free** - no extra credits. Size variant URLs append the resolution suffix before the extension: ```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` in the generate request to get pre-allocated CDN URLs for specific variants: ```json { "type": "animation", "name": "wave", "image_prompt": "standing and waving", "animation_prompt": "waving hello", "duration": 4, "sizes": [480, 360] } ``` The response `urls` object includes keys like `webm_480`, `hevc_480` alongside the originals. These URLs serve placeholders immediately and swap to real files once the size variant workflow finishes. The `sizes` parameter is a filter on what you get back - it only returns URLs for sizes that are enabled in the collection's `animation_sizes` config. Poll the job's `size_variants.status` field to know when all variants are ready. ## Check CDN Status CDN publishing status for the collection is returned as the `cdn_status` field on `GET /v1/collections/:id`. It lists which assets have been published, their file sizes, and current status. GET /v1/collections/:id ```bash curl https://api.masko.ai/v1/collections/COLLECTION_ID \ -H "Authorization: Bearer masko_YOUR_API_KEY" ``` ```json { "data": { "id": "COLLECTION_ID", "name": "Felix the Fox", "slug": "felix-the-fox", "cdn_status": [ { "asset_id": "ast_img_001", "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": "ast_vid_010", "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 Collection Assets Retrieve assets for a collection. Returns `cdn_url` when available, falling back to signed `file_url`. For metadata-only lists, add `?include_file_urls=false` to skip signed `file_url` generation. GET /v1/collections/:id/assets ```bash curl https://api.masko.ai/v1/collections/COLLECTION_ID/assets \ -H "Authorization: Bearer masko_YOUR_API_KEY" ``` ```json { "data": [ { "id": "ast_img_001", "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": "item_123" } ], "meta": { "pagination": { "total": 1, "limit": 50, "offset": 0, "has_more": false } } } ``` ## Export Get Links JSON Use `GET /v1/collections/:id/cdn-export` when you want the same copy-paste JSON shown in the collection page **Get Links** export modal. This response is not a raw asset list: it groups images, transparent images, animation videos, size variants, and logos by item name so it can be handed directly to an app. This endpoint only returns published CDN assets. If asset hosting is disabled, the collection is not published, or no CDN assets exist yet, it returns `409 cdn_export_not_ready`. GET /v1/collections/:id/cdn-export ```bash curl https://api.masko.ai/v1/collections/COLLECTION_ID/cdn-export \ -H "Authorization: Bearer masko_YOUR_API_KEY" ``` ```json { "collection": "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" } ] } ] } ``` ## Change Slug Update the collection's CDN slug by patching the collection. This changes the URL path for all future assets. Existing CDN URLs are not affected - only new publishes use the new slug. PATCH /v1/collections/:id ```bash curl -X PATCH https://api.masko.ai/v1/collections/COLLECTION_ID \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "slug": "felix" }' ``` ```json { "data": { "id": "COLLECTION_ID", "slug": "felix" } } ``` Slugs must be 2-50 characters, lowercase alphanumeric with dashes. Each slug must be unique across all collections. ## Disabling CDN If you do not need CDN URLs, set `settings.cdn_enabled: false` when creating the collection. Assets are still generated and accessible via signed storage URLs through the jobs endpoint. --- ---url: /docs/generation--- # Generation %% animation https://assets.masko.ai/7fced6/spark-4735/painting-dddca74c-360.webm https://assets.masko.ai/7fced6/spark-4735/painting-bba26b7f-360.mov %% A single unified endpoint handles all generation types: images, animations, logos, and edits. Each request creates a job you can poll for results. POST /v1/collections/:id/generate ## Image Generation Generate a mascot image from a text prompt. Costs **1 credit**. Returns both a full image and a transparent (background-removed) variant. ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_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" }' ``` ```javascript const res = await fetch( 'https://api.masko.ai/v1/collections/COLLECTION_ID/generate', { method: 'POST', headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ type: 'image', name: 'Waving hello', image_prompt: 'waving hello with a friendly smile', }), } ); const { data } = await res.json(); const { job_id, asset_ids, urls } = data; ``` Response includes `asset_ids.image` and `asset_ids.transparent_image` for the two variants. ## Animation Generation Generate an animated mascot from scratch. Costs **21 credits** for a 4-second animation (1 credit for the image + 5 credits/sec for the video). Returns image, video, webm, and hevc assets. ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_ID/generate \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "animation", "name": "Dancing", "image_prompt": "standing in a dance pose, arms raised", "animation_prompt": "dancing energetically with bouncy movements", "duration": 4, "loop": true }' ``` ```javascript const res = await fetch( 'https://api.masko.ai/v1/collections/COLLECTION_ID/generate', { method: 'POST', headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ type: 'animation', name: 'Dancing', image_prompt: 'standing in a dance pose, arms raised', animation_prompt: 'dancing energetically with bouncy movements', duration: 4, loop: true, }), } ); const { data } = await res.json(); const { job_id, asset_ids, urls } = data; // asset_ids: { image, transparent_image, video, webm, hevc } ``` ### Size Variants Pass a `sizes` array to get pre-allocated CDN URLs for smaller variants directly in the response. Available sizes: `720`, `480`, `360`, `240`. Requires [size variants enabled](/docs/generate/animations#size-variants) on the collection. ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_ID/generate \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "animation", "name": "Dancing", "image_prompt": "standing in a dance pose", "animation_prompt": "dancing energetically", "duration": 4, "sizes": [360] }' ``` The response includes both original and variant URLs (`webm_360`, `hevc_360`, `stacked_video_360`). These URLs work immediately as placeholders and switch to the real resized files once processing completes. ## Animate Existing Image Animate an image you already have. Pass `source_image_asset_id` to skip image generation. Costs **20 credits** for 4 seconds (video only, no image cost). ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_ID/generate \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "animation", "name": "Waving animated", "source_image_asset_id": "IMAGE_ASSET_ID", "animation_prompt": "waving hello smoothly", "duration": 4 }' ``` ## Transitions Create a transition between two poses by providing both a source and end image. Set `auto_reverse` to automatically generate the return transition at no extra cost. ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_ID/generate \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "animation", "name": "Idle to Wave", "source_image_asset_id": "IDLE_IMAGE_ID", "end_image_asset_id": "WAVE_IMAGE_ID", "animation_prompt": "transitioning from idle to waving", "auto_reverse": true, "reverse_name": "Wave to Idle" }' ``` When `auto_reverse` is true, the response includes a `reverse_job` object with its own job ID and asset IDs. The reverse costs 0 credits. ## Edit Image Modify an existing image with natural language instructions. Costs **1 credit**. ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_ID/generate \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "edit", "item_id": "EXISTING_ITEM_ID", "source_image_asset_id": "IMAGE_ASSET_ID", "edit_instructions": "Add a santa hat and snow falling in the background" }' ``` ## Logo Pack Generate a full logo pack with multiple style variants. Costs **5 credits**. ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_ID/generate \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "logo", "name": "App Logo", "logo_description": "Iconic representation of the character face", "logo_style_name": "Flat", "logo_style_instruction": "Flat design with solid colors, no gradients or shadows" }' ``` ## Batch Generation Generate multiple items in a single request. Each entry in the batch follows the same format as the single generate endpoint. Returns `202 Accepted` with the queued jobs. POST /v1/collections/:id/generate-batch ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_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" }, { "type": "image", "name": "Thinking", "image_prompt": "thinking with hand on chin" }, { "type": "animation", "name": "Dancing", "image_prompt": "dancing pose", "animation_prompt": "dancing", "duration": 4 } ] }' ``` ## Preview Test a generation prompt without creating items or spending credits. Returns a temporary preview image URL. POST /v1/generate/preview ```bash curl -X POST https://api.masko.ai/v1/generate/preview \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "collection_id": "COLLECTION_ID", "prompt": "a friendly fox mascot waving hello" }' ``` ## Polling Jobs Every generation returns a `job_id`. Poll the job endpoint to check progress. Use `?wait=true` for long polling - the server holds the connection until the job completes or times out. GET /v1/jobs/:id ```bash # Standard polling curl https://api.masko.ai/v1/jobs/JOB_ID \ -H "Authorization: Bearer masko_YOUR_API_KEY" # Long polling (waits for completion) curl https://api.masko.ai/v1/jobs/JOB_ID?wait=true \ -H "Authorization: Bearer masko_YOUR_API_KEY" ``` ```javascript // Long polling - simplest approach const res = await fetch( `https://api.masko.ai/v1/jobs/${jobId}?wait=true`, { headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY' }, } ); const { data: job } = await res.json(); if (job.status === 'completed') { console.log('URLs:', job.urls); } ``` CDN URLs are returned immediately in the generate response, before generation finishes. You can embed these URLs in your app right away - they serve a placeholder until the real asset is ready, then automatically switch over. For animations, pass `sizes: [360]` to also get instant CDN URLs for smaller variants - no extra API call needed. --- ---url: /docs/canvas--- # Canvas & Templates A canvas is an interactive state machine for your mascot. Define poses (nodes), transitions (edges), conditions, and inputs so the runtime can resolve app or user activity into mascot behavior. ## Concepts - [Nodes (Poses)] - Each node represents a mascot pose or state - idle, waving, thinking, etc. Nodes reference an animation asset. - [Edges (Transitions)] - Edges connect nodes and define how the mascot moves between poses. Each edge is an animation from one pose to another. - [Loops] - Looping animations play continuously within a pose. Non-looping animations play once then hold the last frame. - [Conditions & Inputs] - Edges can depend on resolved inputs such as `behavior::interact`, `behavior::attention`, or `node::nodeTime`. Raw hover and click events are normalized by the runtime before new desktop graphs see them. %% /features %% ## Create a Canvas Create a canvas within a collection. A canvas starts empty, or you can pass a `template_id` to populate it with a pre-built set of nodes and edges in the same call. POST /v1/collections/:id/canvases ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_ID/canvases \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Main Canvas" }' ``` ```javascript const res = await fetch( 'https://api.masko.ai/v1/collections/COLLECTION_ID/canvases', { method: 'POST', headers: { Authorization: 'Bearer masko_YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Main Canvas' }) } ); const { data: canvas } = await res.json(); // canvas.id is used in subsequent requests ``` ## Create from a Template Templates define a pre-built set of nodes and edges. Pass `template_id` to the same `POST /canvases` endpoint to populate the new canvas with poses and transitions and kick off image generation for each node. POST /v1/collections/:id/canvases ```bash # Create a new canvas from the Claude Code 4-state template curl -X POST https://api.masko.ai/v1/collections/COLLECTION_ID/canvases \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Main Canvas", "template_id": "claude-code-4state" }' ``` ```javascript const res = await fetch( `https://api.masko.ai/v1/collections/COLLECTION_ID/canvases`, { method: 'POST', headers: { Authorization: 'Bearer masko_YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Main Canvas', template_id: 'claude-code-4state' }) } ); const { data } = await res.json(); // data.graph.nodes, data.graph.edges, data.jobs, data.node_mapping ``` ## Update a Graph Safely Read the current canvas, modify its complete graph locally, and replace it with the revision hash returned by that read. Masko preserves generated fields for unchanged node and edge IDs. A stale revision returns `409`, so concurrent editors cannot silently overwrite each other. GET /v1/collections/:id/canvases/:canvasId ```bash curl https://api.masko.ai/v1/collections/COLLECTION_ID/canvases/CANVAS_ID \ -H "Authorization: Bearer masko_YOUR_API_KEY" ``` Save the returned `data.graph` to `canvas.json`, edit that complete graph, and send it back with `data.graph_content_hash`: PATCH /v1/collections/:id/canvases/:canvasId ```bash curl -X PATCH https://api.masko.ai/v1/collections/COLLECTION_ID/canvases/CANVAS_ID \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d "$(jq -n \ --arg expected_graph_hash 'HASH_FROM_GET' \ --slurpfile graph canvas.json \ '{graph:$graph[0], expected_graph_hash:$expected_graph_hash}')" ``` For server-owned edge metadata changes such as playback speed, the dedicated atomic edge endpoint remains available: PATCH /v1/collections/:id/canvases/:canvasId/edges/:edgeId ```bash curl -X PATCH https://api.masko.ai/v1/collections/COLLECTION_ID/canvases/CANVAS_ID/edges/idle-to-interact \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "speed": 3 }' ``` ## Generate Canvas Assets Generate pending node images and/or transition animations in a single call. Use `dry_run: true` to preview the planned work, prompts, and credit cost before spending credits. The preview returns `plan_id` and `graph_content_hash`; send them back as `approved_plan_id` and `expected_graph_hash` when executing. Use `targets: "images"` for a pose review pass, then `targets: "animations"` after the poses look right. Pass `node_ids` and `edge_ids` to select an exact part of a larger graph. Reverse animations are free when the graph marks the return edge as a reverse of the forward edge. POST /v1/collections/:id/canvases/:canvasId/generate-all ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_ID/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": ["idle-to-wave", "wave-loop", "wave-to-idle"], "duration": 4, "skip_completed": true }' # After approving the returned plan and price: curl -X POST https://api.masko.ai/v1/collections/COLLECTION_ID/canvases/CANVAS_ID/generate-all \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "targets": "animations", "edge_ids": ["idle-to-wave", "wave-loop", "wave-to-idle"], "approved_plan_id": "PLAN_ID_FROM_DRY_RUN", "expected_graph_hash": "GRAPH_HASH_FROM_DRY_RUN" }' ``` The response includes `plan_id`, `graph_content_hash`, the normalized `selection`, `planned_jobs`, `generated`, `skipped_items`, `reverse_free`, `already_complete`, `estimated_cost`, `would_charge_credits`, and `actual_cost`. Unknown selected IDs fail before any work is queued or credits are spent. Reverse transitions are free (0 credits). When generating a transition from A to B with a reverse edge that references it, the B to A animation is created automatically at no additional cost. ## Check Progress Canvas generation progress is returned as the `status` field on the canvas detail response. Poll `GET /v1/collections/:id/canvases/:canvasId` and read `status` to see node and edge counts. Use `status.media` as the main readiness interface: - `status.media.generation.ready` means node images and parent edge videos are complete. - `status.media.preview.ready` means the canvas editor can play every concrete edge using base WebM and HEVC derivatives. - `status.media.preview.waiting_edges` means parent videos exist but the original generation job is still preparing derivatives; wait and poll again. - `status.media.preview.repairable` means parent videos exist, no source job is still preparing derivatives, and missing base preview derivatives can be repaired safely. - `status.media.variants` reports optimized size variants that exist or are incomplete; preview repair does not create size variants. - `status.failed_nodes` and `status.failed_edges` contain terminal generation failures with a job ID and customer-safe error message. Review the message before asking for approval for a new paid attempt. `status.nodes.pending` continues to count every incomplete node, including failed nodes, for backward compatibility. `status.nodes.failed` is the terminal subset. `status.ready`, `status.generated_ready`, and `status.preview_ready` are kept for backward compatibility. GET /v1/collections/:id/canvases/:canvasId ```bash curl https://api.masko.ai/v1/collections/COLLECTION_ID/canvases/CANVAS_ID \ -H "Authorization: Bearer masko_YOUR_API_KEY" # Response: # { # "data": { # "id": "...", # "graph": { ... }, # "status": { # "nodes": { "total": 4, "completed": 4, "pending": 0, "failed": 0 }, # "edges": { "total": 12, "completed": 8, "pending": 3, "failed": 1 }, # "ready": false, # "generated_ready": false, # "preview_ready": false, # "media": { # "generation": { # "ready": false, # "nodes": { "total": 4, "completed": 4, "pending": 0, "failed": 0 }, # "edges": { "total": 12, "completed": 8, "pending": 3, "failed": 1 } # }, # "preview": { # "ready": false, # "repairable": false, # "repairable_edges": 0, # "waiting_edges": 0, # "missing_edges": 4, # "missing_formats": ["webm", "hevc"] # }, # "variants": { "sizes": [], "missing": [] } # }, # "failed_nodes": [], # "failed_edges": [] # } # } # } ``` If `status.media.preview.waiting_edges` is greater than zero, wait and poll the canvas again. If `status.media.preview.repairable` is true, repair missing base derivatives with a dry-run first. Inspect `status.edge_media[].base.missing` only when you need the exact edge-level missing formats. POST /v1/collections/:id/canvases/:canvasId/repair ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_ID/canvases/CANVAS_ID/repair \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "target": "video_derivatives", "dry_run": true }' ``` Only call again with `"dry_run": false` after checking the returned `repairs`, `in_flight`, and `skipped` lists. This repair does not regenerate animations, does not run collection-wide generation, does not create size variants, and does not archive existing assets. ## Export Export the canvas as a `MaskoAnimationConfig` JSON object ready to use with the Masko embed player. All asset URLs point to the CDN. GET /v1/collections/:id/canvases/:canvasId/export ```bash curl https://api.masko.ai/v1/collections/COLLECTION_ID/canvases/CANVAS_ID/export \ -H "Authorization: Bearer masko_YOUR_API_KEY" ``` ```javascript const res = await fetch( `https://api.masko.ai/v1/collections/COLLECTION_ID/canvases/CANVAS_ID/export`, { headers: { Authorization: 'Bearer masko_YOUR_API_KEY' } } ); const { data: config } = await res.json(); // Pass config to your Masko embed player // ``` ## Built-in Templates These templates are available out of the box. Use the template ID as `template_id` when calling `POST /v1/collections/:id/canvases`. Some built-in templates are compatibility scaffolds for older Claude Code style graphs; new desktop app-control graphs should prefer the `behavior::*`, `action::*`, and `node::*` contract. - [claude-code-4state] - 4 poses (idle, thinking, talking, celebrating) with 16 transitions at 4 seconds each. Full interactive mascot - approximately 400 credits. - [claude-code-4state-lite] - Same 4 poses with shorter transitions for a lighter-weight mascot. Lower credit cost with faster generation times. %% /features %% ## Custom Templates You can save your own templates from an existing canvas or from a raw JSON definition, then apply them to any collection. This lets you reuse state machine graphs across projects. ```bash curl -X POST https://api.masko.ai/v1/canvas-templates \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "My Custom Template", "description": "2-state widget with a shared attention behavior", "template": { "nodes": [ { "key": "idle", "name": "Idle", "imagePrompt": "standing relaxed", "position": { "x": 0, "y": 0 } }, { "key": "active", "name": "Active", "imagePrompt": "alert and ready", "position": { "x": 300, "y": 0 } } ], "edges": [ { "source": "idle", "target": "active", "duration": 4, "description": "becoming alert", "conditions": [{ "input": "behavior::attention", "op": "==", "value": true }] } ], "inputs": [ { "name": "behavior::attention", "type": "boolean", "default": false, "system": true } ] } }' ``` ```bash curl -X POST https://api.masko.ai/v1/canvas-templates \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "My Canvas Template", "description": "Saved from an existing canvas", "canvas_id": "CANVAS_ID", "collection_id": "COLLECTION_ID" }' ``` The response includes the template ID. Use it with `template_id` in the canvas create call to apply it to a new canvas: ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_ID/canvases \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "My Canvas", "template_id": "TEMPLATE_ID" }' ``` See [Canvas Templates](/docs/canvas/templates) for the full reference including node/edge overrides and listing templates. --- ---url: /docs/canvas/build--- # Build a Canvas Create an interactive state machine with poses, transitions, and conditions. A canvas defines how your mascot performs resolved app and user behavior. ## What is a Canvas? A canvas is a state machine where each state is a mascot pose (an image or animation) and transitions are animated videos that play when moving between states. You define conditions and inputs that trigger transitions, usually resolved inputs such as `behavior::interact`, `behavior::attention`, or `node::nodeTime`. The Masko embed player reads the canvas configuration and handles playback. ## Nodes & Edges **Nodes** represent poses or states. Each node references an image asset ID and has a position on the canvas editor grid. The `itemName` is the human-readable label. **Edges** are transitions between nodes. Each edge connects a `source` node to a `target` node and can have: - **conditions** - Rules that must be met to trigger the transition (see below). - **priority** - When multiple edges can fire, the highest priority wins. Higher number = higher priority. - **speed** - Playback speed multiplier (e.g. 1.5 for faster, 0.5 for slower). Default is 1. - **duration** - Video duration in seconds. Used for generation. - **description** - Animation prompt used when generating the transition video. - **reverse** - If true, this edge plays the reverse of another edge's video instead of generating a new one. Use `source: "*"` (any state) for edges that can fire from any node - useful for global triggers like an error state or reset. ## Conditions & Inputs Each edge can have an array of `conditions`. A condition has three fields: - **input** - The name of the input to check (e.g. `"behavior::interact"`, `"node::nodeTime"`). - **op** - The comparison operator: `"=="`, `"!="`, `">"`, `"<"`, `">="`, `"<="`. - **value** - The value to compare against. The canvas supports these input types: - **boolean** - True/false values. Example: `behavior::interact`, `behavior::working`. - **number** - Numeric values. Example: `node::nodeTime`, `node::loopCount`. - **trigger** - Fire-once events. New desktop app-control graphs should normally use resolved behavior/action inputs instead. - **string** - Text values. Rare for desktop mascot graphs. ## Create a Canvas Create a canvas with an inline graph definition. The graph contains nodes, edges, viewport settings, and input declarations. POST /v1/collections/:id/canvases ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_ID/canvases \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Main Canvas", "graph": { "nodes": [ { "id": "ast_img_idle", "x": 0, "y": 0, "itemName": "Idle" }, { "id": "ast_img_wave", "x": 300, "y": 0, "itemName": "Waving" } ], "edges": [ { "id": "edge_001", "source": "ast_img_idle", "target": "ast_img_wave", "duration": 4, "description": "transitioning from idle to waving", "conditions": [ { "input": "behavior::attention", "op": "==", "value": true } ] }, { "id": "edge_002", "source": "ast_img_wave", "target": "ast_img_idle", "duration": 4, "description": "returning from wave to idle", "conditions": [ { "input": "behavior::rest", "op": "==", "value": true } ], "reverse": true, "reverseOfEdgeId": "edge_001" } ], "viewport": { "x": 0, "y": 0, "zoom": 0.8 }, "inputs": [ { "name": "behavior::attention", "type": "boolean", "default": false }, { "name": "behavior::rest", "type": "boolean", "default": true } ] } }' ``` ## Update the Graph Fetch the canvas first, update the complete graph locally, and send it via PATCH with the returned `graph_content_hash` as `expected_graph_hash`. The entire authored graph (nodes, edges, inputs, viewport) is replaced. If the hash is stale, Masko returns `409` and leaves the newer graph untouched. PATCH /v1/collections/:id/canvases/:canvasId ## Delete a Canvas Remove a canvas from a collection. This removes the graph definition; the underlying items and their generated assets stay in the collection. DELETE /v1/collections/:id/canvases/:canvasId ## Example: Sleep/Wake Canvas A complete two-state canvas where the mascot sleeps by default and wakes up when the desktop runtime resolves direct interaction as `behavior::interact`. ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_ID/canvases \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Sleep Wake", "graph": { "nodes": [ { "id": "ast_sleeping", "x": 0, "y": 0, "itemName": "Sleeping" }, { "id": "ast_awake", "x": 400, "y": 0, "itemName": "Awake" } ], "edges": [ { "id": "edge_wake", "source": "ast_sleeping", "target": "ast_awake", "duration": 4, "description": "waking up with a stretch and yawn", "conditions": [ { "input": "behavior::interact", "op": "==", "value": true } ], "priority": 10 }, { "id": "edge_sleep", "source": "ast_awake", "target": "ast_sleeping", "duration": 4, "description": "slowly falling asleep, eyes drooping", "conditions": [ { "input": "behavior::rest", "op": "==", "value": true } ], "priority": 10 } ], "viewport": { "x": 0, "y": 0, "zoom": 0.8 }, "inputs": [ { "name": "behavior::interact", "type": "boolean", "default": false }, { "name": "behavior::rest", "type": "boolean", "default": true } ] } }' ``` ```json { "data": { "id": "canvas_abc123", "name": "Sleep Wake", "collection_id": "COLLECTION_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" } } ``` After creating a canvas with a graph, use the `generate-all` endpoint to generate all transition animations at once. Reverse transitions are created for free with auto_reverse. --- ---url: /docs/canvas/templates--- # Canvas Templates Templates are blueprints for canvases. They define the nodes (poses), edges (transitions), conditions, and prompts - but no generated assets. When you apply a template, Masko creates items and kicks off image generation for every node. ## What Templates Provide A template contains the full graph structure: node positions, names, image prompts, edge connections, conditions, durations, and input definitions. It does not contain any generated assets. When applied, the template creates new items in the collection, generates images for each node, and saves the graph to the canvas. You then use `generate-all` to create all the transition animations. ## Built-in Templates Two templates are available out of the box. Pass the template ID as `template_id` when creating a canvas. - [claude-code-4state]() - 4 poses (idle, thinking, talking, celebrating) with 16 transitions at 4 seconds each. Full interactive mascot with all directions covered. Approximately 288 credits for images + animations. - [claude-code-4state-lite]() - Same 4 poses with fewer transitions and shorter durations for a lighter-weight mascot. Approximately 144 credits - half the cost of the full template. ## List Templates Fetch all available templates - both built-in and your own saved templates. Use the `source` query parameter to filter. GET /v1/canvas-templates ```bash # All templates (built-in + yours) curl https://api.masko.ai/v1/canvas-templates \ -H "Authorization: Bearer masko_YOUR_API_KEY" # Only built-in templates curl "https://api.masko.ai/v1/canvas-templates?source=builtin" \ -H "Authorization: Bearer masko_YOUR_API_KEY" # Only your saved templates curl "https://api.masko.ai/v1/canvas-templates?source=mine" \ -H "Authorization: Bearer masko_YOUR_API_KEY" ``` ```json { "data": [ { "id": "claude-code-4state", "name": "Claude Code 4-State", "description": "4 poses with 16 transitions for a full interactive mascot", "source": "builtin", "nodes": [ { "key": "idle", "name": "Idle", "imagePrompt": "standing relaxed, arms at sides" }, { "key": "thinking", "name": "Thinking", "imagePrompt": "hand on chin, looking up" }, { "key": "talking", "name": "Talking", "imagePrompt": "mouth open, gesturing" }, { "key": "celebrating", "name": "Celebrating", "imagePrompt": "arms raised, excited" } ], "edges": [ ... ], "inputs": [ ... ] }, { "id": "my-custom-template", "name": "My 3-State Widget", "description": "Custom template for sidebar widget", "source": "user", "public": false, "nodes": [ ... ], "edges": [ ... ], "created_at": "2026-03-25T14:30:00Z" } ] } ``` ## Apply a Template Create a new canvas from a template in one call. The endpoint creates items for each node, generates images, and saves the graph. The response includes a `node_mapping` that maps template node keys to the created item and asset IDs, plus a list of generation jobs. POST /v1/collections/:id/canvases ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_ID/canvases \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Main Canvas", "template_id": "claude-code-4state" }' ``` ```json { "data": { "graph": { "nodes": [ { "id": "ast_idle_001", "x": 0, "y": 0, "itemName": "Idle" }, { "id": "ast_think_002", "x": 300, "y": 0, "itemName": "Thinking" }, { "id": "ast_talk_003", "x": 0, "y": 300, "itemName": "Talking" }, { "id": "ast_celeb_004", "x": 300, "y": 300, "itemName": "Celebrating" } ], "edges": [ ... ], "viewport": { "x": 0, "y": 0, "zoom": 0.8 }, "inputs": [ ... ] }, "jobs": [ { "job_id": "job_t001", "node_key": "idle" }, { "job_id": "job_t002", "node_key": "thinking" }, { "job_id": "job_t003", "node_key": "talking" }, { "job_id": "job_t004", "node_key": "celebrating" } ], "node_mapping": { "idle": { "item_id": "item_idle_01", "asset_id": "ast_idle_001", "urls": { ... } }, "thinking": { "item_id": "item_think_02", "asset_id": "ast_think_002", "urls": { ... } }, "talking": { "item_id": "item_talk_03", "asset_id": "ast_talk_003", "urls": { ... } }, "celebrating": { "item_id": "item_celeb_04", "asset_id": "ast_celeb_004", "urls": { ... } } }, "total_cost": 4 } } ``` The `total_cost` from applying a template only covers image generation (1 credit per node). Animation generation is a separate step via `generate-all` which costs 5 credits/sec per transition. ## Customize with Overrides Pass `node_overrides` and `edge_overrides` to customize template prompts and names without modifying the template itself. Node overrides are keyed by the template's node key. Edge overrides are keyed by `source->target` format. ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_ID/canvases \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Main Canvas", "template_id": "claude-code-4state", "node_overrides": { "idle": { "name": "Resting", "imagePrompt": "sitting peacefully on a rock, eyes half-closed" }, "celebrating": { "imagePrompt": "doing a backflip with sparkles" } }, "edge_overrides": { "idle->thinking": { "description": "slowly standing up and scratching head" } } }' ``` ## Save Your Own Save a template from an existing canvas or from a raw JSON graph definition. Set `public: true` to make it available to other users. POST /v1/canvas-templates ```bash curl -X POST https://api.masko.ai/v1/canvas-templates \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "My Widget Template", "description": "3-state sidebar widget with shared behavior inputs", "canvas_id": "CANVAS_ID", "collection_id": "COLLECTION_ID", "public": false }' ``` ```bash curl -X POST https://api.masko.ai/v1/canvas-templates \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Simple 2-State", "description": "Minimal idle/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": 4, "description": "becoming alert", "conditions": [{ "input": "behavior::attention", "op": "==", "value": true }] }, { "source": "active", "target": "idle", "duration": 4, "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" } } ``` ## Get or Update a Template Fetch or rename one of your saved templates. Built-in templates are read-only. GET /v1/canvas-templates/:id ```bash curl https://api.masko.ai/v1/canvas-templates/tmpl_abc123 \ -H "Authorization: Bearer masko_YOUR_API_KEY" ``` PATCH /v1/canvas-templates/:id ```bash curl -X PATCH https://api.masko.ai/v1/canvas-templates/tmpl_abc123 \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "My Widget v2", "description": "Updated description", "public": true }' ``` --- ---url: /docs/canvas/generate-all--- # Generate Canvas Assets Generate pending node images and/or transition videos for your canvas. Use `dry_run: true` first. The plan returns `plan_id` and `graph_content_hash`; send both back when executing so Masko rejects a changed graph, prompt, or selection before spending credits. POST /v1/collections/:id/canvases/:canvasId/generate-all ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_ID/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, "duration": 4 }' ``` ```javascript const res = await fetch( `https://api.masko.ai/v1/collections/${collectionId}/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, duration: 4, }), } ); 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/collections/${collectionId}/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'], approved_plan_id: data.plan_id, expected_graph_hash: data.graph_content_hash, }), } ); ``` ## How It Works The endpoint scans the canvas and dispatches missing node images and/or edge videos based on `targets`. The `skip_completed` parameter defaults to `true`, meaning graph parts that already have assigned assets are skipped so running the endpoint twice does not duplicate in-flight or completed work. Use `node_ids` and `edge_ids` to plan an exact graph selection. Omit either field to consider every part in that category. Unknown IDs are rejected before any job is created or credit is spent. Execute with the returned `plan_id` as `approved_plan_id` and `graph_content_hash` as `expected_graph_hash`. A changed graph, prompt, option, or selection returns `409` instead of silently changing approved work. The response separates generation accounting into: - `dry_run`: true when no jobs/assets were created and no credits were deducted. - `plan_id`: identity of the exact graph, prompts, scope, and generation options. - `graph_content_hash`: canvas revision used to create the plan. - `planned_jobs`: node, sticker, and edge work that would be queued, including prompts. Planned animation and sticker jobs include signed source/target asset URLs when those input images already exist; media that would be generated by the run is represented by its prompt and cost, not a fake preview URL. - `generated`: newly queued node or edge jobs. - `skipped`: legacy skipped edge count. - `skipped_items`: graph parts not queued, including target-filtered nodes/edges and graph parts with existing asset IDs. - `reverse_free`: reverse edges that cost 0 credits because they reuse a forward transition. - `already_complete`: nodes and edges whose assigned assets are already completed. - `estimated_cost`: cost calculated before dispatch. - `actual_cost`: credits deducted for this request. - `would_charge_credits`: dry-run estimate for the credits a real request would deduct. Each generated job follows the same lifecycle as a regular animation job - you can poll individual jobs or read the `status` field on the canvas detail response to track overall progress. ## Edge Classification Not all edges cost the same. The endpoint classifies each edge before generation: | Type | Condition | Cost | Notes | | --- | --- | --- | --- | | Loop | source == target | Paid | Looping animation on a single pose | | Forward | source != target | Paid | Transition from one pose to another | | Reverse | Auto-generated from forward | 0 credits | Triggered automatically when forward completes | | Any State | source = "*" | Skipped | Virtual edges resolved at runtime, no video needed | ## Cost Calculation Only loops and forward transitions cost credits. The formula is: `(loops + forwards) x 5 x duration` For example, a canvas with 4 loop edges and 3 forward edges at 4 seconds each: ```text Paid edges: 4 loops + 3 forwards = 7 Cost per edge: 5 credits/sec x 4 sec = 20 credits Total: 7 x 20 = 140 credits Reverse edges (auto): 3 (one per forward) = 0 credits Any State edges: skipped = 0 credits ``` The response includes `estimated_cost`, `actual_cost`, and legacy `total_cost` fields so you know the exact cost. If you do not have enough credits, the request returns a 402 error with the required amount. This endpoint returns `200 OK` with the list of queued jobs - generation runs asynchronously. ## Auto-Reverse When a forward transition completes (e.g. Idle to Waving), the reverse transition (Waving to Idle) is automatically triggered at 0 credits. You do not need to request it separately. The reverse job appears in the canvas status response alongside the forward jobs. If a forward edge already has a matching reverse edge in the canvas, the auto-reverse fills in the reverse edge video. If no reverse edge exists, one is created automatically. ## Poll Progress After kicking off generation, fetch the canvas and read the `status` field to track overall progress: Before retrying, inspect `status.failed_nodes` and `status.failed_edges`. Each entry includes the failed part, its job ID, and a customer-safe error message. A retry is a new paid attempt and should require approval. `status.nodes.pending` includes failed nodes for backward compatibility; `status.nodes.failed` identifies the terminal subset. GET /v1/collections/:id/canvases/:canvasId ```bash curl https://api.masko.ai/v1/collections/COLLECTION_ID/canvases/CANVAS_ID \ -H "Authorization: Bearer masko_YOUR_API_KEY" # Response: # { # "data": { # "id": "abc-123", # "graph": { ... }, # "status": { # "nodes": { "total": 4, "completed": 4, "pending": 0, "failed": 0 }, # "edges": { "total": 14, "completed": 10, "pending": 3, "failed": 1 }, # "ready": false, # "failed_nodes": [], # "failed_edges": [] # } # } # } ``` ```javascript async function waitForCanvas(collectionId, canvasId) { while (true) { const res = await fetch( `https://api.masko.ai/v1/collections/${collectionId}/canvases/${canvasId}`, { headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY' }, } ); const { data: canvas } = await res.json(); const status = canvas.status; const total = status.edges.total || 1; console.log(`Progress: ${status.edges.completed}/${total} edges`); if (status.media.generation.ready) { console.log('All edges have parent videos.'); if (status.media.preview.ready) { console.log('Preview media is ready. Canvas is playable in the editor.'); } else if (status.media.preview.waiting_edges > 0) { console.log('Preview derivatives are still being prepared.'); } else if (status.media.preview.repairable) { console.log('Preview derivatives are missing. Run canvas repair-media dry-run.'); } return canvas; } if (status.edges.failed > 0) { console.warn(`${status.edges.failed} edges failed`); } await new Promise((r) => setTimeout(r, 5000)); } } ``` When `status.media.generation.ready` is `true`, every node image and every non-Any-State edge video has a completed parent asset. Assigned pending asset IDs do not count as ready. For editor playback, wait for `status.media.preview.ready`; if `status.media.preview.waiting_edges > 0`, keep polling, and if `status.media.preview.repairable` is true, dry-run preview-media repair. ## Regenerate a Single Edge To re-run one edge without re-dispatching the whole canvas, use the per-edge endpoint: POST /v1/collections/:id/canvases/:canvasId/edges/:edgeId/generate ```bash curl -X POST https://api.masko.ai/v1/collections/COLLECTION_ID/canvases/CANVAS_ID/edges/EDGE_ID/generate \ -H "Authorization: Bearer masko_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "duration": 4 }' ``` The endpoint returns `202 Accepted` and a `job_id`. Only non-reverse edges can be dispatched individually. Poll the job as usual. Use this when one edge failed or you updated the edge prompt and want to regenerate it without spending credits on the rest of the canvas. --- ---url: /docs/canvas/export--- # Export Canvas Once all edge videos are generated, export the canvas as a `MaskoAnimationConfig` JSON object. This config contains everything needed to run your interactive mascot - nodes, edges, video URLs, and input mappings. GET /v1/collections/:id/canvases/:canvasId/export ```bash curl https://api.masko.ai/v1/collections/COLLECTION_ID/canvases/CANVAS_ID/export \ -H "Authorization: Bearer masko_YOUR_API_KEY" ``` ```javascript const res = await fetch( `https://api.masko.ai/v1/collections/${collectionId}/canvases/${canvasId}/export`, { headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY' }, } ); const { data: config } = await res.json(); // Pass config to your Masko embed player ``` ## The MaskoAnimationConfig The `MaskoAnimationConfig` is a versioned JSON format (current version `2.0`) that describes an interactive mascot state machine. It is the output of the export endpoint and the input to the Masko embed player. The config is self-contained - all asset URLs point to the CDN and can be used directly in a browser or desktop app. ## Response Structure ```json { "version": "2.0", "name": "My Mascot", "initialNode": "idle", "autoPlay": true, "clickEffect": "next_state", "nodes": [ { "id": "idle", "name": "Idle", "transparentThumbnailUrl": "https://assets.masko.ai/col_abc/idle.png" }, { "id": "waving", "name": "Waving", "transparentThumbnailUrl": "https://assets.masko.ai/col_abc/waving.png" }, { "id": "thinking", "name": "Thinking", "transparentThumbnailUrl": "https://assets.masko.ai/col_abc/thinking.png" } ], "edges": [ { "id": "idle-loop", "source": "idle", "target": "idle", "isLoop": true, "duration": 4, "videos": { "webm": "https://assets.masko.ai/col_abc/idle-loop.webm", "hevc": "https://assets.masko.ai/col_abc/idle-loop.mp4" } }, { "id": "idle-to-waving", "source": "idle", "target": "waving", "isLoop": false, "duration": 4, "videos": { "webm": "https://assets.masko.ai/col_abc/idle-to-waving.webm", "hevc": "https://assets.masko.ai/col_abc/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/col_abc/waving-to-idle.webm", "hevc": "https://assets.masko.ai/col_abc/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. | | `inputs[]` | array | Programmatic inputs. Each has `name`, `type`, and `default`. | ## Using in Web The config includes both `webm` and `hevc` video URLs for each edge. Use WebM for Chrome and Firefox, and HEVC (MP4) for Safari. Here is a minimal example for format selection: ```html
``` ## Using in Desktop The Masko Code desktop app loads the `MaskoAnimationConfig` directly. Export the config, save it as a JSON file, and point the app to it. The desktop player handles format selection, caching, and state machine logic automatically. The export endpoint only succeeds when all node images and edge videos have completed assets. Fetch the canvas first and wait for `status.media.generation.ready: true` before exporting; pending assigned asset IDs do not count as ready. For editor playback, also wait for `status.media.preview.ready: true`. --- ---url: /docs/manage/jobs--- # Jobs & Polling Every generation creates a job. Track progress and get results by polling the job endpoint, or use long-polling to wait for completion without repeated requests. ## Job Lifecycle Every job moves through a simple lifecycle: `pending` - `processing` - `completed` or `failed`. A job enters `pending` when the generation request is accepted, transitions to `processing` once a worker picks it up, and resolves to either `completed` with asset URLs or `failed` with an error message. ```text pending --> processing --> completed \--> failed ``` ## List Jobs Retrieve all your jobs with optional filters. Use query parameters to narrow results by status, collection, or type. GET /v1/jobs ```bash # List all jobs curl https://api.masko.ai/v1/jobs \ -H "Authorization: Bearer masko_YOUR_API_KEY" # Filter by status and collection curl "https://api.masko.ai/v1/jobs?status=completed&collection_id=COLLECTION_ID" \ -H "Authorization: Bearer masko_YOUR_API_KEY" # Filter by type curl "https://api.masko.ai/v1/jobs?type=animation" \ -H "Authorization: Bearer masko_YOUR_API_KEY" ``` ```javascript const params = new URLSearchParams({ status: 'completed', collection_id: collectionId, }); const res = await fetch( `https://api.masko.ai/v1/jobs?${params}`, { headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY' }, } ); const { data: jobs } = await res.json(); // jobs: [{ id, status, type, created_at, ... }, ...] ``` Available filters: `?status=pending|processing|completed|failed`, `?collection_id=...`, `?type=item_generation|image_generation|logo|animation|reverse|size_variant|size_variant_batch|export_animation|scene_generation`. ## Get Job Detail Fetch a single job by ID. Completed jobs include full output data with asset IDs and CDN URLs. GET /v1/jobs/:id ```bash curl https://api.masko.ai/v1/jobs/JOB_ID \ -H "Authorization: Bearer masko_YOUR_API_KEY" # Completed job response: # { # "data": { # "id": "job_abc123", # "status": "completed", # "type": "item_generation", # "collection_id": "col_xyz", # "cost_credits": 21, # "created_at": "2026-03-28T10:00:00Z", # "updated_at": "2026-03-28T10:01:32Z", # "item_id": "item_456", # "item_name": "Wave", # "urls": { # "image": "https://assets.masko.ai/col_xyz/image.png", # "webm": "https://assets.masko.ai/col_xyz/video.webm" # } # } # } ``` ```javascript const res = await fetch( `https://api.masko.ai/v1/jobs/${jobId}`, { headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY' }, } ); 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 Instead of polling repeatedly, use long-polling. Add `?wait=true` and the server holds the connection open until the job completes or the timeout is reached. The default timeout is 120 seconds, configurable with `?timeout=`. 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" # 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) { const res = await fetch( `https://api.masko.ai/v1/jobs/${jobId}?wait=true&timeout=120`, { headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY' }, } ); const { data: job } = await res.json(); if (job.status === 'completed') { return job; } if (job.status === 'failed') { throw new Error(job.error); } // Timed out but still processing - try again return waitForJob(jobId); } const job = await waitForJob('job_abc123'); console.log('Done:', job.urls); ``` ## When to Poll vs Webhooks - **Long-polling** - simplest approach. Best for scripts, CLI tools, and prototyping. One request, one response, no infrastructure needed. - **Webhooks** - best for production. Your server gets notified when jobs complete. No open connections, handles high volumes, works behind load balancers. - **Hybrid** - use webhooks for background processing and long-polling for user-facing flows where you need immediate feedback. - [Webhooks](/docs/manage/webhooks) - Get notified when jobs complete instead of polling. Set up webhook endpoints for production use. - [List & Browse](/docs/manage/collections) - Browse your projects, collections, and assets to find the resources you need. --- ---url: /docs/manage/webhooks--- # Webhooks Get notified when jobs complete instead of polling. Register a webhook URL and Masko sends a POST request with job results as soon as they are ready. ## Create a Webhook Register a webhook endpoint with the events you want to receive. The response includes a `secret` for verifying payloads. 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 ``` Save the `secret` immediately. It cannot be retrieved later. If you lose it, delete the webhook and create a new one. ## Webhook Payload When a subscribed event fires, Masko sends a POST request to your URL with a JSON body: ```json { "event": "job.completed", "job_id": "job_abc123", "type": "animation", "collection_id": "col_xyz", "asset_ids": { "image": "ast_img_001", "transparent_image": "ast_timg_001", "video": "ast_vid_001", "webm": "ast_webm_001", "hevc": "ast_hevc_001" }, "urls": { "image": "https://assets.masko.ai/col_xyz/image.png", "transparent_image": "https://assets.masko.ai/col_xyz/transparent.png", "video": "https://assets.masko.ai/col_xyz/video.mp4", "webm": "https://assets.masko.ai/col_xyz/video.webm", "hevc": "https://assets.masko.ai/col_xyz/video_hevc.mp4" }, "timestamp": "2026-03-28T10:01:32Z" } ``` ```json { "event": "job.failed", "job_id": "job_abc123", "type": "animation", "collection_id": "col_xyz", "error": { "code": "generation_failed", "message": "Video generation timed out after 5 minutes" }, "timestamp": "2026-03-28T10:05:00Z" } ``` ## Verify Signatures Every webhook request includes a `X-Masko-Signature` header containing `sha256=` followed by an HMAC-SHA256 signature of the raw request body. Verify it using the secret from when you created the webhook. ```javascript import crypto from 'crypto'; function verifyWebhook(body, signature, secret) { const expected = crypto .createHmac('sha256', secret) .update(body, 'utf-8') .digest('hex'); const actual = signature?.startsWith('sha256=') ? signature.slice('sha256='.length) : signature; return crypto.timingSafeEqual( Buffer.from(actual), Buffer.from(expected) ); } // In your webhook handler: app.post('/api/masko-webhook', (req, res) => { const signature = req.headers['x-masko-signature']; const isValid = verifyWebhook( JSON.stringify(req.body), signature, process.env.MASKO_WEBHOOK_SECRET ); if (!isValid) { return res.status(401).send('Invalid signature'); } const { event, job_id, urls } = req.body; console.log(`Job ${job_id} ${event}:`, urls); res.status(200).send('OK'); }); ``` ```python import hmac import hashlib def verify_webhook(body: bytes, signature: str, secret: str) -> bool: expected = hmac.new( secret.encode('utf-8'), body, hashlib.sha256, ).hexdigest() actual = signature.removeprefix('sha256=') return hmac.compare_digest(actual, expected) # In your webhook handler (Flask example): @app.route('/api/masko-webhook', methods=['POST']) def handle_webhook(): signature = request.headers.get('X-Masko-Signature', '') is_valid = verify_webhook( request.get_data(), signature, os.environ['MASKO_WEBHOOK_SECRET'], ) if not is_valid: return 'Invalid signature', 401 data = request.get_json() print(f"Job {data['job_id']} {data['event']}") return 'OK', 200 ``` ## Retry Policy If your endpoint returns a non-2xx status code or the request times out, Masko retries with exponential backoff: - **Retry 1:** after 1 second - **Retry 2:** after 5 seconds - **Retry 3:** after 30 seconds After 3 failed attempts, the delivery is marked as failed. If a webhook accumulates 100 consecutive delivery failures, it is automatically disabled. You can re-enable it by deleting and recreating it. ## Manage Webhooks List all your registered 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 a webhook when you no longer need it: 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--- # List & Browse Browse your projects, collections, and assets. Use these endpoints to find resource IDs, check generation status, and retrieve CDN URLs. ## List Projects Projects are the top-level container. Each project can hold multiple collections. GET /v1/projects ```bash curl https://api.masko.ai/v1/projects \ -H "Authorization: Bearer masko_YOUR_API_KEY" # 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 Collections List all collections, optionally filtered by project. Each collection represents one mascot character. GET /v1/collections ```bash # All collections curl https://api.masko.ai/v1/collections \ -H "Authorization: Bearer masko_YOUR_API_KEY" # Filter by project curl "https://api.masko.ai/v1/collections?project_id=proj_abc123" \ -H "Authorization: Bearer masko_YOUR_API_KEY" # 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: 'proj_abc123', }); const res = await fetch( `https://api.masko.ai/v1/collections?${params}`, { headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY' }, } ); const { data: collections } = await res.json(); ``` ## Collection Detail Get full details of a single collection, including its configuration with style card and reference settings. GET /v1/collections/:id ```bash curl https://api.masko.ai/v1/collections/col_xyz789 \ -H "Authorization: Bearer masko_YOUR_API_KEY" # 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": [] # }, # "cdn_status": [], # "created_at": "2026-02-01T10:00:00Z", # "updated_at": "2026-03-15T16:20:00Z" # } # } ``` ## List Items Items are individual poses or assets within a collection. Each item can have multiple asset types (image, animation, logo). GET /v1/collections/:id/items ```bash curl https://api.masko.ai/v1/collections/col_xyz789/items \ -H "Authorization: Bearer masko_YOUR_API_KEY" # 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/collections/${collectionId}/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 List all assets in a collection with their current status. Useful for checking which generations are still in progress. GET /v1/collections/:id/assets ```bash curl https://api.masko.ai/v1/collections/col_xyz789/assets \ -H "Authorization: Bearer masko_YOUR_API_KEY" # 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", # "item_id": "b646b856-5832-49f4-90bf-9c41a50515b3", # "collection_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` when a CDN URL exists. Assets without CDN publishing still include a signed `file_url` when available. For metadata-only polling or inventory views, add `?include_file_urls=false` to skip signed `file_url` generation while keeping `cdn_url`. ## CDN Export JSON Use the CDN export endpoint when you want the same hosted-link JSON shown in the collection page's **Get Links → Export JSON** panel. This endpoint is built from published `cdn_assets`, so it includes pose items that have video loops attached even when the item itself is not typed as an animation. GET /v1/collections/:id/cdn-export ```bash curl https://api.masko.ai/v1/collections/col_xyz789/cdn-export \ -H "Authorization: Bearer masko_YOUR_API_KEY" # Response: # { # "collection": "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" # } # ] # } # ] # } ``` Use `/v1/collections/:id/items` and `/v1/collections/:id/assets` for raw metadata, item IDs, asset IDs, prompts, and status checks. Use `/v1/collections/:id/cdn-export` for the clean copy/paste export format. If asset hosting is disabled, the collection is unpublished, or no CDN assets are available yet, the endpoint returns `409 cdn_export_not_ready` instead of an empty export. Enable asset hosting and publish/sync the collection before retrying. ## Items, Assets, and Deletion Individual items can be renamed or deleted via: - `PATCH /v1/collections/:id/items/:itemId` - body `{ name?, prompt? }` - `DELETE /v1/collections/:id/items/:itemId` Assets are also exposed as a first-class resource: - `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) --- ---url: /docs/manage/references--- # Reference images and style consistency A mascot is only useful when it looks like the same character in every pose. The Masko API keeps your character on-model by passing reference images as visual context on every generation and by extracting a style card that captures the defining traits of the design. There are three different API fields that accept an image, and they are not interchangeable. This page explains what each one does and when to use it. ## The three reference concepts | Where | Persistence | Use case | | --- | --- | --- | | `POST /v1/collections/:id/references` (body: `asset_id` or `url`) | Persistent | Style anchors for every generation in this collection. Max 6. | | `CreateCollectionBody.reference_image_urls` / `reference_asset_ids` | Persistent (seeded at creation) | Same as above, set when creating the collection. | | `PreviewBody.reference_image_urls` on `/v1/generate/preview` | Ephemeral (one-off) | Style hints for a single preview, not stored. | | `GenerateBody.source_image_asset_id` on `/v1/collections/:id/generate` | N/A | The image to animate or edit. NOT a style reference. | The first two rows are the same concept (persistent collection references) set via different endpoints. The third row is a per-request hint. The fourth row is something completely different: the source image you are transforming. ## Removing a reference Remove a persistent reference by passing its asset ID in the path: DELETE /v1/collections/:id/references/:assetId ```bash curl -X DELETE https://api.masko.ai/v1/collections/COLLECTION_ID/references/ASSET_ID \ -H "Authorization: Bearer masko_YOUR_API_KEY" ``` Removing a reference clears the cached style card; it will be re-extracted on the next generation. ## How the style card works On the first generation in a collection, Masko analyses your reference images and extracts a **style card** - a short description that captures the character's color palette, proportions, outline style, and any other defining traits. The style card is cached on the collection config and injected into every subsequent prompt alongside the raw reference images. The cached style card is cleared whenever you add or remove a reference. The next generation will re-extract it, so your character stays in sync with your current reference set. You never call the style card extraction yourself - it runs lazily on generation. ## Common mistakes - **Using a preview URL as `source_image_asset_id`.** Previews returned by `/v1/generate/preview` are not persisted assets. They have no asset ID and cannot be used as a source. If you want to animate a preview, generate it as a real item first with `POST /v1/collections/:id/generate` and then use the returned `asset_ids.image`. - **Passing a `transparent_image` asset ID as `source_image_asset_id`.** The source must be the full image (type `image`), not the background-removed variant. Use the `asset_ids.image` field from the generate response, not `asset_ids.transparent_image`. - **Confusing `source_image_asset_id` with collection references.** `source_image_asset_id` is the specific pose you want to animate or edit. Collection references are the style anchors that define what the character looks like. They are independent - the source tells the model "animate this exact pose" and the references tell the model "this is what the character looks like". ## See also - [Images and poses](/docs/generate/images) for generating static images - [Animations](/docs/generate/animations) for animating a pose --- ---url: /docs/reference/errors--- # Error Codes %% animation https://assets.masko.ai/7fced6/spark-4735/confused-scratching-head-b90fd4d3-360.webm https://assets.masko.ai/7fced6/spark-4735/confused-scratching-head-4c52ba61-360.mov %% All API errors follow the same format. Here is how to handle each one. ## Response Envelope Every v1 response is wrapped in a consistent envelope. On success, the body is `{ "data": , "meta"?: { ... } }`. On error, the body is `{ "error": { "code": "...", "message": "...", "details"?: { ... } } }`. Paginated lists include `meta.pagination = { total, limit, offset, has_more }`. Generation endpoints return job details with `data.poll_url`. ## Error Response Format Every error response contains an `error` object with a machine-readable `code`, a human-readable `message`, and an optional `details` object for extra context: ```json { "error": { "code": "insufficient_credits", "message": "Not enough credits. Required: 20, balance: 5.", "details": { "required": 20, "balance": 5 } } } ``` ## Error Reference | HTTP | Error Code | Meaning | What To Do | |------|-----------|---------|------------| | 400 | `validation_failed` | Invalid request body or missing required fields. | Check the `message` for which field failed validation. Fix the request and retry. | | 401 | `unauthorized` | Missing or invalid API key. | Verify your `Authorization: Bearer masko_...` header is correct and the key has not been revoked. | | 402 | `insufficient_credits` | Not enough credits for this operation. Response includes `required` and `balance`. | Top up credits at [app.masko.ai/billing](https://app.masko.ai/billing) or reduce the request scope. | | 403 | `forbidden` | You do not have access to this resource. | Verify the resource belongs to your account. Check that your API key has the necessary permissions. | | 404 | `not_found` | The requested resource does not exist. | Check the resource ID in the URL. Use the list endpoints to find valid IDs. | | 429 | `rate_limited` | Too many requests. | Back off before retrying. | | 500 | `internal` | Something went wrong on our side. | Retry after a short delay. If persistent, contact support at [paul@masko.ai](mailto:paul@masko.ai). | ## Handling Insufficient Credits The 402 response includes `required` and `balance` fields under `error.details` so you can show users exactly how many credits they need: ```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/collections/COLLECTION_ID/generate', { method: 'POST', headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ type: 'animation', name: 'Dancing', image_prompt: 'dancing pose', animation_prompt: 'dancing', duration: 4, }), } ); 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 app.masko.ai/billing`); return; } ``` ## Rate Limit Recovery When you hit a 429, use exponential backoff for robustness. If a `Retry-After` header is present, honor it. ```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 delay = retryAfter ? parseInt(retryAfter, 10) * 1000 : 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/collections/COLLECTION_ID/generate', { method: 'POST', headers: { 'Authorization': 'Bearer masko_YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ type: 'image', name: 'Hello', image_prompt: 'waving hello', }), } ); ``` Parse the `error.code` field programmatically to decide how to handle each error. Use `error.message` for logging and debugging - it may change between versions, but the code will stay stable. --- ---url: /docs/reference/changelog--- # API Changelog Every update to the Masko v1 API is listed here with the release date. 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. --- ## 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 [`/api/v1/openapi.json`](/api/v1/openapi.json); its `info.version` matches the latest entry above. ---