Choose a model, enter your prompt, and see the result.
HiAPI Blog
HiAPI
Generate it with HiAPI
Most image-to-video APIs take one still and improvise the motion. A smaller set of models let you pin down where the shot ends, not just where it starts — you supply a first frame and a last frame, and the model interpolates the motion between them. That's the difference between "animate this photo" and "make this exact transition happen."
The catch: support for last-frame control is inconsistent across models, and even within the same vendor's lineup, the flagship tier and the fast/turbo tier can differ. This recipe shows exactly which hiapi video models support first-frame-only vs. first+last-frame control, with the real (non-uniform) input fields for each, so you don't have to find out the hard way that your chosen model silently ignores your last frame.
An hiapi API key (sk-...) from the dashboard, and two image URLs your target model can fetch (public HTTPS URLs — no local file uploads on the task endpoint). Every model below is called through the same unified task API, so once you have a key working for one, it works for all of them.
All hiapi video generation goes through one endpoint: create a task, then poll it (or use a callback — see Production patterns) until it reaches a terminal state.
# 1. Create the task
curl -s -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer sk-<your-key>" \
-H "Content-Type: application/json" \
-d '{
"model": "kling-3.0-omni/image-to-video",
"input": {
"prompt": "the subject turns toward the camera and smiles",
"image_urls": [
"https://your-cdn.example.com/first-frame.jpg",
"https://your-cdn.example.com/last-frame.jpg"
],
"resolution": "720p",
"duration": 5,
"sound": false
}
}'
# -> {"data": {"id": "task_...", "status": "pending"}}
# 2. Poll until terminal
curl -s https://api.hiapi.ai/v1/tasks/task_... \
-H "Authorization: Bearer sk-<your-key>"
# -> {"data": {"id": "task_...", "status": "succeeded",
# "output": [{"url": "https://...mp4", "expireAt": "..."}]}}
kling-3.0-omni/image-to-video is the cleanest example of the pattern: input.image_urls takes one or two image URLs. Pass one and the model only anchors the first frame, improvising the rest. Pass two and the second URL becomes the enforced last frame — the model is told exactly where to land the shot. Duration is an integer 3–15 (seconds), resolution is 720p / 1080p / 4k, and the optional sound boolean adds generated ambient audio. There's no aspect_ratio field — output framing follows whatever frame(s) you supplied.
Not every model shares that shape. seedance-2.5/image-to-video gets to the same result through two separately named fields instead of an array:
{
"model": "seedance-2.5/image-to-video",
"input": {
"prompt": "smooth camera push toward the subject",
"first_frame_url": "https://your-cdn.example.com/first-frame.jpg",
"last_frame_url": "https://your-cdn.example.com/last-frame.jpg",
"resolution": "720p",
"duration": 6,
"aspect_ratio": "adaptive",
"generate_audio": false
}
}
last_frame_url only makes sense paired with first_frame_url — you can send first_frame_url alone for a first-frame-only shot, but not the reverse. Duration on this model runs 4–30s, resolution is capped at 720p/1080p (no 4K tier), and aspect_ratio defaults to adaptive, meaning it follows the first frame instead of taking an explicit 16:9/9:16 value.
The field names are genuinely different per model — image_urls vs. first_frame_url/last_frame_url — so treat every model swap as a schema change, not a drop-in replacement.
veo-3.1/image-to-video takes a single image_url string and has no last-frame parameter at all — you get aspect_ratio (auto/16:9/9:16), resolution, and a fixed duration enum (4, 6, or 8 seconds), but no way to pin the ending frame. Same story one tier down in the Kling lineup: kling-3.0-turbo/image-to-video (the fast/cheap sibling of kling-3.0-omni) caps image_urls at a single item — no last frame, and no 4K resolution either. If you need last-frame control, the flagship tier or a model built for it (like seedance-2.5) is the deliberate choice, not an assumption you can make from the model family name.
Callbacks over polling for anything at scale. Pass a callback object in the same request body instead of polling in a loop:
{
"model": "kling-3.0-omni/image-to-video",
"input": { "...": "..." },
"callback": { "url": "https://your-service.example.com/hooks/hiapi", "when": "final" }
}
when: "final" fires once, on whichever terminal state the task reaches — succeeded or failed are both terminal, so dedupe your webhook handler by taskId rather than assuming a callback only ever means success. Polling GET /v1/tasks/:id is the better fit for local debugging, low-volume one-offs, or as a reconciliation fallback if a callback never arrives (network blips, your endpoint being briefly down).
Idempotency. Retries after a timeout or dropped connection can create duplicate tasks if you're not careful — track the task id you get back from the create call and make retries a GET on that id before you consider submitting a new one.
Output URLs expire. The expireAt field on each output entry is not decorative — download and persist the video (to your own storage) as soon as the task succeeds, whether you learn that from a callback or a poll.
Auth failures. A bad or missing key returns HTTP 401 with {"error": {"code": "permission_denied", ...}}. Check for that status/code explicitly rather than assuming any non-2xx means the generation itself failed — it's worth distinguishing "my key is wrong" from "the model rejected my input" in your error handling, since the fix is completely different.
first_frame_url/last_frame_url variant.Authorization: Bearer sk-... header and key scoping work./v1/tasks request/response reference, including callback options.Which hiapi video models support last-frame control?
kling-3.0-omni/image-to-video (via a 2-item image_urls array) and seedance-2.5/image-to-video (via first_frame_url + last_frame_url) both support it today. veo-3.1/image-to-video and kling-3.0-turbo/image-to-video do not — they accept a single starting frame only.
Can I use a last frame without also specifying a prompt?
Both models still accept (and generally expect) a prompt alongside the frames — it guides the motion between the two frames, not just the content. An empty or missing prompt isn't rejected outright on every model, but you'll get more predictable motion by describing the transition explicitly.
Do I need the same aspect ratio for both frames?
Neither kling-3.0-omni/image-to-video nor seedance-2.5/image-to-video exposes an explicit aspect_ratio you set independently of your images — output framing follows the input frame(s), so mismatched aspect ratios between your first and last image will produce a visibly awkward result rather than an error. Crop or pad both frames to the same ratio before you submit them.
What happens if I only send a last frame and no first frame?
Not supported on either model — last_frame_url is only valid alongside first_frame_url on seedance-2.5, and the kling-3.0-omni array is read positionally (first item = first frame). There's no "reverse" mode where you specify only an ending.
Is last-frame control the same thing as video-to-video motion transfer?
No — that's a separate capability (seedance-2.5/reference-to-video on hiapi, which takes a reference video to drive motion or camera movement rather than two still frames). First/last-frame control interpolates between two images; reference-to-video imitates the motion pattern of an existing clip.
Does adding a last frame cost more than a first-frame-only generation? Pricing is per-second/per-resolution on both models rather than per-input-image, so adding a last frame doesn't itself add cost — check current rates on the pricing page.