# Gemini Omni 1.2 Flash — /videos/generations

Gemini Omni 1.2 Flash is the video generation and editing model Google released in November 2026. Submit an async job with `POST /v1/videos/generations`: no image for text-to-video, one image as the first frame, two images as first + last frame, 1-3 reference images, or a 1-15 s video to edit it. 720P / 1080P / 4K, 16:9 or 9:16, with sound and no visible watermark. Billed per second; edits by the source seconds.

**Endpoint:** `POST https://kovapi.com/v1/videos/generations`

## Quick start

Three steps: submit a job, poll it, take the video link. Replace YOUR_API_KEY with your key and the commands below run as they are.

**Step 1 · Submit (returns a job id at once, HTTP 202)**

```
curl https://kovapi.com/v1/videos/generations \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "gemini-omni-1.2-flash", "prompt": "a paper boat drifts down a rainy neon street at dusk while a child in a yellow raincoat runs after it laughing", "duration": 10, "resolution": "720P", "size": "16:9"}'
```

Response (HTTP 202)

```
{
  "id": "img_5c2e8f1a9b3d7e6f0a4c8b2d",
  "object": "video.generation.job",
  "status": "queued",
  "created": 1791281400,
  "model": "gemini-omni-1.2-flash"
}
```

**Step 2 · Poll every 5–10 seconds until `status` is `succeeded` or `failed`**

```
curl https://kovapi.com/v1/videos/jobs/img_5c2e8f1a9b3d7e6f0a4c8b2d \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Response when it succeeds

```
{
  "id": "img_5c2e8f1a9b3d7e6f0a4c8b2d",
  "object": "video.generation.job",
  "status": "succeeded",
  "created": 1791281400,
  "model": "gemini-omni-1.2-flash",
  "data": [
    {"url": "https://img.kovapi.com/i/8c1f0e5d2b7a49e3a6d0c4f1b9e2a7d5.mp4"}
  ],
  "usage": {"clips": 1, "model": "gemini-omni-1.2-flash"}
}
```

**Step 3 · `data[0].url` is the clip (MP4). It is kept for 7 days; download it to your own storage to keep it longer.**

**Complete example (Python: submit, wait, save to disk)**

```
import time
import requests

BASE = "https://kovapi.com/v1"
HEADERS = {"Authorization": "Bearer YOUR_API_KEY"}

# 1. submit
job = requests.post(f"{BASE}/videos/generations", headers=HEADERS, json={
    "model": "gemini-omni-1.2-flash",
    "prompt": "a paper boat drifts down a rainy neon street at dusk while a child in a yellow raincoat runs after it laughing",
    "duration": 10,
    "resolution": "720P",
    "size": "16:9",
}).json()
if "error" in job:
    raise SystemExit(job["error"]["message"])

# 2. poll every 10 seconds, for up to an hour
deadline = time.time() + 3600
while job["status"] not in ("succeeded", "failed") and time.time() < deadline:
    time.sleep(10)
    job = requests.get(f"{BASE}/videos/jobs/{job['id']}", headers=HEADERS).json()

# 3. download the video
if job["status"] == "succeeded":
    url = job["data"][0]["url"]
    with open("gemini-omni-1.2-flash.mp4", "wb") as f:
        f.write(requests.get(url).content)
    print("saved", url)
elif job["status"] == "failed":
    print("failed:", job["error"]["code"], job["error"]["message"])
else:
    print("still", job["status"], job["id"])
