Developer guidesMiniMax H3 video generation
Browse this guide
Asynchronous Videos API

Generate MiniMax H3 video through Moca Router

Submit a text-to-video job to POST /api/proxy/v1/videos, recover pending jobs from the authenticated list after a refresh, poll their gateway job IDs, then stream finished videos through the content endpoint.

Authentication

One Moca key

Use the team-issued Moca Router key. The platform-managed MiniMax credential is never exposed.

Lifecycle

Create, then poll

Creation returns 202 Accepted with a stable vid_... job ID; generation continues asynchronously.

Metering

Actual output seconds

Spend is reserved before dispatch and settled from the provider-reported output duration after success.

Make your first request

Create a key in the dashboard and confirm that MiniMax-H3 is enabled for that key's team. Use the exact, case-sensitive model ID shown here.

Terminal
export MOCA_ROUTER_API_KEY="sk-mocarouter-..."

# Create a video job. Save the returned id (vid_...).
curl https://staging.mocarouter.com/api/proxy/v1/videos \
  -H "Authorization: Bearer $MOCA_ROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: video-$(uuidgen)" \
  -d '{
    "model": "MiniMax-H3",
    "prompt": "A cinematic tracking shot through a quiet Tokyo alley at night",
    "duration": 6,
    "resolution": "768P",
    "ratio": "16:9"
  }'

# Poll with the same Moca Router API key.
curl https://staging.mocarouter.com/api/proxy/v1/videos/vid_REPLACE_ME \
  -H "Authorization: Bearer $MOCA_ROUTER_API_KEY"

# Stream the completed video through the gateway.
curl https://staging.mocarouter.com/api/proxy/v1/videos/vid_REPLACE_ME/content \
  -H "Authorization: Bearer $MOCA_ROUTER_API_KEY" \
  --output result.mp4

Text-to-video only

This integration supports MiniMax-H3 with a text prompt. H3-Max, image input, audio input, reference media, uploads, callback URLs, and arbitrary provider fields are not supported.

Two credential layers

A Super Admin configures the upstream MiniMax credential. Teams and users call only Moca Router; the gateway applies team model access, key ownership, rate limits, concurrency limits, quota, and billing.

Job lifecycle

Use the asynchronous workflow

01

Create

POST /api/proxy/v1/videos with an Idempotency-Key. Save the returned job id.

02

Restore

GET /api/proxy/v1/videos?status=active after startup or refresh to recover unfinished jobs.

03

Poll

GET /api/proxy/v1/videos/{id} about every 10 seconds until the job reaches a terminal status.

04

Download

When status is completed, GET the returned content_url with the same Moca Router key.

05

Cancel if queued

DELETE /api/proxy/v1/videos/{id}. Cancellation is accepted only while the job is still queued.

Job ownership is key-scoped

Status, cancellation, and content requests must use the same Moca Router API key that created the job. Another key from the same team cannot access it. Treat the job ID as opaque and do not substitute an upstream provider task ID.

You can inspect the model capability and effective pricing record before creating a job:

Capabilities request
curl --get https://staging.mocarouter.com/api/proxy/v1/model-capabilities \
  -H "Authorization: Bearer $MOCA_ROUTER_API_KEY" \
  --data-urlencode "model=MiniMax-H3"

Refresh-safe recovery

List and resume your video jobs

Call GET /api/proxy/v1/videos with the same API key used to create the jobs. The list is scoped to both the team and that exact key, ordered newest first, and contains only safe job metadata. A browser refresh, client restart, or lost local job ID does not interrupt generation—the gateway worker continues processing it.

List active jobs with curl
# Restore unfinished jobs after an app restart or page refresh.
curl --get https://staging.mocarouter.com/api/proxy/v1/videos \
  -H "Authorization: Bearer $MOCA_ROUTER_API_KEY" \
  --data-urlencode "status=active" \
  --data-urlencode "limit=20"

# Request the next page using next_cursor from the previous response.
curl --get https://staging.mocarouter.com/api/proxy/v1/videos \
  -H "Authorization: Bearer $MOCA_ROUTER_API_KEY" \
  --data-urlencode "status=active" \
  --data-urlencode "limit=20" \
  --data-urlencode "after=eyJ...opaque..."
