Skip to main content
info
The feature described on this page is in beta.

Video API

One request makes a video. Name a character and it keeps that character's look, the same way the Image API does.

Generate​

curl -X POST https://api.aicu.ai/v1/video/generate \
-H "Authorization: Bearer $AICU_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "wan3.0-video",
"prompt": "@MeiSoleil waves at the camera on a beach at sunset",
"resolution": "720p",
"seconds": 5
}'
{
"id": "5f9c…",
"status": "processing",
"ap_cost": 7000,
"status_url": "https://api.aicu.ai/v1/video/status/5f9c…",
"url": "https://api.aicu.ai/v1/video/file/5f9c…"
}

Generation takes longer than one HTTP request allows, so the call returns 202 immediately with an id. Poll status_url until it says completed, then fetch url.

curl "https://api.aicu.ai/v1/video/status/$ID" -H "Authorization: Bearer $AICU_API_KEY"
{
"id": "5f9c…",
"status": "completed",
"ap_cost": 7000,
"url": "https://api.aicu.ai/v1/video/file/5f9c…",
"download_url": "https://api.aicu.ai/v1/video/d/…",
"download_expires_at": "2026-12-03T00:00:00.000Z"
}
curl -L "https://api.aicu.ai/v1/video/file/$ID" -H "Authorization: Bearer $AICU_API_KEY" -o out.mp4

status is processing, completed or failed. Polling every 10 seconds is plenty; a 5-second clip usually lands in one to five minutes.

Give the video to someone​

url needs your API key, so it is for your program, not for a person. A finished job also comes with download_url, which opens without a key — that is the one to put in a mail, a chat message or a web page.

# Opens in a browser. No key, no header.
open "$(curl -s "https://api.aicu.ai/v1/video/status/$ID" \
-H "Authorization: Bearer $AICU_API_KEY" | jq -r .download_url)"

Add ?dl=1 to make the browser save the file instead of playing it.

The link expires 30 days after the video is made; download_expires_at is the exact moment. After that, and for a token that never existed or has the wrong shape, the link returns the same 404 — it does not say which, so a link you share leaks nothing about the others.

Keep a copy of anything you need for longer. The link is a way to hand the result over, not storage.

Choose a model​

model picks the video model. Leave it out and you get gemini-omni-1.1-flash, so existing calls keep working.

modelFromResolutionsSecondsGood for
gemini-omni-1.1-flash (default)Google360p / 720p / 1080p5 to 10, decided by the modelValue at 720p. You are charged for the length it actually made.
wan3.0-videoAlibaba480p / 720p / 1080p2 to 10Quality, with sound. Keeps a character from one reference image.
wan3.0-video-primeAlibaba480p / 720p / 1080p2 to 10The same quality in a third to half the time.

The current list, with each model's limits and rates, is always in GET /v1/models (type: "video"). A model or resolution outside these ranges returns 400 rather than being quietly rounded.

Fields​

FieldMeaning
modelSee above. Default gemini-omni-1.1-flash.
promptWhat happens in the shot. An @Name anywhere in the text selects that character and is removed from the prompt.
characterSame as writing @Name, as a separate field. A registered character supplies its own reference image; your own character's name needs reference_image.
reference_imageBase64 image used as the character reference. Up to about 8 MB.
first_frameBase64 image that becomes the first frame, for animating a storyboard. Cannot be combined with reference_image.
last_frameBase64 image that becomes the last frame. Use it together with first_frame and the model fills the motion between them.
resolutionDepends on the model (table above). Default 720p. This sets the price.
secondsDepends on the model (table above). Default 5. This sets the price.
aspect_ratio16:9 (default), 9:16, or 1:1 where the model supports it. With first_frame the images decide the shape.
visibilitypublic lists the result with its credit line; private keeps nothing.

First and last frame​

Give the start and the end, and let the model find the way between.

{
"model": "wan3.0-video",
"first_frame": "<base64 of the opening shot>",
"last_frame": "<base64 of the closing shot>",
"prompt": "the camera slowly pulls back as she turns toward the window",
"seconds": 5
}

Two frames from the same contact sheet (below) make a natural pair: same lighting, same character, different moment.

Price​

Per second of finished video, by model and resolution. 1 USD = 10,000 AP. The current rates are on the pricing page and in GET /v1/models (type: "video"); rate changes are announced on News seven days ahead.

AP is held when the job starts and returned in full if the job fails or times out. A failed generation costs nothing. When the model decides the length, it holds the maximum and returns the difference once the video is done. You can have three jobs running at once per key.

Scope​

Video needs the video scope. It is not included in older keys: a key you issued for images alone was never meant to spend at video rates. Add it from your keys page, or issue a new key, which gets every released public scope.

Calling without it returns 403 forbidden_scope with required_scope: "video".

Characters​

A registered character needs nothing but its name.

{ "prompt": "@SakiNoire walks through a neon backstreet at night", "seconds": 5 }

Your own character needs one picture of them, exactly as in the Character API.

{ "character": "luc4", "reference_image": "<base64>", "prompt": "sitting at a cafe, typing on a laptop" }

LuC4 generated from a single reference image

The same reference that produces this still is what keeps the character stable across frames. Put the character's official appearance words in front of the scene; the reference sets the overall look and the words hold the details.

Making a good first frame​

A video is only as good as the image it starts from, and the Image API is right here. One technique that works well: ask for a contact sheet, then hand the sheet to the video model.

curl -X POST https://api.aicu.ai/v1/images/generations \
-H "Authorization: Bearer $AICU_API_KEY" -H "Content-Type: application/json" \
-d '{"model":"gpt-image-2.5-flare","size":"1024x1536","quality":"medium",
"prompt":"A 3x3 contact sheet of snapshots a child took on a phone, 9:16. Low angles, feet, the dog’s nose up close, the sky, a palm, a parent seen from below, a toy on the floor. Everyday and lived-in. No text or logos."}'

Asking for a child's photos gives you low angles, because that is where a child's eyes are. Asking for amateur failures gives you camera shake, a thumb over the lens and blown-out backlight. You did not have to describe any of that.

A child's photosAmateur failuresA character's own snapshots
contact sheet, a child&#39;s photoscontact sheet, amateur failurescontact sheet, a character&#39;s snapshots

Each cell of the sheet is a usable first frame. Pass one as reference_image and describe the motion in prompt.

Errors​

StatusCodeWhat to do
400invalid_parameterCheck model, resolution, seconds, or the size of the images
402insufficient_apTop up. Nothing was started.
403forbidden_scopeAdd the video scope to the key
429rate_limitedThree jobs per key are already running. Wait for one to finish.
5xxgeneration_failedRetry. The held AP was already returned.

Support​

© 2026 AICU Inc.