Browse this guide
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
Lifecycle
Create, then poll
202 Accepted with a stable vid_... job ID; generation continues asynchronously.Metering
Actual output seconds
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.
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.mp4Text-to-video only
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
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
You can inspect the model capability and effective pricing record before creating a job:
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.
# 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..."statusoptionalactive for queued, in_progress, and finalizing; use terminal for all finished states; or send one exact public status. Omit it to list all jobs.limitoptional1 to 100. The default is 20.afteroptionalnext_cursor from the previous page. Do not decode, edit, or reuse it with a different key or filter.{
"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
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+)
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
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.
modelrequiredMiniMax-H3.promptrequireddurationrequired4 through 15, representing requested seconds.resolutionrequired768P or 2K.ratiorequired21: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+)
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
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.
{
"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:
{
"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
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
4xxvalidation, access, quota, or idempotency-conflict responses unchanged. - Respect
Retry-Afteron429responses and add jitter to polling retries.
Uncertain upstream outcomes are not redispatched
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.
{
"error": {
"message": "duration must be an integer between 4 and 15 seconds",
"type": "invalid_request_error",
"param": "duration",
"code": "unsupported_duration"
}
}| HTTP | Meaning | Client action |
|---|---|---|
| 400 / 413 | Invalid or oversized request | Correct the indicated parameter. |
| 401 / 403 | Invalid key, model access, or job ownership | Use the creating key and verify team access. |
| 404 | Job not found for this key | Verify the opaque gateway job ID. |
| 409 | Idempotency conflict or invalid cancellation state | Do not retry unchanged with a new key. |
| 429 | Rate, concurrency, quota, or spend limit | Honor Retry-After when present. |
| 5xx | Gateway or upstream availability issue | Retry 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
- 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.