```

While queued, the poll also returns `queue_position` (jobs ahead of yours) and `eta_seconds` (expected wait). If your time limit passes, do not resubmit: the job is still there, so keep polling the same id later.

## Authentication
```
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
```

## At a glance

| Duration | 3-15 s in one-second steps (default 10) · first / first + last frame 3-10 s · edits follow the source (1-15 s) |
| --- | --- |
| Resolution | 720P / 1080P / 4K |
| Aspect ratio | 16:9 / 9:16 |
| Reference images | first frame 1 · first + last frame 2 · references 1-3 · up to 5 references for an edit |
| Video edit | Video edit: one source video, 1-15 s, mp4 / mov, up to 50 MB · billed by the source seconds |
| Reference audio | not supported |
| Sound | Always on (describe dialogue, music and effects in the prompt); cannot be switched off (`audio: false` is refused) |
| Prompt length | up to 20,000 characters (Chinese and English count the same) |
| Billing | Per second: the per-second rate of the chosen resolution x the length (for edits, the source seconds) |

## Request parameters

The JSON body of `POST /v1/videos/generations`:

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| model | string | Yes | The model id: `gemini-omni-1.2-flash`. |
| prompt | string | Yes | Describes the video, camera, action and sound, up to 20,000 characters (Chinese and English count the same). Name several references as @Image1 / @Image2; for an edit, say what to change, e.g. "make it night, keep everything else the same". negative_prompt is not supported: write what you do not want into the prompt. |
| duration | integer | No | Length: an integer from 3 to 15 seconds (default 10); alias `seconds`. First / first + last frame mode: 3-10 s; for a video edit leave it out, the output follows the source length. Billed per second, see Pricing below. |
| resolution | string | No | Resolution: 720P (default) / 1080P / 4K (1080P and 4K are the official upscale; 4K portrait is 2160x3840). 720p, 1080p, 4k and 2160p are accepted too. 360P is not available. |
| size | string | No | Aspect ratio: 16:9 (default) / 9:16; alias aspect_ratio. Other ratios are refused. For an edit, omitted means the orientation of the source. |
| images | array | No | Several images (up to 16 MB each): 2-3 are references by default (3 at most); with image_role=first_frame two are first + last frame; a video edit takes up to 5 references. image_urls and image_with_roles ([{"url": ..., "role": "first_frame" \| "last_frame" \| "reference"}]) are accepted too. Over the cap is refused, never truncated. One image (public URL, data: URI or base64): the first frame by default (3-10 s); with image_role=reference it is a reference. first_frame_image and last_frame_image are accepted too. |
| image_role | string | No | `first_frame` (first / first + last frame, 3-10 s) or `reference` (1-3 references). Omitted: one image is the first frame, 2-3 are references. Other common spellings are accepted too: `first_frame_image`, `last_frame_image`, `image_urls`, `image_with_roles`, `omni_reference_task_type`. |
| video_urls | array | No | Video edit: one 1-15 s video (mp4 / mov, up to 50 MB; a public link, data:video/mp4;base64,... or a Playground upload); alias video_url / video_references. The output follows the source length and is billed by the source seconds; a part past the last whole second is cut (4.5 s is edited and billed as 4 s). Cannot be combined with first / first + last frame; continuation (extend) is not available. |
| audio | boolean | No | This model always generates sound (describe dialogue, music and effects in the prompt); the parameter can be omitted (false is rejected). |
| callback_url | string | No | Where to push the result when the job ends; see Webhooks below. |
| n | integer | No | Each job makes one clip; leave it out. A value above 1 still makes one clip, but n clips are held at submit and the extra is returned when the job ends. |

Other fields (`seed`, `negative_prompt`, `fps`, `watermark` and the like) are not passed on and do not change the price; results are always MP4, returned as a `url`.

## Price

Billed per second: cost = the per-second rate of the chosen resolution x the length. A video edit is billed by the source seconds (a part past the last whole second is cut and not billed). Reference images are free. That amount is held at submit and settled on success; failures are refunded in full. 1 credit = $0.005.

| Resolution | Credits / s | USD / s | A 10-second clip | Edit of an 8 s source |
| --- | --- | --- | --- | --- |
| 720P | 7 | $0.035 | $0.35 | $0.28 |
| 1080P | 10 | $0.05 | $0.50 | $0.40 |
| 4K | 20 | $0.10 | $1.00 | $0.80 |

## Reference material

- **First / first + last frame**: one image in `image` is the first frame; two in `images` with `image_role: first_frame` are first + last frame. Up to 10 seconds. Match the orientation of the images to `size`.

- **References**: 1-3 images in `images` with `image_role: reference` (2-3 images are references by default); the people, objects or settings appear in the clip without becoming a frame. Name them @Image1 / @Image2 in the prompt.

- **Video edit**: one 1-15 s video in `video_urls`, and a prompt saying what to change, e.g. "make it night under the moon, keep everything else the same"; up to 5 reference images can come along ("replace the person with the one in @Image1"). We download the source at submit and measure it: unreadable, under 1 s or over 15 s returns 400, not charged.

- Not available yet: 360P, continuation (extend), reference video / audio input, negative_prompt (write what you do not want into the prompt).

- Each image is a public image URL, a `data:` URI or base64, up to 16 MB; send several as an array in `images`. We store the images before the job is queued; one that cannot be read is refused with 400, `message` says which, and nothing is charged.

- Reference files we store and the results are both kept for 7 days.

**Video edit: change the setting and swap in the person from a reference image**

```
curl https://kovapi.com/v1/videos/generations \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "gemini-omni-1.2-flash", "prompt": "make it a moonlit night and replace the person in the centre with the one in @Image1, keep everything else the same", "video_urls": ["https://example.com/clip-8s.mp4"], "images": ["https://example.com/person.png"], "resolution": "720P"}'
```

The output follows the source length; an 8 s source is billed as 8 s, the reference image is free.

## Job status and fields

`GET /v1/videos/jobs/{id}` reports these statuses; every field is described in [Async jobs](https://docs.kovapi.com/en/guide/async-jobs#fields). Poll every 5–10 seconds, or let a webhook (below) push the result. Polling is free, and any key of the same account can read the job.

| Status | Meaning |
| --- | --- |
| queued | Waiting in line. Carries `queue_position` (jobs ahead of yours) and `eta_seconds` (expected wait). |
| processing | Rendering. |
| succeeded | Done. The result is in `data[0].url`, the billing detail in `usage`. |
| failed | Failed. The reason is in `error`; the credits held for the job are refunded in full. |

- `processing` can go back to `queued`, for example while the service is busy, while a finished clip is fetched again, or after a restart. If a job cannot be finished, the gateway may render it again automatically; however many retries it takes, a job is charged once.

- One account runs up to 50 jobs at a time; further jobs wait in submission order, and a job that has not started after 15 minutes fails and is refunded in full. Job records are kept for 3 days and video links for 7 days.

## How long it takes

Our tests: 3-6 s at 720P about 1 minute, 13 s at 1080P about 2.5 minutes, 4K about 3 minutes, edits about 1.5 minutes; longer in a queue, up to 20 minutes, then fully refunded.

## Errors and refunds

An error at submit comes back at once as HTTP 4xx / 5xx: no job is created and nothing is charged. A failure after the job was accepted is reported by the job endpoint, and the hold is refunded in full. Every code is listed under [Error codes](https://docs.kovapi.com/en/guide/errors).

### Errors at submit

| HTTP | code | Cause | What to do |
| --- | --- | --- | --- |
| 400 | invalid_request | A parameter is not valid: the duration, resolution, ratio or number of references is outside this model's range, the prompt is too long, a reference image or video cannot be read, `audio: false` was sent to a model whose clips always have sound, and so on. `message` says which. | Fix what `message` says and submit again. |
| 400 | invalid_source_video | The source video was not accepted: its length could not be read, it is under 1 s or over 15 s, or the file is damaged. | Send a playable 1-15 s mp4 / mov instead; this request was not charged. |
| 400 | invalid_callback_url | `callback_url` is not a public http(s) address, or is longer than 2000 characters. | Use an address that can be reached from the internet. |
| 400 | model_not_found | The model id is misspelled, `model` is missing, or the model cannot be used on this endpoint (a chat model on the image endpoint, for example). | Use a model id from `GET /v1/models` or the [API reference](https://docs.kovapi.com/en/api). |
| 401 | invalid_api_key | The key does not exist, was deleted or is disabled, or the account is disabled. | Check the key on the API Keys page of the console. |
| 402 | insufficient_quota | Not enough balance to hold this request. The gateway holds the highest possible cost of the request first, so a burst of concurrent calls can trigger it too. | After a burst, wait a few seconds and retry; otherwise top up. |
| 429 | rate_limit_exceeded | Over the requests-per-minute limit set on this key (counted over the last 60 seconds). No `Retry-After` header. | Wait a few seconds and retry, or raise the key's limit in the console. |
| 503 | model_maintenance | The model is under maintenance and takes no requests; no other model answers in its place meanwhile. | Retry when maintenance ends; see [Status](https://kovapi.com/en/status). |
| 503 | service_unavailable | A temporary failure on our side, such as storing a reference file. No job was created and nothing was charged. | Retry shortly. |

### Failed jobs (`status` `failed`)

| code | When you see it | What to do |
| --- | --- | --- |
| content_policy | The prompt or a reference image did not pass the model provider's content review. | Rewrite the prompt or replace the reference before you submit again; the same input is refused again. |
| invalid_request | The model provider does not accept this combination (a mode and a resolution, for example). `message` says why. | Adjust the parameters as `message` says and submit again. |
| wait_timeout | Video: no result within 120 minutes, so the job timed out and its credits were refunded in full; if the video finishes later, it is delivered free. | Submit again. |
| upstream_error | Any other failure: the automatic retries did not help, the job waited 15 minutes without starting, the upstream refused without a reason, and so on; `message` says which. | Submitting again usually works. |

Branch on `code`, not on the text of `message`. Treat any `code` you do not recognise (or a missing one) as `upstream_error`: resubmitting usually works. Every failed job is refunded in full.

## Webhooks instead of polling

Add `callback_url` when you submit and the moment the job ends, success or failure, the gateway pushes exactly what the poll endpoint returns to that URL, signed and retried on failure. You can also set an account-wide address under Settings → Webhook in the console. [Webhook docs →](https://docs.kovapi.com/en/webhooks)

```
curl https://kovapi.com/v1/videos/generations \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "gemini-omni-1.2-flash", "prompt": "a paper boat drifts down a rainy neon street at dusk while a child in a yellow raincoat runs after it laughing", "duration": 10, "resolution": "720P", "size": "16:9", "callback_url": "https://your-domain.com/webhook"}'
```