statusoptional
Optional single filter. Use active for queued, in_progress, and finalizing; use terminal for all finished states; or send one exact public status. Omit it to list all jobs.
limitoptional
Optional page size from 1 to 100. The default is 20.
afteroptional
Optional opaque next_cursor from the previous page. Do not decode, edit, or reuse it with a different key or filter.
Paginated list response
{
  "object": "list",
  "data": [
    {
      "id": "vid_7782d2a77f2c4d3094d2a51d0daf39b7",
      "object": "video",
      "model": "MiniMax-H3",
      "status": "in_progress",
      "cancellable": false,
      "downloadable": false,
      "progress": 42,
      "duration_seconds": 6,
      "resolution": "768P",
      "aspect_ratio": "16:9",
      "created_at": 1789552800
    }
  ],
  "has_more": true,
  "next_cursor": "eyJ...opaque..."
}

Use the list endpoint for discovery and recovery. Poll each visible active job through GET /api/proxy/v1/videos/{id} about every 10 seconds, stop polling once it reaches a terminal state, and add jitter or backoff after 429 or transient 5xx responses. Re-list the first active page when the jobs screen opens or regains focus; paginate only when has_more is true.

Only gateway-owned content paths are exposed

List items use the same allowlisted job DTO as individual status responses. An owned completed job may include the gateway's authenticated content_url and downloadable: true; the list never includes prompts, video bytes, MiniMax task IDs, signed provider URLs, provider credentials, or raw provider responses. Use cancellable and downloadable instead of inferring actions from the public status alone.

JavaScript (Node.js 18+)

list-video-jobs.mjs
const API_KEY = process.env.MOCA_ROUTER_API_KEY;
const VIDEOS_URL = "https://staging.mocarouter.com/api/proxy/v1/videos";
const headers = { Authorization: `Bearer ${API_KEY}` };

async function listVideoJobs({ status = "active", after } = {}) {
  const url = new URL(VIDEOS_URL);
  url.searchParams.set("status", status);
  url.searchParams.set("limit", "20");
  if (after) url.searchParams.set("after", after);

  const response = await fetch(url, { headers });
  const page = await response.json();
  if (!response.ok) {
    throw new Error(page.error?.message ?? `HTTP ${response.status}`);
  }
  return page;
}

// Call this when your server starts or the user refreshes the jobs screen.
const firstPage = await listVideoJobs();
for (const job of firstPage.data) {
  console.log(job.id, job.status);
}

if (firstPage.has_more) {
  const secondPage = await listVideoJobs({ after: firstPage.next_cursor });
  console.log(secondPage.data);
}

Python

list_video_jobs.py
import os
import requests

videos_url = "https://staging.mocarouter.com/api/proxy/v1/videos"
auth = {"Authorization": f"Bearer {os.environ['MOCA_ROUTER_API_KEY']}"}

def list_video_jobs(status="active"):
    params = {"status": status, "limit": 20}
    while True:
        response = requests.get(videos_url, headers=auth, params=params, timeout=30)
        response.raise_for_status()
        page = response.json()
        yield from page["data"]
        if not page["has_more"]:
            break
        params["after"] = page["next_cursor"]

for job in list_video_jobs():
    print(job["id"], job["status"])

Strict JSON contract

All five request fields are required

Unknown fields are rejected. Send Authorization, Content-Type: application/json, and a unique Idempotency-Key header with each intended generation.

modelrequired
Must be exactly MiniMax-H3.
promptrequired
A non-empty UTF-8 text prompt. Do not send secrets or personal data.
durationrequired
An integer from 4 through 15, representing requested seconds.
resolutionrequired
Either 768P or 2K.
ratiorequired
One of 21:9, 16:9, 4:3, 1:1, 3:4, or 9:16.

Idempotency header format

Idempotency-Key is required and must contain 1–256 visible ASCII characters. Generate it server-side and keep it with your local operation record until the creation outcome is known.

Server-side examples

Create, poll, and download

The examples wait ten seconds between polls, stop on every terminal state, and authenticate the content download. Run this flow on your backend so the API key never enters browser or mobile client code.

