Home › Guides › Errors and retries

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:

What to retry

Status / signalMeaningRetry?
400Bad request: unknown model id, missing prompt, or a field the model or host does not supportNo. Fix the request
401Missing, malformed, revoked or expired keyNo. Fix the credential
402spend_cap_exceeded (key's monthly cap) or insufficient_credits (balance)No. Top up or raise the cap; retrying cannot help
403Key scope or model allow-list does not permit thisNo. Change the key or the model
429Your key's rate limitYes, after the Retry-After seconds
500, 502, 503, 504upstream_error: every candidate host failedYes, with backoff and a cap on attempts; this was not billed
Network timeout on createYou do not know whether the job was createdCareful: see below
Job failedUpstream rejected or could not finish itDepends 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:

  1. Use a generous create timeout (30 seconds is plenty; creation returns quickly because the work is asynchronous), so true ambiguity is rare.
  2. 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

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

Using MiniMax is one part of the job.

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 →