how-tos

MiniMax H3 API Guide: PoYo Integration, Pricing, and Prompts

Poyo.ai Team
Updated
15 min read
Share:

With PoYo's MiniMax H3 API, you can generate 5–15 second 2K videos from text, first and last frames, or reference media. Without reference media, a 5-second video has an estimated cost of 105 credits ($0.525).

This guide starts with your first request, then covers pricing, input modes, Python and Node.js examples, and prompts for different scenes. All endpoints and prices in this guide apply to PoYo.

Quick start: submit a task and retrieve the video

1. Submit a 5-second video task

Get an API key from the PoYo console, replace YOUR_POYO_API_KEY below, and run the command in a terminal or on your server. Keep the key on the server; do not include it in browser code or public repositories.

In PoYo requests, the model value for MiniMax H3 is hailuo-03. The following example submits a paid generation task.

export POYO_API_KEY="YOUR_POYO_API_KEY"

curl --fail-with-body --max-time 30 \
  --request POST 'https://api.poyo.ai/api/generate/submit' \
  --header "Authorization: Bearer ${POYO_API_KEY}" \
  --header 'Content-Type: application/json' \
  --data '{
    "model": "hailuo-03",
    "input": {
      "prompt": "A golden retriever walks through a sunlit autumn park. One slow tracking shot, natural movement, warm afternoon light.",
      "duration": 5,
      "resolution": "2K",
      "aspect_ratio": "16:9"
    }
  }'

After a successful submission, read the task ID from data.task_id. This example shows the response structure with a placeholder task ID:

{
  "code": 200,
  "message": "success",
  "data": {
    "task_id": "YOUR_TASK_ID"
  }
}

2. Query the task result

Set TASK_ID to the returned task ID and use the same API key:

TASK_ID="YOUR_TASK_ID"

curl --fail-with-body --max-time 30 \
  --request GET "https://api.poyo.ai/api/generate/status/${TASK_ID}" \
  --header "Authorization: Bearer ${POYO_API_KEY}"

A successful submission means the task has been accepted; you still need to wait for video generation to finish. The query response has four possible data.status values:

StatusMeaningNext step
not_startedWaiting to be processedQuery again later
runningGeneration in progressQuery again later
finishedGeneration completeRead data.files[].file_url
failedGeneration failedCheck data.error_message and stop polling

Here is an example of a completed response. The URL and timestamp are illustrative:

{
  "code": 200,
  "data": {
    "task_id": "YOUR_TASK_ID",
    "status": "finished",
    "credits_amount": 105,
    "files": [
      {
        "file_url": "https://example.com/generated/video.mp4",
        "file_type": "video"
      }
    ],
    "created_time": "2026-09-22T08:30:00",
    "progress": 100,
    "error_message": null
  }
}

See the PoYo task query documentation for field definitions and response examples for other states. If a query temporarily fails or your waiting period expires, retain the task ID and query again later; do not automatically submit another paid generation.

How MiniMax H3 pricing works on PoYo

The following prices were checked on September 21, 2026. All amounts are in USD, using 1 credit = $0.005. Before generating in bulk, confirm the latest prices on the model page.

Billable itemCreditsUSD
Generated video duration21 credits/second$0.105/second
Input reference video duration21 credits/second$0.105/second
First 5 reference images per generation0$0
Each reference image from the 6th onward6.4 credits$0.032

Estimated credits = 21 × (output seconds + billable reference video seconds) + 6.4 × the number of reference images beyond 5. With 5 or fewer reference images, the additional image charge is 0. Multiply the estimated credits by $0.005 for the USD cost. For reference videos with fractional seconds, billing uses the duration determined by the server.

ExampleEstimated creditsEstimated USD
Generate 5 seconds without reference media105$0.525
Generate 10 seconds without reference media210$1.05
Generate 15 seconds without reference media315$1.575
Generate 10 seconds + 5 seconds of reference video + 7 reference images327.8$1.639

The last example uses 21 × (10 + 5) + 6.4 × (7 − 5). These are costs per generation; if a shot takes several attempts, include each generation in your budget.

Choose an input mode and reference media

The media fields you submit determine the input mode. The quick-start example above uses text-to-video. Add the appropriate fields when you need to specify starting and ending frames or use reference media.

