HiAPI
Blog

Tutorial

How to Call flux-3-max/keyframes-to-video via the HiAPI API: curl and Python Guide

Auth, keyframe submission, task polling, and result retrieval for developers

Nov 3, 2026 6 min read
封面图:深松绿背景上被直线切割成平面色块的大型棱镜,鼠尾草绿与雾绿点缀,几何克制。

What this integration actually does

flux-3-max/keyframes-to-video is an asynchronous task API for generating video from keyframe images. Unlike a synchronous endpoint that returns a result in the same HTTP response, this model accepts your keyframes, creates a background task, and hands back a task identifier you can use to check progress.

The end-to-end lifecycle has four stages:

  1. Authenticate — every request carries your HiAPI API key.
  2. Submit — send the keyframes and parameters to start a video task.
  3. Poll — repeatedly query the task status until it reaches a terminal state.
  4. Retrieve — download the generated video from the URL returned when the task succeeds.

Keeping this lifecycle in mind shapes how you write the client: submission and result retrieval are separate calls, and your application must tolerate a task that takes tens of seconds to minutes. The curl and Python examples below follow the same four stages, so you can port the flow into any language or job runner.

Get your API key and authenticate

Your API key is issued from the HiAPI dashboard. Treat it like a password: it identifies your account and authorizes billable calls, so it should never appear in source code, commit history, or log output.

Authentication is passed as an HTTP header on every request:

Authorization: Bearer $HIAPI_API_KEY

In practice, store the key in an environment variable and read it at runtime. For shell examples, export it once per session:

export HIAPI_API_KEY="your-key-here"

For Python, read it with os.environ["HIAPI_API_KEY"] rather than hardcoding the string. Add .env files to .gitignore, and if you log requests for debugging, redact the Authorization header before it reaches your log aggregator. If a key is ever exposed, rotate it from the dashboard immediately.

Build the request with curl

Submitting a video task means sending a JSON body that names the model and carries your keyframes. The exact field names for keyframe inputs and generation parameters are documented on the model page, so use that as the source of truth; the shape below shows the request structure.

curl -X POST "https://api.hiapi.ai/v1/tasks" \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "flux-3-max/keyframes-to-video",
    "input": {
      "keyframes": [
        "https://example.com/frame-01.png",
        "https://example.com/frame-02.png"
      ]
    }
  }'

What each part does:

  • model — selects flux-3-max/keyframes-to-video, so the same endpoint can route to other models later without changing your transport code.
  • input.keyframes — an ordered list of image URLs. Order matters: the first frame anchors the start of the clip, later frames guide subsequent moments.
  • Authorization — the bearer header described above.
  • Content-Type — required for JSON bodies.

The response is a task object containing an identifier. Save that ID; it is the handle for every subsequent poll.

The same request in Python

The Python version mirrors the curl call. Using requests keeps it short, but the standard library works just as well if you want zero dependencies.

import os
import requests

API_KEY = os.environ["HIAPI_API_KEY"]
BASE_URL = "https://api.hiapi.ai/v1"


def submit_video_task(keyframe_urls: list[str]) -> str:
    response = requests.post(
        f"{BASE_URL}/tasks",
        headers={
            "Authorization": f"Bearer {API_KEY}",
            "Content-Type": "application/json",
        },
        json={
            "model": "flux-3-max/keyframes-to-video",
            "input": {"keyframes": keyframe_urls},
        },
        timeout=30,
    )
    response.raise_for_status()
    return response.json()["id"]


task_id = submit_video_task([
    "https://example.com/frame-01.png",
    "https://example.com/frame-02.png",
])
print(task_id)

Two details worth keeping: the explicit timeout so a hung connection cannot block your worker forever, and raise_for_status() so a 4xx or 5xx surfaces as an exception instead of a silently malformed payload. Keep the function small and pure — it should submit and return the ID, nothing else. Polling is a separate concern.

Poll for task status and retrieve the result

Video generation takes real compute time, so the API does not block until a file appears. Instead, you query the task until it finishes. A status of succeeded carries an output URL; failed carries an error; anything else means keep waiting.

import time
import requests


def wait_for_video(task_id: str, poll_interval: int = 5, max_wait: int = 600) -> str:
    deadline = time.monotonic() + max_wait
    while time.monotonic() < deadline:
        response = requests.get(
            f"{BASE_URL}/tasks/{task_id}",
            headers={"Authorization": f"Bearer {API_KEY}"},
            timeout=30,
        )
        response.raise_for_status()
        task = response.json()

        if task["status"] == "succeeded":
            return task["output"]["video_url"]
        if task["status"] == "failed":
            raise RuntimeError(task.get("error", "task failed"))

        time.sleep(poll_interval)

    raise TimeoutError(f"task {task_id} did not finish in time")

Design notes for production:

  • Poll interval — a few seconds is enough. Hammering the endpoint adds load without making the task finish sooner.
  • Deadline — always cap total wait time and fail loudly instead of looping forever.
  • Download — the returned URL is temporary; fetch the file and move it to your own storage as soon as the task succeeds.
  • Decoupling — in a web app, run this loop in a background worker and notify the client by webhook or queue, rather than holding an HTTP request open.

Handle errors and edge cases

Most integration failures fall into four buckets.

Authentication errors (401/403). The key is missing, malformed, or no longer valid. Check that the environment variable is actually exported in the process that makes the call — a common trap is a working local shell but a missing variable in a container or cron job.

Bad input (4xx). Unreachable keyframe URLs, unsupported image formats, or parameters outside allowed ranges will reject the submission. Validate that every keyframe URL is publicly fetchable before submitting; a slow or gated host is the most frequent cause of an otherwise correct request failing.

Task failure. A task can be accepted and then fail during generation. Always inspect the error field rather than treating a completed poll as success. Log the task ID alongside the error so you can correlate it with support requests.

Timeouts and retries. Network errors on submission are usually safe to retry with backoff. Retrying a successful submission is not — you may create duplicate tasks and duplicate charges. Read the response before retrying, and use exponential backoff with jitter on the polling loop as well.

A final hygiene point: never log the Authorization header, and scrub keyframe URLs if they contain signed query strings. When in doubt about field names or limits for this model, check the flux-3-max/keyframes-to-video documentation and mirror those values exactly in your client.

Related articles

View all articles →

Start building with persistent AI artifacts

Get your API key and call image, video and audio models through one task-based API.

Read the API docs