JavaScript (Node.js 18+)

generate-video.mjs
import { randomUUID } from "node:crypto";
import { createWriteStream } from "node:fs";
import { Readable } from "node:stream";
import { pipeline } from "node:stream/promises";

const API_KEY = process.env.MOCA_ROUTER_API_KEY;
const VIDEOS_URL = "https://staging.mocarouter.com/api/proxy/v1/videos";
const headers = { Authorization: `Bearer ${API_KEY}` };

const createResponse = await fetch(VIDEOS_URL, {
  method: "POST",
  headers: {
    ...headers,
    "Content-Type": "application/json",
    "Idempotency-Key": randomUUID(),
  },
  body: JSON.stringify({
    model: "MiniMax-H3",
    prompt: "A cinematic tracking shot through a quiet Tokyo alley at night",
    duration: 6,
    resolution: "768P",
    ratio: "16:9",
  }),
});

let job = await createResponse.json();
if (!createResponse.ok) {
  throw new Error(job.error?.message ?? `HTTP ${createResponse.status}`);
}

const terminal = new Set([
  "completed", "failed", "cancelled", "needs_review", "expired",
]);
while (!terminal.has(job.status)) {
  await new Promise((resolve) => setTimeout(resolve, 10_000));
  const statusResponse = await fetch(`${VIDEOS_URL}/${job.id}`, { headers });
  job = await statusResponse.json();
  if (!statusResponse.ok) {
    throw new Error(job.error?.message ?? `HTTP ${statusResponse.status}`);
  }
}

if (job.status !== "completed") {
  throw new Error(`Video job ended with status: ${job.status}`);
}

const contentUrl = new URL(job.content_url, new URL(VIDEOS_URL).origin);
const contentResponse = await fetch(contentUrl, { headers });
if (!contentResponse.ok) throw new Error(`Download failed: HTTP ${contentResponse.status}`);
if (!contentResponse.body) throw new Error("Download response has no body");
await pipeline(
  Readable.fromWeb(contentResponse.body),
  createWriteStream("result.mp4"),
);

Python

generate_video.py
import os
import time
import uuid
from urllib.parse import urljoin, urlparse

import requests

videos_url = "https://staging.mocarouter.com/api/proxy/v1/videos"
api_key = os.environ["MOCA_ROUTER_API_KEY"]
auth = {"Authorization": f"Bearer {api_key}"}

response = requests.post(
    videos_url,
    headers={
        **auth,
        "Content-Type": "application/json",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "model": "MiniMax-H3",
        "prompt": "A cinematic tracking shot through a quiet Tokyo alley at night",
        "duration": 6,
        "resolution": "768P",
        "ratio": "16:9",
    },
    timeout=30,
)
response.raise_for_status()
job = response.json()

terminal = {"completed", "failed", "cancelled", "needs_review", "expired"}
while job["status"] not in terminal:
    time.sleep(10)
    response = requests.get(f"{videos_url}/{job['id']}", headers=auth, timeout=30)
    response.raise_for_status()
    job = response.json()

if job["status"] != "completed":
    raise RuntimeError(f"Video job ended with status: {job['status']}")

origin = f"{urlparse(videos_url).scheme}://{urlparse(videos_url).netloc}"
content_url = urljoin(origin, job["content_url"])
with requests.get(content_url, headers=auth, stream=True, timeout=300) as content:
    content.raise_for_status()
    with open("result.mp4", "wb") as output:
        for chunk in content.iter_content(chunk_size=1024 * 1024):
            if chunk:
                output.write(chunk)

Gateway job resource

Read status, then stream content

Creation and status requests return the same safe job shape. They never reveal the MiniMax task ID or signed provider URL.

202 queued response
{
  "id": "vid_7782d2a77f2c4d3094d2a51d0daf39b7",
  "object": "video",
  "model": "MiniMax-H3",
  "status": "queued",
  "cancellable": true,
  "downloadable": false,
  "progress": null,
  "duration_seconds": 6,
  "resolution": "768P",
  "aspect_ratio": "16:9",
  "created_at": 1789552800
}

