Video generations
Generate video from a text prompt, optionally guided by reference images, video or audio.
Video generation is asynchronous. You submit a job, receive an id immediately, then poll until the video is ready. It is billed on the real usage reported for the finished job, not a fixed per-video price — see Billing.
POST https://api.ecohash.com/v1/video/generations
Generation modes
A request is in exactly one of four modes, chosen by what you put in
reference_images. The modes cannot be combined — a request mixing them is
rejected with 400.
| Mode | You supply | Control you get |
|---|---|---|
| Text to video | prompt only | The model composes everything |
| First frame | prompt + 1 image, role: "first_frame" | The video opens on your image |
| First and last frames | prompt + 2 images, first_frame and last_frame | Both ends pinned; the model fills the middle |
| Multimodal reference | prompt + 1–9 images, role: "reference_image" (± video, ± audio) | Images guide style and subject; no frame is pinned |
The distinction between the last two matters. Reference images influence the whole clip but do not pin any frame. If you need the video to begin or end on a specific image, use the frame roles — describing it in the prompt is not equivalent.
Quick start
curl https://api.ecohash.com/v1/video/generations \
-H "Authorization: Bearer eco_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "ecolink-video-gen-2.0",
"prompt": "a red balloon drifting over a mountain range at dawn",
"resolution": "720p",
"duration": 5,
"aspect_ratio": "16:9"
}'
Parameters
Required
| Field | Type | Notes |
|---|---|---|
model | string | ecolink-video-gen-2.0 |
prompt | string | What to generate. Required in every mode, including when you supply reference media |
Prompt language — English, Japanese, Indonesian, Spanish and Portuguese. Keep it under about 1000 words. Longer prompts scatter the model's attention: it starts tracking the main subjects and dropping the details, so elements you asked for go missing.
Output controls
| Field | Type | Default | Valid values |
|---|---|---|---|
resolution | string | 720p | 480p, 720p, 1080p, 4k |
duration | integer | 5 | 4–15 (seconds) |
aspect_ratio | string | adaptive | 16:9, 4:3, 1:1, 3:4, 9:16, 21:9, adaptive |
generate_audio | boolean | true | true, false |
resolution sets the output size and the pricing band, so it is the single
parameter with the largest effect on cost. 480p and 720p share a band;
1080p and 4k each have their own. 4k output is 10-bit H.265 — richer
colour, but some players will not open it.
duration is clamped, not rejected: below 4 becomes 4, above 15
becomes 15. You get a video rather than an error, so if the length surprises
you, check what you sent. Duration is a direct input to the bill.
aspect_ratio defaults to adaptive, which picks a ratio to suit the
request — from the prompt for text-only jobs, from the first reference image or
video when you supply one. The ratio actually used is returned on the finished
job. If your chosen ratio does not match a reference image, the image is cropped
from the centre.
generate_audio is on by default — the model composes speech, effects
and background to match the prompt and picture. Set false for silent video.
For spoken lines, put the words in double quotes in your prompt, e.g. the captain turns and says "we sail at dawn". Generated audio is mono regardless of
any reference audio you supply.
Reference media
| Field | Type | Notes |
|---|---|---|
reference_images | object[] | Up to 9. Each entry is { "url": "...", "role": "..." } |
reference_image_urls | string[] | Shorthand: a plain URL list, all treated as reference_image |
reference_video_urls | string[] | Up to 3 reference videos |
reference_audio_urls | string[] | Up to 3 reference audio tracks |
reference_video_url | string | A single reference video — shorthand for a one-element reference_video_urls |
reference_audio_url | string | A single reference audio track — shorthand for a one-element reference_audio_urls |
The singular and plural forms are mutually exclusive for a given kind —
send reference_video_url or reference_video_urls, not both. Likewise
reference_images and reference_image_urls. Sending both returns 400.
When you supply several videos or several audio clips, the combined duration of each kind must stay within 15 seconds — see Input media requirements.
Image roles
role | Meaning | Count |
|---|---|---|
first_frame | The video opens on this image | 1 |
last_frame | The video ends on this image. Requires a first_frame | 1 |
reference_image | Guides style and subject; pins nothing | 1–9 |
| (omitted) | Treated as reference_image | — |
Rules, all enforced before the job is created:
reference_imagecannot be mixed withfirst_frameorlast_frame.last_framewithout afirst_frameis rejected.- At most one
first_frameand onelast_frame. first_frame/last_framecannot be combined with reference video or audio.- Reference audio needs at least one image or a reference video — audio cannot be the only reference.
The first and last frame may be the same image. If the two have different aspect ratios, the first frame wins and the last is cropped to match.
Omitting role means reference_image, never first_frame. If you want the
opening frame pinned, say so explicitly.
Accepted but not used by this model
These belong to the shared video request shape and are accepted without error,
but ecolink-video-gen-2.0 does not act on them. Listed so you can tell a
silently-ignored field from one that is not working.
| Field | What happens |
|---|---|
seed | Ignored. This model has no seeded output; identical requests will differ |
num_frames | Only derives duration (num_frames ÷ fps) when duration is omitted. Never sets frame count |
fps | Only derives duration, as above. Does not set the output frame rate |
width, height | Ignored. Use resolution and aspect_ratio |
num_inference_steps | Ignored |
Watermarking is off for all generated video and cannot be enabled.
Input media requirements
All reference media is supplied as a publicly downloadable URL — the generator fetches it directly. Base64 payloads are not accepted.
These are checked when the job is submitted, so a file that breaks a rule comes
back immediately with a code of reference_media_unreachable or
reference_media_too_long.
Images
| Formats | JPEG, PNG, WebP, BMP, TIFF, GIF, HEIC, HEIF |
| Dimensions | 300–6000 px on each side |
| Aspect ratio | between 0.4 and 2.5 (width ÷ height) |
| Size | 30 MB per image |
Video
| Container | MP4 (.mp4), QuickTime (.mov) |
| Video codec | H.264/AVC, H.265/HEVC |
| Audio codec | AAC, MP3 |
| Resolution | 480p, 720p, 1080p, 4k |
| Duration | 2–15 seconds each; with several videos, 15 seconds combined |
| Count | up to 3 |
| Dimensions | 300–6000 px per side; total pixels between 409,600 and 8,295,044 |
| Aspect ratio | between 0.4 and 2.5 |
| Frame rate | 24–60 fps |
| Size | 200 MB each |
Audio
| Formats | WAV, MP3 |
| Duration | 2–15 seconds each; with several clips, 15 seconds combined |
| Count | up to 3 |
| Size | 15 MB each |
Output dimensions
resolution and aspect_ratio together determine the exact pixel size:
| Resolution | 16:9 | 4:3 | 1:1 | 3:4 | 9:16 | 21:9 |
|---|---|---|---|---|---|---|
| 480p | 864×496 | 752×560 | 640×640 | 560×752 | 496×864 | 992×432 |
| 720p | 1280×720 | 1112×834 | 960×960 | 834×1112 | 720×1280 | 1470×630 |
| 1080p | 1920×1080 | 1664×1248 | 1440×1440 | 1248×1664 | 1080×1920 | 2206×946 |
| 4k | 3840×2160 | 3326×2494 | 2880×2880 | 2494×3326 | 2160×3840 | 4398×1886 |
With aspect_ratio: "adaptive" the model picks a row; the finished job reports
which.
Examples
First frame
The video opens on your image and animates forward.
curl https://api.ecohash.com/v1/video/generations \
-H "Authorization: Bearer eco_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "ecolink-video-gen-2.0",
"prompt": "the boat pulls away from the dock as gulls scatter",
"resolution": "1080p",
"duration": 6,
"reference_images": [
{ "url": "https://example.com/harbour.jpg", "role": "first_frame" }
]
}'
First and last frames
Both ends are pinned; the model generates the transition between them.
curl https://api.ecohash.com/v1/video/generations \
-H "Authorization: Bearer eco_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "ecolink-video-gen-2.0",
"prompt": "slow push in from the wide harbour view to the lighthouse at dusk",
"resolution": "1080p",
"aspect_ratio": "16:9",
"duration": 8,
"reference_images": [
{ "url": "https://example.com/harbour-wide.jpg", "role": "first_frame" },
{ "url": "https://example.com/lighthouse.jpg", "role": "last_frame" }
]
}'
Multimodal reference
Several images guide subject and style; no frame is pinned. A reference video can supply motion, and reference audio can supply timing.
curl https://api.ecohash.com/v1/video/generations \
-H "Authorization: Bearer eco_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "ecolink-video-gen-2.0",
"prompt": "a documentary shot of the same harbour in winter, muted colour grade",
"resolution": "720p",
"duration": 10,
"reference_images": [
{ "url": "https://example.com/style-1.jpg", "role": "reference_image" },
{ "url": "https://example.com/style-2.jpg", "role": "reference_image" }
],
"reference_video_urls": ["https://example.com/camera-move.mp4"],
"reference_audio_urls": ["https://example.com/ambient-timing.mp3"]
}'
Each of reference_video_urls and reference_audio_urls takes up to 3. The
singular reference_video_url / reference_audio_url still work as a one-item
shorthand, but prefer the list form.
The shorthand form is equivalent when every image is reference material:
"reference_image_urls": ["https://example.com/style-1.jpg", "https://example.com/style-2.jpg"]
Silent, vertical, cheapest band
curl https://api.ecohash.com/v1/video/generations \
-H "Authorization: Bearer eco_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "ecolink-video-gen-2.0",
"prompt": "rain running down a window, neon reflections",
"resolution": "480p",
"aspect_ratio": "9:16",
"duration": 4,
"generate_audio": false
}'
Response
202 Accepted — the job is queued. No video yet.
{
"id": "b3f1c2a4-...",
"status": "processing",
"model": "ecolink-video-gen-2.0"
}
Poll for completion
GET https://api.ecohash.com/v1/video/jobs/{id}
status moves queued → processing → completed, or ends at failed.
Generation takes minutes; poll every few seconds rather than continuously.
{
"id": "b3f1c2a4-...",
"status": "completed",
"model": "ecolink-video-gen-2.0",
"width": 1280,
"height": 720,
"created_at": "2026-07-14T09:00:00Z",
"completed_at": "2026-07-14T09:01:12Z",
"url": "https://.../b3f1c2a4.mp4",
"billing": {
"charge_usd": 0.42,
"pricing_tier": "480_720_no_video",
"resolution": "720p",
"duration_seconds": 5,
"billable_tokens": 100000,
"usage_source": "upstream_reported"
}
}
url is a temporary download link valid for 30 minutes. Fetch and store the
file if you need it longer; polling again issues a fresh link.
On failure, status is "failed" and the job carries three fields describing
what went wrong:
{
"id": "b3f1c2a4-...",
"status": "failed",
"model": "ecolink-video-gen-2.0",
"created_at": "2026-07-14T09:00:00Z",
"completed_at": "2026-07-14T09:00:48Z",
"error": "The generated audio was flagged by the content-safety filter — please try a different prompt, or turn off audio generation.",
"code": "content_filtered.output_audio",
"retryable": false
}
| Field | Use it for |
|---|---|
code | Branching in your client — a stable value, listed under Errors |
retryable | Whether resubmitting the same request could succeed |
error | Showing a person what happened. Wording changes; do not match on it |
Failed jobs are not billed.
Estimate cost first
POST https://api.ecohash.com/v1/video/quote
Takes the same body as a submit and returns the pricing band and an estimated range without generating anything.
{
"pricing_tier": "480_720_no_video",
"unit_price_per_1m_tokens": 7.14,
"estimated_tokens_min": 85000,
"estimated_tokens_max": 115000,
"estimated_cost_min_usd": 0.607,
"estimated_cost_max_usd": 0.821,
"currency": "USD",
"note": "Final charge is based on actual tokens reported by the provider."
}
Billing
A finished job is charged on the tokens reported for it, at a rate set by two things:
| Resolution band | 480p/720p · 1080p · 4k |
| Reference video | whether reference_video_url was supplied |
Six rates in total. Supplying a reference video uses a lower per-token rate than generating from a prompt alone.
Because the charge follows real usage, a quote is a range rather than a fixed
price. duration and resolution move it most.
For current rates call GET /platform/models/ecolink-video-gen-2.0/pricing,
which returns the live per-band figures — read them rather than hard-coding.
Failed jobs are never charged.
Limits
- 10 submissions per 60 seconds per account for this model. Over it returns
429. - Failed submissions do not count, so a rejected request can be retried immediately.
- Up to 3 reference videos and 3 reference audio tracks per request, each kind capped at 15 seconds combined.
- Reference media must be a URL; base64 payloads are not accepted.
Errors
A failure — whether it comes back from the submit call or shows up later on a
polled job — carries a code. Branch on that. The error text is written for
people and is reworded whenever we can explain something better, so matching on
it will break.
{
"error": "A reference image was rejected by the content-safety filter — please replace it and try again. It may contain an identifiable real person.",
"code": "content_filtered.input_image"
}
Error codes
code | HTTP | Retry? | What to change |
|---|---|---|---|
invalid_request | 400 | no | A parameter is missing or out of range. Check resolution, aspect ratio and duration against Parameters. |
reference_media_unreachable | 400 | no | A reference URL could not be fetched, or the file was not readable media. It must be publicly downloadable. |
reference_media_too_long | 400 | no | A reference clip's duration is out of range — each audio or video must be 2–15 seconds, and several of one kind must total ≤15 seconds. |
content_filtered.input_text | 400 | no | Your prompt was rejected. Rephrase it. |
content_filtered.input_image | 400 | no | A reference image was rejected. Replace it. |
content_filtered.input_video | 400 | no | A reference video was rejected. Replace it. |
content_filtered.input_audio | 400 | no | A reference audio clip was rejected. Replace it. |
content_filtered.output_text | 400 | no | Generated text was flagged. Try a different prompt. |
content_filtered.output_image | 400 | no | A generated image was flagged. Try a different prompt. |
content_filtered.output_video | 400 | no | The generated video was flagged. Try a different prompt. |
content_filtered.output_audio | 400 | no | The generated audio was flagged. Try a different prompt, or set generate_audio to false. |
capacity_exceeded | 429 | yes | Generation capacity is saturated. Wait and retry. |
generation_timeout | 504 | yes | Generation ran past its deadline. Retry, or reduce duration / resolution. |
internal_error | 502 | yes | A temporary fault. Retry. |
model_unavailable | 502 / 503 | no | The model cannot serve requests right now. This one is ours, not yours — retrying the same request will not help. If it persists, contact support. |
The HTTP column applies when the error comes back from the submit call. A job
that fails during generation returns 200 on the poll with status: "failed" —
there the code is the only signal, which is why it is worth branching on.
Two shortcuts worth building in:
content_filtered.prefix — match the prefix to handle every content-safety rejection in one branch, and the exact code when you want to tell a user which file to swap. New modalities added later keep working with a prefix match.retryable— polled jobs also return a booleanretryable, so you can drive backoff from it without keeping a list of codes in your client.
Where errors surface
Your job is submitted for generation as part of your request, so problems with the request itself — a missing parameter, an unreachable reference URL, an over-long clip — come back immediately rather than as a failure discovered minutes later.
Content-safety rejections can happen at either point. A reference file is
checked on submit; generated output can only be checked once it exists, so
content_filtered.output_* always arrives on a polled job.
A job accepted with 202 can still end at status: "failed". Check the polled
job rather than assuming acceptance means success. Failed jobs are not billed.
Authentication and account errors are not video-specific: 401 for a missing or
invalid API key, 402 for insufficient credits, 404 for an unknown model. See
Errors for those.
Related
- Video generation guide — worked examples
- Billing overview — how usage charges appear
- API keys