# Veo 3.1

Veo 3.1 is Google's video generation model. SeedRouter offers it as five model IDs on one endpoint: three priced per clip, every clip 8 seconds long, and two priced per second with more controls (duration, audio, seed, negative prompt, first and last frame). Send the request, keep the returned task ID, and read the finished video from the task. Images go in as URLs.

## Model IDs

| Model ID                   | Billing    | Length            | Images                           | Audio            |
| -------------------------- | ---------- | ----------------- | -------------------------------- | ---------------- |
| `veo-3.1-fast`             | per clip   | 8 seconds         | up to 3, frame or reference mode | no switch        |
| `veo-3.1-quality`          | per clip   | 8 seconds         | up to 3, frame mode              | no switch        |
| `veo-3.1-lite`             | per clip   | 8 seconds         | none (text to video)             | no switch        |
| `veo-3.1-fast-official`    | per second | 4, 6 or 8 seconds | first and last frame             | `generate_audio` |
| `veo-3.1-quality-official` | per second | 4, 6 or 8 seconds | first and last frame             | `generate_audio` |

See the [model page](https://seedrouter.ai/models/veo-3-1#pricing) for current prices.

## Quick example

```bash
curl https://api.seedrouter.ai/v1/videos/generations \
  -H "Authorization: Bearer $SEEDROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "veo-3.1-fast",
    "prompt": "A red paper boat drifts across a calm pond at sunrise, soft mist on the water, slow push-in on a 35mm lens, no text, no logos.",
    "resolution": "720p",
    "aspect_ratio": "16:9"
  }'
```

```python
import os
import requests

response = requests.post(
    "https://api.seedrouter.ai/v1/videos/generations",
    headers={"Authorization": f"Bearer {os.environ['SEEDROUTER_API_KEY']}"},
    json={
        "model": "veo-3.1-fast",
        "prompt": "A red paper boat drifts across a calm pond at sunrise, soft mist on the water, slow push-in on a 35mm lens, no text, no logos.",
        "resolution": "720p",
        "aspect_ratio": "16:9",
    },
    timeout=60,
)
response.raise_for_status()
task_id = response.json()["id"]
```

```javascript
const response = await fetch('https://api.seedrouter.ai/v1/videos/generations', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SEEDROUTER_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    model: 'veo-3.1-fast',
    prompt: 'A red paper boat drifts across a calm pond at sunrise, soft mist on the water, slow push-in on a 35mm lens, no text, no logos.',
    resolution: '720p',
    aspect_ratio: '16:9',
  }),
});
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
const { id: taskId } = await response.json();
```

```go
package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "os"
    "strings"
    "time"
)

func main() {
    body := strings.NewReader(`{
      "model": "veo-3.1-fast",
      "prompt": "A red paper boat drifts across a calm pond at sunrise, soft mist on the water, slow push-in on a 35mm lens, no text, no logos.",
      "resolution": "720p",
      "aspect_ratio": "16:9"
    }`)
    req, err := http.NewRequest("POST", "https://api.seedrouter.ai/v1/videos/generations", body)
    if err != nil { panic(err) }
    req.Header.Set("Authorization", "Bearer " + os.Getenv("SEEDROUTER_API_KEY"))
    req.Header.Set("Content-Type", "application/json")
    client := &http.Client{Timeout: 60 * time.Second}
    res, err := client.Do(req)
    if err != nil { panic(err) }
    defer res.Body.Close()
    if res.StatusCode != http.StatusOK { panic(res.Status) }
    var task struct { ID string `json:"id"` }
    if err := json.NewDecoder(res.Body).Decode(&task); err != nil { panic(err) }
    fmt.Println(task.ID)
}
```

## Endpoint

```text
POST https://api.seedrouter.ai/v1/videos/generations
```

| Header        | Value                 |
| ------------- | --------------------- |
| Authorization | `Bearer YOUR_API_KEY` |
| Content-Type  | `application/json`    |

The response is a task (`{"id": "task_...", "status": "processing"}`), not the finished video. Poll `GET /v1/tasks/{task_id}` for the result. Keep API keys in server-side code.

## Parameters: per-clip models

`veo-3.1-fast`, `veo-3.1-quality` and `veo-3.1-lite`.

| Field             | Type    | Default        | Notes                                                                      |
| ----------------- | ------- | -------------- | -------------------------------------------------------------------------- |
| `model`           | string  | required       | One of the three IDs above.                                                |
| `prompt`          | string  | required       | Describes the shot.                                                        |
| `duration`        | integer | `8`            | Only `8` is accepted.                                                      |
| `aspect_ratio`    | enum    |                | `16:9` or `9:16`.                                                          |
| `resolution`      | enum    | `720p`         | `720p`, `1080p` or `4k` (any case). `veo-3.1-lite` has no `4k`.            |
| `enable_gif`      | boolean | `false`        | Return the clip as an animated GIF instead of MP4. `720p` only.            |
| `nsfw_check`      | boolean | `false`        | Check the prompt and images for unsafe content before generating.          |
| `image_urls`      | array   |                | Fast and Quality only. Up to 3 public image URLs.                          |
| `generation_type` | enum    | by image count | Fast and Quality only. `frame` or `reference`; Quality takes `frame` only. |

## Parameters: per-second models

`veo-3.1-fast-official` and `veo-3.1-quality-official`.

| Field               | Type    | Default       | Notes                                                             |
| ------------------- | ------- | ------------- | ----------------------------------------------------------------- |
| `model`             | string  | required      | One of the two IDs above.                                         |
| `prompt`            | string  | required      | Describes the shot.                                               |
| `negative_prompt`   | string  |               | What to keep out of the clip.                                     |
| `duration`          | integer | `8`           | `4`, `6` or `8` seconds.                                          |
| `aspect_ratio`      | enum    | `16:9`        | `16:9` or `9:16`.                                                 |
| `resolution`        | enum    | `720p`        | `720p`, `1080p` or `4k` (any case).                               |
| `first_frame_image` | string  |               | Public image URL. The clip opens on it.                           |
| `last_frame_image`  | string  |               | Public image URL. Needs `first_frame_image`.                      |
| `seed`              | integer | random        | 0 to 4294967295.                                                  |
| `generate_audio`    | boolean | `false`       | Add an audio track. Billed at a higher per-second rate.           |
| `person_generation` | enum    | `allow_adult` | `allow_adult` or `disallow`.                                      |
| `resize_mode`       | enum    | `pad`         | `pad` or `crop`. Needs `first_frame_image`.                       |
| `enhance_prompt`    | boolean | `true`        | Only `true` is accepted; omit the field otherwise.                |
| `nsfw_check`        | boolean | `false`       | Check the prompt and images for unsafe content before generating. |

The schema is strict: unknown fields are rejected rather than ignored, and each model takes only its own fields. Callbacks are not available; poll the task instead.

## Image modes

On `veo-3.1-fast` and `veo-3.1-quality`, `generation_type` sets how `image_urls` are used:

| `generation_type` | Images  | Effect                                                          |
| ----------------- | ------- | --------------------------------------------------------------- |
| `frame`           | 1 or 2  | The first image is the first frame, the second the last frame.  |
| `reference`       | up to 3 | The images are references for the subject and style. Fast only. |
| omitted           | 2 or 3  | Two images use frame mode, three use reference mode.            |

`veo-3.1-quality` does not run reference mode, so it refuses `generation_type: "reference"` and three images without a `generation_type`. `veo-3.1-lite` takes no images.

On the per-second models, set `first_frame_image` and, optionally, `last_frame_image`. `resize_mode` chooses whether an image of another shape is padded or cropped.

## Media inputs

Images are public HTTP(S) URLs:

```json
{ "image_urls": ["https://example.com/first.jpg", "https://example.com/last.jpg"] }
```

On the per-clip models each image is JPEG, PNG or WebP and at most 10 MB; a file that breaks these rules fails the task without a charge. Base64 data is not accepted: upload the file to your own storage and pass its URL.

## Pricing dimensions

Check the [model pricing section](https://seedrouter.ai/models/veo-3-1#pricing) for current rates.

```text
per-clip models:    cost = price of one clip at the output resolution        (720p and 1080p cost the same)
per-second models:  cost = duration × rate for the resolution and audio setting
```

The charge is fixed when the request is accepted, so the amount reserved is the amount charged. View final charges in your account usage history. Failed tasks are not charged.

## Output schema

Submission returns the task:

```json
{"id": "task_...", "model": "veo-3.1-fast", "status": "processing", "created_at": 1789689600}
```

### Get the task

```text
GET https://api.seedrouter.ai/v1/tasks/{task_id}
```

Poll every 10–20 seconds until `status` is `completed` or `failed`. A network timeout while polling does not mean generation failed: keep the task ID and resume checking it. Do not create another task to check progress.

### Completed task

```json
{
  "id": "task_...",
  "model": "veo-3.1-fast",
  "status": "completed",
  "created_at": 1789689600,
  "finished_at": 1789689720,
  "output": {
    "video_url": "https://static.seedrouter.ai/media/tasks/task_example/0.mp4"
  }
}
```

`video_url` is an MP4, or a GIF when the request set `enable_gif`. The link is on SeedRouter's storage.

What our test runs returned (one run each, 2026-10-04):

| Request                                                         | File                                                               |
| --------------------------------------------------------------- | ------------------------------------------------------------------ |
| `veo-3.1-fast`, `9:16`, frame mode                              | MP4, H.264, 720 × 1280, 24 fps, 8 s, with a stereo AAC audio track |
| `veo-3.1-fast-official`, `16:9`, 720p, 4 s, no `generate_audio` | MP4, H.264, 1280 × 720, 24 fps, 4 s, no audio track                |
| `veo-3.1-lite`, `enable_gif`                                    | GIF, 480 × 270, 16 fps, 8 s                                        |

The per-clip models have no audio switch; the per-second models add an audio track only with `generate_audio`.

## Errors

Requests rejected before a task is created return an HTTP error with an `error` object and are not charged. A task that fails after acceptance returns HTTP 200 when queried, with `status: "failed"` and an `error` object.

See the [shared error catalog](https://seedrouter.ai/docs/api/errors) for codes, HTTP statuses, and retry guidance.

```json
{
  "id": "task_...",
  "model": "veo-3.1-fast",
  "status": "failed",
  "error": {
    "code": 60001,
    "message": "The request was rejected by the content policy. Please revise the prompt or input images."
  }
}
```

If submission itself times out, check your tasks before submitting again: the first request may have been accepted.

## Tips

* Start on `veo-3.1-lite` or `veo-3.1-fast` at 720p to try a prompt, then move to Quality or 4k for the final render.
* Name the camera and the light: a lens and a camera move change the shot more than adjectives do.
* Add `no text, no logos` to keep invented lettering and marks out of the frame.
* For a shot that must start and end on known images, use frame mode with two images, or the per-second models with `first_frame_image` and `last_frame_image`.
* Fix `seed` on the per-second models and change one clause at a time to iterate on a shot.

## Related

* [Veo 3.1 Playground and pricing](https://seedrouter.ai/models/veo-3-1)
* [Download OpenAPI](https://seedrouter.ai/docs/veo-3-1.openapi.json)
* [Copyable Markdown](https://seedrouter.ai/docs/veo-3-1.md)