ModeInput fieldsUsage
Text-to-videoprompt, with no media URLsDescribe the scene, action, and camera movement in words
First/last frame generationimage_urlsThe first image is the starting frame; the second, optional image is the ending frame. Omit aspect_ratio; the first frame determines the ratio
Multimodal reference generationreference_image_urls, reference_video_urls, reference_audio_urlsCombine images, video, and audio; reference audio requires a reference image or video

Do not combine image_urls with any reference_*_urls field. All media URLs must be publicly accessible and directly downloadable.

Parameter and media limits

The following limits come from the PoYo MiniMax H3 API documentation:

  • prompt is required and accepts up to 2000 characters.
  • duration is an integer from 5–15 seconds, defaulting to 5; resolution only supports 2K.
  • Text-to-video supports 21:9, 16:9, 4:3, 1:1, 3:4, and 9:16, defaulting to 16:9. Reference mode also supports adaptive and uses it by default.
  • You can provide up to 9 reference images, 3 reference videos, and 3 reference audio clips. Each video or audio clip must be 2–15 seconds; the total video duration and total audio duration must each stay within 15 seconds.

First/last frame request example

Use the JSON below as the body of POST /api/generate/submit. Media URLs under example.com are placeholders: replace them with your own downloadable media before submitting. To use only a starting frame, remove the second URL from the array.

{
  "model": "hailuo-03",
  "input": {
    "prompt": "The camera slowly moves through the temple doorway, starting at the entrance and ending inside the hall. Dust floats in the sunlight.",
    "duration": 6,
    "resolution": "2K",
    "image_urls": [
      "https://example.com/assets/temple-entrance.jpg",
      "https://example.com/assets/temple-interior.jpg"
    ]
  }
}

Combined image and video reference example

Use Image 1 to specify the character's appearance and Video 1 for the desired camera movement. These refer to the first item in the image array and the video array, respectively. Replace the media URLs and check the reference video's duration before submitting.

{
  "model": "hailuo-03",
  "input": {
    "prompt": "Use Image 1 for the character appearance and outfit. The character turns and walks toward the window. Use Video 1 as the camera movement reference.",
    "duration": 8,
    "resolution": "2K",
    "aspect_ratio": "adaptive",
    "reference_image_urls": [
      "https://example.com/assets/character.png"
    ],
    "reference_video_urls": [
      "https://example.com/assets/camera-motion.mp4"
    ]
  }
}

Python and Node.js integration examples

The examples below separate submission and querying into two functions: obtain and save the task ID first, then wait for the result. If querying is interrupted, resume with the original task ID without generating again. Only status queries retry connection errors, timeouts, HTTP 429, and selected 5xx errors; submissions are not automatically retried.

Both implementations wait 10 seconds between queries, make up to 60 queries, and set a 30-second timeout per request. Total waiting time therefore also includes request duration. These are application settings for the examples, not a generation-time guarantee. Adjust the intervals and retry policy to your application's needs.

Python: submit a task and poll for the result

Install the dependency with python -m pip install requests and set the POYO_API_KEY environment variable as described above. This synchronous Python script polls an asynchronous generation task.

import os
import time
import requests

BASE_URL = "https://api.poyo.ai"
API_KEY = os.environ["POYO_API_KEY"]
if not API_KEY.strip():
    raise ValueError("Set POYO_API_KEY")
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
RETRYABLE_HTTP = {429, 500, 502, 503, 504}


def response_data(response):
    response.raise_for_status()
    body = response.json()
    if body.get("code") != 200 or not isinstance(body.get("data"), dict):
        raise RuntimeError(f"Unexpected API response: {body}")
    return body["data"]


def submit_h3(prompt, duration=5):
    response = requests.post(
        f"{BASE_URL}/api/generate/submit",
        headers=HEADERS,
        json={"model": "hailuo-03", "input": {
            "prompt": prompt, "duration": duration,
            "resolution": "2K", "aspect_ratio": "16:9",
        }},
        timeout=30,
    )
    task_id = response_data(response).get("task_id")
    if not isinstance(task_id, str) or not task_id:
        raise RuntimeError("Submission response is missing a task ID; check records instead of resubmitting automatically")
    return task_id


