Get started
Official libraries for JavaScript/TypeScript and Python. They handle auth, polling, retries and errors, so a video is one function call.
npm install @manimotion/sdkpip install manimotionCreate a key in Settings → API keys and put it in your environment. Both SDKs read it automatically.
MANIMOTION_API_KEY=chalk_live_sk_v1_...generate() starts the job and waits until the MP4 is ready. That’s usually 1–5 minutes.
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);https://cdn.example.com/videos/….mp4Videos 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.
// 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",
},
},
});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.
// 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.
In web requests and serverless functions, don’t block for minutes. Start the job, store its id, and check on it later.
// 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.
Every SDK error extends ManimotionError. API errors carry the HTTP status.
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;
}| Error | When |
|---|---|
BadRequestError | 400 — e.g. no storage, unknown model, AI key rejected |
AuthenticationError | 401 — missing or revoked key |
PermissionDeniedError | 403 — model not in your plan (use your own key) |
NotFoundError | 404 — unknown job id |
ValidationError | 422 — invalid parameter |
RateLimitError | 429 — too many requests or quota used up |
VideoFailedError | The job failed; code says why (codes) |
TimeoutError | Waited 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.
| Option | Values | Notes |
|---|---|---|
prompt | 10–2000 chars | Required |
style | auto · math · graphics | Default auto |
duration | 15–180 s | Soft target; omit for automatic |
model | e.g. anthropic/claude-sonnet-5.5 | Who writes the lesson. All models |
llm | { provider, apiKey } | Your own AI key for this video (api_key in Python) |
tier | tier1 · tier2 · tier3 | Fastest → most detailed |
storage | inline or saved id | Optional with a saved bucket |
Python has an async client with the same methods:
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())| Method | Does |
|---|---|
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) |