Possible states are queued, in_progress, finalizing, completed, failed, cancelled, needs_review, and expired. Only the first three are non-terminal. A completed job adds settled usage and a gateway content path:

Completed response
{
  "id": "vid_7782d2a77f2c4d3094d2a51d0daf39b7",
  "object": "video",
  "model": "MiniMax-H3",
  "status": "completed",
  "cancellable": false,
  "downloadable": true,
  "progress": 100,
  "duration_seconds": 6,
  "resolution": "768P",
  "aspect_ratio": "16:9",
  "created_at": 1789552800,
  "usage": { "output_seconds": 6 },
  "content_url": "/api/proxy/v1/videos/vid_7782d2a77f2c4d3094d2a51d0daf39b7/content"
}

Do not construct a provider URL

Use the returned content_url or GET /api/proxy/v1/videos/{id}/content. Send the same Bearer key and stream the response to your client or storage. The gateway does not persist the video bytes.

Retry without creating a duplicate job

A repeated POST with the same API key, idempotency key, and exact request returns the original gateway job. Reusing that key with a different model, prompt, duration, resolution, or ratio returns 409 idempotency_key_conflict.

  • Generate one key for each intended generation and persist it before the first POST.
  • Reuse that key only when retrying the same request after a timeout or dropped connection.
  • Do not retry 4xx validation, access, quota, or idempotency-conflict responses unchanged.
  • Respect Retry-After on 429 responses and add jitter to polling retries.

Uncertain upstream outcomes are not redispatched

If the provider accepted a request but its acknowledgement was lost, the gateway may report needs_review. This prevents an automatic second dispatch and duplicate provider charge.

Precise settlement

Billed by actual output second

Before dispatch, Moca Router checks team model access, API-key controls, account rate and concurrency limits, available quota, and available spend. It reserves against the requested duration and resolution, then settles the job from usage.output_seconds reported by MiniMax. The effective per-second price depends on the selected resolution and is shown in the model catalog and capabilities response.

On success

Settle reported output seconds

The completed response includes the authoritative billable quantity in usage.output_seconds.

On uncertainty

Hold for reconciliation

A needs_review result keeps billing conservative until the provider outcome can be reconciled.

Handle stable gateway errors

Error responses use a safe envelope with message, type, param, and code. Provider response bodies, signed URLs, and internal identifiers are not forwarded.

Validation error
{
  "error": {
    "message": "duration must be an integer between 4 and 15 seconds",
    "type": "invalid_request_error",
    "param": "duration",
    "code": "unsupported_duration"
  }
}
HTTPMeaningClient action
400 / 413Invalid or oversized requestCorrect the indicated parameter.
401 / 403Invalid key, model access, or job ownershipUse the creating key and verify team access.
404Job not found for this keyVerify the opaque gateway job ID.
409Idempotency conflict or invalid cancellation stateDo not retry unchanged with a new key.
429Rate, concurrency, quota, or spend limitHonor Retry-After when present.
5xxGateway or upstream availability issueRetry cautiously with the same idempotency key.

Pass-through content path

Video content is not persisted by Moca Router

The gateway sends the prompt to MiniMax and streams completed video bytes back through the content endpoint. Its application database does not store the raw prompt, video bytes, signed MiniMax content URL, upstream task ID in plaintext, or raw idempotency key. It stores the minimum routing, security, status, usage, and billing metadata required to authorize and meter the job.

MiniMax policy still applies

MiniMax receives the prompt and generates the video. Moca Router's no-content-persistence boundary does not alter MiniMax's own processing, abuse-monitoring, logging, or retention terms. Review the applicable upstream agreement before sending sensitive material.
  • Do not include API keys, passwords, personal identifiers, or regulated secrets in prompts.
  • Avoid logging prompts, content responses, or job URLs in your own application.
  • Store downloaded output only if needed and apply your own access and deletion policy.
  • Use x-mocarouter-request-id, the gateway job ID, and your idempotency key for support correlation.

Ready to integrate?

Confirm MiniMax-H3 access and create a key

Verify that the model is enabled for your team, inspect its effective per-second price, then submit a minimal request with a fresh idempotency key.