Reference
Every error message is written for people: it says what went wrong and what to do next, and never contains secrets. You can show it to your users as-is. Use the status codes and error codes below to decide what your code does.
POST /video/request returns a 4xx status. No job is created and nothing counts toward your limit. Fix the request and send it again.GET /video/status returns HTTP 200 with status: "failed", a readable error and a machine-readable error_code.Refused requests return JSON with a detail message:
{
"detail": "Claude Opus 5.5 needs the Pro plan. Upgrade at manimotion.dev/pricing, or use your own Anthropic or OpenRouter key on any plan."
}| Status | Meaning | Common causes |
|---|---|---|
400 | Bad request | No storage configured; invalid bucket credentials; unknown model id; the key in llm was rejected or can’t run the model |
401 | Not authenticated | Missing, mistyped or revoked x-api-key |
403 | Not allowed | The model isn’t in your plan, or only runs on your own key. See Bring your own key |
404 | Not found | Unknown job id, or a job that belongs to another account |
422 | Invalid field | Prompt shorter than 10 or longer than 2000 characters; duration outside 15–180; unknown llm.provider. detail is a list naming each bad field. |
429 | Too many requests | Your plan’s video limit is used up (Free: 3 per day, Hobby: 40 per month, Pro: 80 per month), or more than 20 requests in 10 minutes from one IP address |
5xx | Our problem | Temporary. Retry with a backoff |
When status is failed, error_code is one of these:
| error_code | What happened | What to do |
|---|---|---|
llm_auth | Your AI provider rejected your key | Replace the key in Settings → AI providers (or in llm.api_key) |
llm_quota | Your AI provider account is out of credits or over its limit | Add credits with the provider, then retry |
llm_rate_limit | Your AI provider is rate-limiting your key | Wait a minute, then retry |
llm_model | Your AI provider couldn’t run that model id | Check the id, or choose another model |
llm_bad_request | Your AI provider refused the request (the message says why) | Usually a provider-side policy or setting; try another model |
llm_unavailable | The AI service is down or overloaded | Retry in a few minutes |
storage_failed | Uploading to your bucket failed | Check the bucket in Settings → Storage, then retry |
generation_failed | The lecture couldn’t be made | Retry, rephrase the prompt more specifically, try another style or a stronger model |
The llm_auth, llm_quota, llm_rate_limit, llm_model and llm_bad_request codes only happen on your own key. On our key, any AI problem shows as llm_unavailable.
{
"job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "failed",
"video_url": null,
"error": "Anthropic rejected the API key. Check or replace your saved key in Settings → AI providers.",
"error_code": "llm_auth",
"model": "anthropic/claude-sonnet-5.5",
"key_source": "byok",
"cached": false,
"style": null,
"duration": null,
"phase": "failed",
"message": "Something went wrong"
}A typical pattern with the SDKs: fall back to the default model when a model is locked, alert yourself when a key needs attention, and show everything else to the user.
import {
BadRequestError,
PermissionDeniedError,
RateLimitError,
VideoFailedError,
} from "@manimotion/sdk";
async function makeVideo(prompt, model) {
try {
const video = await manimotion.videos.generate({ prompt, model });
return video.videoUrl;
} catch (err) {
if (err instanceof PermissionDeniedError) {
// Model not in the plan: fall back to the default model
return makeVideo(prompt);
}
if (err instanceof VideoFailedError) {
if (err.code === "llm_auth" || err.code === "llm_quota") {
notifyOwner(err.message); // the AI key needs attention
} else if (err.code === "llm_rate_limit" || err.code === "llm_unavailable") {
retryLater(prompt);
} else {
showUser(err.message); // safe to show as-is
}
return null;
}
if (err instanceof BadRequestError || err instanceof RateLimitError) {
showUser(err.message);
return null;
}
throw err;
}
}POST /video/request only when you got a clear error response.429, wait before retrying. Plan limits reset at midnight UTC (daily) or on the 1st of the month (monthly).The SDKs turn these into typed errors. All of them extend ManimotionError; HTTP errors also carry the status code (status in JavaScript, status_code in Python) and the API’s message.
| Class | When |
|---|---|
BadRequestError | 400 |
AuthenticationError | 401 |
PermissionDeniedError | 403 |
NotFoundError | 404 |
ValidationError | 422 |
RateLimitError | 429 |
InternalServerError | 5xx |
APIConnectionError | Couldn’t reach the API |
TimeoutError | Waiting took too long (APITimeoutError in Python) |
VideoFailedError | The job failed; code holds the error_code |