def wait_for_h3(task_id, max_polls=60):
    for _ in range(max_polls):
        time.sleep(10)
        try:
            response = requests.get(
                f"{BASE_URL}/api/generate/status/{task_id}",
                headers=HEADERS, timeout=30,
            )
        except (requests.Timeout, requests.ConnectionError):
            continue
        if response.status_code in RETRYABLE_HTTP:
            continue
        task = response_data(response)
        status = task.get("status")
        if status == "finished":
            for item in task.get("files") or []:
                if item.get("file_type") == "video" and item.get("file_url"):
                    return item["file_url"]
            raise RuntimeError(f"Task {task_id} finished, but the response has no video URL")
        if status == "failed":
            raise RuntimeError(f"Task {task_id} failed: {task.get('error_message')}")
        if status not in {"not_started", "running"}:
            raise RuntimeError(f"Task {task_id} returned an unknown status: {status}")
    raise TimeoutError(f"Query limit reached; keep the task ID and query again later: {task_id}")


if __name__ == "__main__":
    task_id = submit_h3("A golden retriever walks through a sunlit autumn park.")
    # In your application, save task_id to the database here before polling.
    print(f"Task ID: {task_id}", flush=True)
    print(wait_for_h3(task_id))

To resume querying, call wait_for_h3(saved_task_id) directly. HTTP errors such as 401 and 403, API-level errors, or unexpected responses stop polling; address their cause before continuing.

Node.js: use the built-in fetch

The following JavaScript uses the built-in fetch in Node.js 20 or later, so no HTTP client dependency is needed. Save it as an .mjs file and run it on the server.

const BASE_URL = 'https://api.poyo.ai';
const apiKey = process.env.POYO_API_KEY;
if (!apiKey?.trim()) throw new Error('Set POYO_API_KEY');
const headers = {
  Authorization: `Bearer ${apiKey}`,
  'Content-Type': 'application/json',
};
const retryableHttp = new Set([429, 500, 502, 503, 504]);
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function responseData(response) {
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  const body = await response.json();
  if (body.code !== 200 || !body.data || typeof body.data !== 'object') {
    throw new Error(`Unexpected API response: ${JSON.stringify(body)}`);
  }
  return body.data;
}

export async function submitH3(prompt, duration = 5) {
  const response = await fetch(`${BASE_URL}/api/generate/submit`, {
    method: 'POST', headers,
    body: JSON.stringify({
      model: 'hailuo-03',
      input: { prompt, duration, resolution: '2K', aspect_ratio: '16:9' },
    }),
    signal: AbortSignal.timeout(30_000),
  });
  const data = await responseData(response);
  if (typeof data.task_id !== 'string' || !data.task_id) {
    throw new Error('Submission response is missing a task ID; check records instead of resubmitting automatically');
  }
  return data.task_id;
}

export async function waitForH3(taskId, maxPolls = 60) {
  for (let attempt = 0; attempt < maxPolls; attempt++) {
    await sleep(10_000);
    let response;
    try {
      response = await fetch(
        `${BASE_URL}/api/generate/status/${encodeURIComponent(taskId)}`,
        { headers, signal: AbortSignal.timeout(30_000) },
      );
    } catch (error) {
      if (error instanceof Error &&
          (error.name === 'TimeoutError' || error.name === 'TypeError')) {
        continue;
      }
      throw error;
    }
    if (retryableHttp.has(response.status)) {
      await response.body?.cancel();
      continue;
    }
    const task = await responseData(response);
    if (task.status === 'finished') {
      const video = task.files?.find((file) => file.file_type === 'video' && file.file_url);
      if (!video) throw new Error(`Task ${taskId} finished, but the response has no video URL`);
      return video.file_url;
    }
    if (task.status === 'failed') {
      throw new Error(`Task ${taskId} failed: ${task.error_message || 'No error details provided'}`);
    }
    if (!['not_started', 'running'].includes(task.status)) {
      throw new Error(`Task ${taskId} returned an unknown status: ${task.status}`);
    }
  }
  throw new Error(`Query limit reached; keep the task ID and query again later: ${taskId}`);
}

const taskId = await submitH3('A golden retriever walks through a sunlit autumn park.');
// In your application, save taskId to the database here before polling.
console.log(`Task ID: ${taskId}`);
console.log(await waitForH3(taskId));

To resume querying, replace the submission and query calls at the end of the file with waitForH3(savedTaskId). This polling example is suited to scripts or background tasks. In a web application, return the task ID to the frontend and track generation in the background rather than keeping a page request waiting.

Receive results through a webhook

You can also pass callback_url at the top level of the submission request, alongside model and input, for example https://your-domain.com/webhooks/poyo. PoYo sends the result to that URL when the task succeeds or fails.

