Get started

SDKs

Official libraries for JavaScript/TypeScript and Python. They handle auth, polling, retries and errors, so a video is one function call.

Install

JavaScript / TypeScript
npm install @manimotion/sdk
Python
pip install manimotion

Create a key in Settings → API keys and put it in your environment. Both SDKs read it automatically.

.env
MANIMOTION_API_KEY=chalk_live_sk_v1_...

Your first video

generate() starts the job and waits until the MP4 is ready. That’s usually 1–5 minutes.

generate.mjs
import { Manimotion } from "@manimotion/sdk";

const manimotion = new Manimotion(); // reads MANIMOTION_API_KEY

const video = await manimotion.videos.generate({
  prompt: "Explain how binary search works",
  onProgress: (v) => console.log(v.status, v.message),
});

console.log(video.videoUrl);
RESPONSEstdout
https://cdn.example.com/videos/….mp4

Storage

Videos upload to your bucket. If you saved one in Settings → Storage, it’s used automatically and you can skip this. Otherwise pick a saved bucket or pass credentials per request. See Storage for S3, MinIO and Backblaze.

storage.mjs
// Saved bucket (Settings → Storage → Copy id)
await manimotion.videos.generate({
  prompt: "Intro to photosynthesis",
  storage: { integrationId: "your-integration-id" },
});

// Or credentials for this request only
await manimotion.videos.generate({
  prompt: "Intro to photosynthesis",
  storage: {
    inline: {
      provider: "r2",
      bucket: "my-videos",
      accountId: process.env.R2_ACCOUNT_ID,
      accessKeyId: process.env.R2_ACCESS_KEY_ID,
      secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
      publicUrl: "https://pub-xxxx.r2.dev",
    },
  },
});

Models and your own key

Pass model to choose who writes the lesson, and llm to run a video on your own OpenRouter, Anthropic, OpenAI or Gemini key. If you saved a key in Settings → BYOK setup, it’s used automatically. See Models and Bring your own key.

models.mjs
// Which models can I use?
const { data: models } = await manimotion.models.list();
for (const m of models) {
  console.log(m.id, m.available ? `via ${m.via}` : "locked");
}

// Pick one
await manimotion.videos.generate({
  prompt: "Prove there are infinitely many primes",
  model: "anthropic/claude-sonnet-5.5",
});

// Run this video on your own AI key (not stored)
await manimotion.videos.generate({
  prompt: "Explain eigenvectors visually",
  model: "anthropic/claude-opus-5.5",
  llm: { provider: "anthropic", apiKey: process.env.ANTHROPIC_API_KEY },
});

Every video reports model and keySource (key_source in Python): "byok" when your key ran it, "platform" when ours did.

Create now, check later

In web requests and serverless functions, don’t block for minutes. Start the job, store its id, and check on it later.

jobs.mjs
// 1. Start — returns right away
const job = await manimotion.videos.create({ prompt: "The water cycle" });
console.log(job.id, job.etaDisplay);

// 2. Check later (cron, webhook, page refresh)
const video = await manimotion.videos.retrieve(job.id);
if (video.status === "completed") console.log(video.videoUrl);

// …or block until done
const done = await manimotion.videos.wait(job.id, { timeoutMs: 10 * 60_000 });

Statuses go queued → planning → generating_audio → generating_code → merging → uploading → completed (or failed). Show message to users; it’s written for them.

Errors

Every SDK error extends ManimotionError. API errors carry the HTTP status.

errors.mjs
import {
  AuthenticationError,
  RateLimitError,
  VideoFailedError,
} from "@manimotion/sdk";

try {
  await manimotion.videos.generate({ prompt: "Explain TCP handshakes" });
} catch (err) {
  if (err instanceof AuthenticationError) console.error("Check your API key");
  else if (err instanceof RateLimitError) console.error(err.message);
  else if (err instanceof VideoFailedError) console.error(err.code, err.message);
  else throw err;
}
ErrorWhen
BadRequestError400 — e.g. no storage, unknown model, AI key rejected
AuthenticationError401 — missing or revoked key
PermissionDeniedError403 — model not in your plan (use your own key)
NotFoundError404 — unknown job id
ValidationError422 — invalid parameter
RateLimitError429 — too many requests or quota used up
VideoFailedErrorThe job failed; code says why (codes)
TimeoutErrorWaited too long (APITimeoutError in Python)

Status checks retry automatically on network errors and 5xx. create() never retries on its own, so you’ll never get a duplicate video or lose quota by accident.

Options

OptionValuesNotes
prompt10–2000 charsRequired
styleauto · math · graphicsDefault auto
duration15–180 sSoft target; omit for automatic
modele.g. anthropic/claude-sonnet-5.5Who writes the lesson. All models
llm{ provider, apiKey }Your own AI key for this video (api_key in Python)
tiertier1 · tier2 · tier3Fastest → most detailed
storageinline or saved idOptional with a saved bucket

Python has an async client with the same methods:

async.py
import asyncio
from manimotion import AsyncManimotion

async def main():
    async with AsyncManimotion() as client:
        video = await client.videos.generate("Explain recursion")
        print(video.video_url)

asyncio.run(main())

Reference

MethodDoes
videos.create()Start a video, return immediately
videos.retrieve(id)Latest status
videos.wait(id)Poll until completed or failed
videos.generate()create + wait
models.list()Supported models, and which you can use
health()API status (no key needed)