Handling errors and retries when you call MiniMax video models
Updated 2026-10-02
A video job has more ways to fail than a chat completion: the create call can be rejected, the job can fail after it is accepted, the poll can time out, and the network can drop between you and the response. Handling each correctly matters because a naive retry can create a second paid job. This page gives a classification of what you will see from the MiniMax H3 family (minimax/h3, minimax/h3-max, minimax/h3-max-turbo) and a Python wrapper you can drop into a project. The failover guide covers host-level behaviour; this one is about your client code.
Two kinds of failure
Separate them in your head, because they are handled differently:
- Create-time errors.
POST /videosreturns a non-2xx status with an OpenAI-style body:{"error": {"message", "type", "code"}}. No job exists. - Job-time failures. The create call succeeded and returned a job id, but polling later shows
status: "failed"with an error. Billing happens once at creation from the requested duration, and jobs that fail upstream are not billed.
What to retry
| Status / signal | Meaning | Retry? |
|---|---|---|
| 400 | Bad request: unknown model id, missing prompt, or a field the model or host does not support | No. Fix the request |
| 401 | Missing, malformed, revoked or expired key | No. Fix the credential |
| 402 | spend_cap_exceeded (key's monthly cap) or insufficient_credits (balance) | No. Top up or raise the cap; retrying cannot help |
| 403 | Key scope or model allow-list does not permit this | No. Change the key or the model |
| 429 | Your key's rate limit | Yes, after the Retry-After seconds |
| 500, 502, 503, 504 | upstream_error: every candidate host failed | Yes, with backoff and a cap on attempts; this was not billed |
| Network timeout on create | You do not know whether the job was created | Careful: see below |
Job failed | Upstream rejected or could not finish it | Depends on the error text; not billed |
The ambiguous case: timeout on create
If your HTTP call times out, the job may or may not exist. Blindly re-posting can create a duplicate that is billed twice. Two defences:
- Use a generous create timeout (30 seconds is plenty; creation returns quickly because the work is asynchronous), so true ambiguity is rare.
- Record intent before the call and the job id after it. If you time out with no id, check your balance and recent jobs before retrying, or accept a rare duplicate for low-value work and cap the per-user retry count.
Do not retry because a job is slow. Polling is free; a second submission is a second charge.
A retry wrapper
import os, random, time, requests
API = "https://videorouter.sh/api/v1"
H = {"Authorization": f"Bearer {os.environ['VIDEOROUTER_KEY']}",
"Content-Type": "application/json"}
class ApiError(Exception):
def __init__(self, status, etype, code, message, retry_after=None):
super().__init__(f"{status} {etype}/{code}: {message}")
self.status, self.etype, self.code = status, etype, code
self.retry_after = retry_after
def _raise(r):
try:
e = r.json().get("error", {})
except ValueError:
e = {}
ra = r.headers.get("Retry-After")
raise ApiError(r.status_code, e.get("type"), e.get("code"),
e.get("message", r.text[:200]),
float(ra) if ra else None)
RETRYABLE = {429, 500, 502, 503, 504}
def create(prompt, model="minimax/h3", attempts=4, **params):
body = {"model": model, "prompt": prompt, **params}
for i in range(attempts):
r = requests.post(f"{API}/videos", headers=H, json=body, timeout=30)
if r.ok:
return r.json()
if r.status_code not in RETRYABLE or i == attempts - 1:
_raise(r)
wait = float(r.headers.get("Retry-After") or 0)
time.sleep(max(wait, min(60, 2 ** i)) + random.random())
raise RuntimeError("unreachable")
This retries only statuses that can plausibly clear up, honours Retry-After exactly when the server sends one, and adds jitter so many workers do not retry in lockstep. Notice it does not catch requests.Timeout: that is the ambiguous case above and deserves an explicit decision, not an automatic loop.
Polling and job-time failures
def wait(job, every=5, budget=1200):
end = time.time() + budget
while job["status"] not in ("completed", "failed"):
if time.time() > end:
raise TimeoutError(f"{job['id']} still {job['status']}")
time.sleep(every)
r = requests.get(f"{API}/videos/{job['id']}", headers=H, timeout=30)
if r.status_code == 429:
time.sleep(float(r.headers.get("Retry-After") or every)); continue
if not r.ok:
_raise(r)
job = r.json()
return job
def generate(prompt, **params):
job = wait(create(prompt, **params))
if job["status"] == "failed":
raise RuntimeError(f"job {job['id']} failed: {job.get('error')}")
return job["data"][0]["url"]
A transient network error while polling is safe to retry, since a GET changes nothing. A failed job, by contrast, is a decision point: log the error text and the job id, then retry once at most. If it fails again with the same text, the cause is probably the prompt or the input image, and a third attempt would only waste time.
Log what you need to debug
For every request, store the model id, the parameters, the job id, final status, and the error object for failures. When something goes wrong a week later, the job id is the thread to pull. Avoid logging the key or full prompts if they contain user data.
When retries are the wrong tool
- Repeated upstream errors. If an unpinned call keeps failing after the router has already tried alternative hosts, a further client retry will likely hit the same condition. Back off, then fall back to a different model in your own code.
- A pinned host. A suffix like
minimax/h3/<host>is a soft preference that can still fall back; a hard pin needsprovider.onlywithallow_fallbacks: false, and then you are responsible for the host being down. - Silent parameter fallback. An unsupported resolution or ratio is ignored and returns 200, so no retry logic will notice. Verify the output file's dimensions.
Read the call guide for the basic flow, check the pricing page for rates, and create a key to test the error paths on a small balance.
Frequently asked questions
Which MiniMax API errors should I retry?
Retry 429 after the Retry-After delay and 5xx upstream errors with capped exponential backoff and jitter. Do not retry 400, 401, 402 or 403, because the same request will fail again.
Will a retry bill me twice?
A create call that returned an error did not create a job. The risk is a timeout with no response, where a job may exist. Use a sensible create timeout and log job ids so you can check before resubmitting.
Are failed jobs billed?
Per VideoRouter's documentation, jobs that fail upstream are not billed. Billing otherwise happens once at creation from the requested duration, and polling is free.
What does a 402 mean?
Either the key's monthly spend cap has been reached or the prepaid balance is empty. The code field tells you which, and retrying will not help until you change the cap or top up.
Keep reading
- MiniMax H3 vs H3 Max vs H3 Max Turbo — Which Variant for Which Job
- MiniMax Video API Use Cases: What to Build with the H3 Family
- MiniMax vs Kling vs Seedance API: A Decision Framework
- MiniMax API Failover and Reliability: Pinning, Timeouts, Retries
VideoRouter puts it next to dozens of other video and image models behind one API key, so you can compare providers, prices and fail over automatically. Compare providers on VideoRouter →