The receiver should match the task ID against local records and handle duplicate notifications by task ID to avoid repeating downstream operations. Time-consuming work such as downloading or transcoding the video can run in background tasks. You can still use status queries to verify results while integrating callbacks.

Prompts and scene examples

Describe the subject and action first, then add camera movement, setting, lighting, and sound. When using reference media, state the role of Image 1, Video 1, or Audio 1 so that a single reference does not carry conflicting requirements.

The four English examples below illustrate prompt writing; they do not include tested generation results. English terms make the examples easy to reuse, but do not imply that English necessarily works better than Chinese. Timing and appearance-preservation instructions describe intended results; inspect the actual output.

Product advertisement: 8 seconds, reference image mode

Put the product image first in input.reference_image_urls, set duration: 8 and aspect_ratio: "16:9", and use the text below as prompt. Follow the reference-mode request structure above; remove reference_video_urls if you do not need a reference video.

Use Image 1 as the product reference. A clear glass perfume bottle rests on
wet black stone after rain. Preserve the bottle shape, cap, and label placement.
One continuous 8-second shot: begin close to the water droplets, then slowly
pull back to reveal the bottle in warm rim light. Realistic glass reflections,
no added text. Soft rain ambience.

Start with a simple shot to check whether the bottle and label remain consistent, then try more complex orbiting camera movement.

Character dialogue: 8 seconds, reference image mode

Put the character image first in input.reference_image_urls and set duration: 8 and aspect_ratio: "16:9". A short line of dialogue makes it easier to check lip sync, speech rate, and the character's appearance.

Use Image 1 as the character reference. An 8-second medium close-up of a pastry
chef in a quiet kitchen at sunrise. She looks into the camera and says:
"Every detail matters in baking." Natural breathing, a subtle smile,
soft window light. Preserve her face, hairstyle, and apron. A steady camera,
clear speech, quiet room tone.

Vertical game advertisement: 10 seconds, text-to-video

Set duration: 10 and aspect_ratio: "9:16". Time segments express the intended sequence of actions; if the scene feels crowded, start by reducing the number of actions and characters.

A 10-second vertical fantasy game trailer.
0-3s: An armored knight walks through a ruined stone gate.
3-6s: One creature charges; the knight blocks once with a shield.
6-9s: The camera rises to reveal the fortress walls.
9-10s: Hold the final composition.
Readable action, consistent armor, wind and impact sounds, no UI or captions.

Continuous tracking shot: 10 seconds, text-to-video

Set duration: 10 and aspect_ratio: "16:9". Build the shot around one subject and one route, then check for cuts, changes in the subject, or discontinuous movement.

One unbroken 10-second tracking shot follows a cyclist through a night market.
Begin behind the rear wheel, rise to shoulder height, then move alongside
without cutting. Wet pavement reflections, a clear path between stalls,
consistent bicycle geometry, no speed ramps or scene transitions.
Natural street ambience and bicycle tires rolling on wet pavement.

FAQ and integration troubleshooting

Can I use MiniMax's own API request format directly?

These examples use PoYo's POST /api/generate/submit and GET /api/generate/status/{task_id}. Endpoints, model identifiers, and field structures may differ between platforms; follow the documentation for the platform you use.

Should I resubmit a task that stays in not_started?

not_started means the task is waiting to be processed; it is not itself a failure. Keep the task ID and continue querying. If your application's waiting limit expires, record the task and check it later. Contact support with the task ID if needed.

What if submission times out before I receive a task ID?

A request timeout does not prove that the server did not accept the task. Check task records first; if needed, contact support with details such as the submission time. You need a task ID to use the status query endpoint. Do not treat resubmission as a remedy for a failed query.

Can I combine images, video, and audio as references?

Yes, combine them in reference mode within the quantity and duration limits above. Reference audio requires a reference image or video. These fields cannot be combined with image_urls for first/last frame mode.

How much does a 10-second video cost?

At the prices listed here, without reference media, the cost is 10 × 21 = 210 credits, or $1.05. Reference videos and reference images beyond the first 5 add to the cost.

Before integrating into your application, confirm that media URLs allow direct downloads, save task IDs, and set appropriate generation and concurrency limits per user. Before publishing a generated video, check that character appearance, product shape, text, motion, and sound suit its intended use.

Try generating on the MiniMax H3 model page, or visit the API key page to start integrating. For more about the models, see the MiniMax H3 review and the MiniMax H3 versus Seedance 2.5 comparison.

Share: