Use native fetch—no client SDK required
Node.js 22 includes fetch, crypto.randomUUID, and AbortSignal, which is enough for a complete MiniMax H3 API integration. Keep the key in your backend and expose a narrow application endpoint to your own users instead of forwarding the vgenv key to the browser.
export VGENV_API_KEY="vgenv_live_..."
node quickstart.mjsBuild one JSON request helper
A shared helper should parse both successful and error JSON, retain the HTTP status, and read Retry-After. This keeps create and polling behavior consistent and prevents error payloads from being silently discarded.
const base = "https://api.vgenv.com/v2";
const key = process.env.VGENV_API_KEY;
async function api(path, init = {}) {
const response = await fetch(base + path, {
...init,
headers: {
Authorization: `Bearer ${key}`,
...(init.body ? { "Content-Type": "application/json" } : {}),
...init.headers,
},
});
const data = await response.json();
if (!response.ok) {
const error = new Error(data.message ?? `HTTP ${response.status}`);
error.status = response.status;
error.retryAfter = Number(response.headers.get("retry-after")) || null;
throw error;
}
return data;
}Create and poll the Video resource
The create response already contains the Video ID, queue state, and final quote. Poll the same resource until it reaches a terminal state. Keep a deadline around the whole operation rather than applying an aggressive timeout to the generation itself.
const video = await api("/videos", {
method: "POST",
headers: { "Idempotency-Key": `node-${crypto.randomUUID()}` },
body: JSON.stringify({
model: "minimax/h3",
tier: "fast",
prompt: "A product reveal with a slow cinematic camera move",
duration: 5,
resolution: "480p",
aspect_ratio: "16:9",
}),
});
let current = video;
while (!["succeeded", "failed", "cancelled"].includes(current.status)) {
await new Promise(resolve => setTimeout(resolve, 8000));
current = await api(`/videos/${encodeURIComponent(video.id)}`);
}
console.log(current.status === "succeeded" ? current.output.url : current.error);Type the state machine, not just the payload
In TypeScript, model status as a discriminated union so code cannot read output before success. Store queued_at, queue_position, progress, and the price snapshot if your product displays job history or cost attribution.
- Do not assume every 2xx response is already completed.
- Treat signed output URLs as temporary delivery URLs.
- Handle 401, 402, 422, and 429 separately in your product UI.
- Use the same idempotency key when retrying an uncertain create response.