Skip to content
maskostudioDocs

All pages

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

Errors and recovery

Every error returns an error object with a stable code. Find the code below to see how to fix the request or retry safely.

Error Response Format

  • code: machine-readable and stable. Switch on this.
  • message: human-readable. It may change between versions, so use it for logs only.
  • param: the request field that caused the error, when there is one.
  • details: extra context, such as issues for validation or required and balance for credits.
  • doc_url: this page, at the section for the code.
  • request_id: the same value as the Request-Id header. Include it when you contact support.
{
  "error": {
    "code": "validation_failed",
    "message": "duration: Number must be less than or equal to 15",
    "param": "duration",
    "details": {
      "issues": [{ "param": "duration", "message": "Number must be less than or equal to 15" }]
    },
    "doc_url": "https://masko.ai/docs/reference/errors#validation-failed",
    "request_id": "req_3f9c2a71d0b84e6a9c1f5e22"
  }
}

Successful responses use { "data": ..., "meta"?: ... } instead; see requests and responses.

Error Codes

Some operations return their own codes: a 409 for an operation-specific conflict, and a 422 when a proposed graph is invalid. Correct the graph rather than retrying generation blindly.

Validation failed

validation_failed (400). The body or query is invalid. Fix the fields named in param and details.issues, then resend.

Unauthorized

unauthorized (401). The API key is missing, invalid or revoked. Send Authorization: Bearer masko_... with an active key.

Insufficient credits

insufficient_credits (402). details.required and details.balance give the numbers. Top up at masko.ai/billing or reduce the request.

Forbidden

forbidden (403). The key lacks permission, for example a read-only key sending a POST. Use a key with the right permission.

Not found

not_found (404). The resource does not exist or is outside the credential's workspace. Check the ID with a list endpoint.

Conflict

conflict (409). A revision, readiness or active-run conflict. Read the resource and details again, then retry with fresh values. With details.reason: "voice_required", the mascot needs a voice for a talking animation: set one with PUT /v1/mascots/{id}/voice.

Idempotency key reused

idempotency_key_reused (409). This account already used the key with a different workspace, API version, URL, content type or body. Restore the original inputs to retry, and use a new key only for an intentionally new operation.

Idempotency request in progress

idempotency_request_in_progress (409). A request with this key is running or its outcome could not be confirmed. Wait a few seconds and retry with the same key, since a new key could duplicate a committed write. Unconfirmed operations stay blocked after 24 hours; check the saved job or contact support.

Idempotency persistence failed

idempotency_persistence_failed (503). The operation ran and may have committed, but its replay receipt could not be saved. Retry only with the same key, and inspect the saved job or contact support before starting a new operation.

Invalid idempotency key

invalid_idempotency_key (400). The Idempotency-Key header must be 1 to 255 characters.

Rate limited

rate_limited (429). Too many requests. Back off before retrying, as shown in rate limit recovery.

Upstream error

upstream_error (502, 503, 504), or suggestion_failed (502). A provider Masko depends on failed. Accepted jobs keep running: recover the job, or retry later through a supported idempotent replay.

Internal

internal (500). Something failed on our side, and a write may have committed. Retry with the original idempotency key (a stored error replays unchanged) and check the outcome before starting a new request. Contact support with request_id if it persists.

Handling Insufficient Credits

Show the user how many credits are missing:

// HTTP 402
{
  "error": {
    "code": "insufficient_credits",
    "message": "Not enough credits. Required: 140, balance: 50.",
    "details": {
      "required": 140,
      "balance": 50
    }
  }
}

Rate Limit Recovery

Back off exponentially, and honor the Retry-After header when present.

async function fetchWithRetry(url, options, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const res = await fetch(url, options);

    if (res.status !== 429) {
      return res;
    }

    if (attempt === maxRetries) {
      throw new Error('Rate limited after max retries');
    }

    // Use Retry-After header, or exponential backoff
    const retryAfter = res.headers.get('Retry-After');
    const seconds = retryAfter === null ? NaN : Number(retryAfter);
    const dateDelay = retryAfter ? Date.parse(retryAfter) - Date.now() : NaN;
    const delay = Number.isFinite(seconds) ? Math.max(0, seconds * 1000)
      : Number.isFinite(dateDelay) ? Math.max(0, dateDelay)
      : Math.pow(2, attempt) * 1000;

    console.log(`Rate limited. Retrying in ${delay / 1000}s...`);
    await new Promise((r) => setTimeout(r, delay));
  }
}

// Usage:
const res = await fetchWithRetry(
  'https://api.masko.ai/v1/mascots/MASCOT_ID/generate',
  {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer masko_YOUR_API_KEY',
      'Masko-API-Version': '2026-09-26',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      type: 'image',
      name: 'Hello',
      image_prompt: 'waving hello',
    }),
  }
);

A rejected 429 is safe to retry. A timed-out paid write is not: check the saved job or run first, and replay only through endpoints that support Idempotency-Key.