Veo 3.1
Generate a video with sound from a prompt or a first frame, in three variants, billed by the output length.
Veo 3.1 (product veo) generates a video from a prompt or a first frame. The output has sound (lite can turn it off). It comes in three variants with different prices and parameters.
mode | What it does |
|---|---|
text_to_video | Text to video: generated from a prompt alone. |
image_to_video | Image to video: starts from a first frame (lite can also end on a last frame). |
Variants
Every task picks one with variant:
variant | What it is | resolution |
|---|---|---|
standard | The best quality. Expands your prompt into a detailed shot description before generating. | 720p, 1080p, 4k |
fast | Cheaper than standard. Expands the prompt like standard. | 720p, 1080p, 4k |
lite | The cheapest. Generates from your prompt as written. The only variant that can add a last frame, turn sound off (generate_audio) and follow the first frame's aspect ratio (aspect_ratio: auto). | 720p |
Each variant has its own price per second, see Pricing. GET /v1/products lists each variant's resolutions in its prices rows.
Create a task
https://api.nanoapi.ai/v1/tasksUse the shared create endpoint with model: "veo-3.1". It usually responds in under 1 second and the cost is frozen within a few seconds; the video usually takes 1–2 minutes. Get the result once the task has finished.
Headers
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer <API key>, see Authentication. |
Content-Type | Yes | application/json |
Idempotency-Key | No | A unique string for every new task, ideally a UUID (1–255 characters). See Preventing duplicate tasks. |
Body
| Parameter | Type | Required | Description |
|---|---|---|---|
model | string | Yes | veo-3.1. |
variant | string | Yes | standard, fast or lite, see Variants. Sets the price. |
mode | string | Yes | text_to_video or image_to_video. |
prompt | string | Yes | What the video shows, at most 8000 characters. Best written in English. |
image_urls | string[] | Yes in image_to_video; not accepted by text_to_video | The first frame; lite can add a last frame as the second image. See Image to video. |
resolution | string | Yes | standard and fast: 720p, 1080p or 4k. lite: 720p. Sets the price. |
aspect_ratio | string | Yes | 16:9 or 9:16. lite in image_to_video also takes auto, which follows the first frame. |
duration | integer | Yes | Output length in seconds: 4, 6 or 8 (8 when lite has a last frame). It's what you pay for, see Pricing. |
generate_audio | boolean | No | lite only: whether the output has sound, default true. Same price either way. standard and fast always have sound, and sending it with them returns 400 invalid_request. |
webhook_url | string | No | Notified when the task succeeds or fails: https, up to 2048 characters, not a private, loopback or reserved address. See Webhooks. |
- Parameter checks: a parameter that breaks the variant's or mode's rules (for example two images with
fast,generate_audiowithstandard,1080pwithlite) returns400 invalid_requestwithparamset to that parameter, and no task is created. So does any other parameter. See Errors. - Prompt: write it in English for the best results.
standardandfastexpand it into a detailed shot description before generating;liteuses it as written.
Response: 200 OK. data is the newly created task, with only these fields; each one is described in Create a task: Response. Keep data.id to get the result. The task can still fail after it's created (for example if your balance can't cover it), so go by what retrieving the task or the webhook tells you.
{
"code": 200,
"message": "success",
"data": {
"object": "task",
"id": "tk_01jbq3v6n9c2x5b8m1q4w7e0r3",
"model": "veo-3.1",
"status": "queued",
"error": null,
"created_at": "2026-11-11T08:30:00Z"
}
}Text to video
Generates the video from prompt alone, with any variant.
standard, 1080p, 16:9, 8 seconds:
curl https://api.nanoapi.ai/v1/tasks \
-H "Authorization: Bearer $NANOAPI_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 3f8a2c71-9d4e-4b6a-8e1f-7c0b5a2d9e46" \
-d '{
"model": "veo-3.1",
"variant": "standard",
"mode": "text_to_video",
"prompt": "A paper boat drifts down a rainy city street gutter, close-up, soft morning light",
"resolution": "1080p",
"aspect_ratio": "16:9",
"duration": 8
}'Image to video
Starts the video from the first image in image_urls; prompt says what happens next.
variant | image_urls | aspect_ratio | duration |
|---|---|---|---|
standard, fast | 1 image: the first frame | 16:9 or 9:16 | 4, 6 or 8 |
lite | 1–2 images: the first frame, then optionally the last frame | 16:9, 9:16 or auto (follows the first frame) | 4, 6 or 8; 8 with a last frame |
Pick the aspect_ratio that matches the first frame: 16:9 for a landscape image, 9:16 for a portrait one (or auto with lite).
fast, first frame only, 1080p, 4 seconds:
curl https://api.nanoapi.ai/v1/tasks \
-H "Authorization: Bearer $NANOAPI_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6b1d9e42-7a3c-4f85-b2e0-1c8f4a6d3b97" \
-d '{
"model": "veo-3.1",
"variant": "fast",
"mode": "image_to_video",
"image_urls": ["https://example.com/first_frame.jpg"],
"prompt": "The cat slowly turns its head, looks at the camera and meows, the camera slowly pushes in",
"resolution": "1080p",
"aspect_ratio": "16:9",
"duration": 4
}'lite with a first and a last frame (so duration is 8), aspect ratio following the first frame:
curl https://api.nanoapi.ai/v1/tasks \
-H "Authorization: Bearer $NANOAPI_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: c4e7a0b3-2f6d-4a19-9c8e-5b3d1f7a2e60" \
-d '{
"model": "veo-3.1",
"variant": "lite",
"mode": "image_to_video",
"image_urls": [
"https://example.com/first_frame.jpg",
"https://example.com/last_frame.jpg"
],
"prompt": "The camera slowly orbits around the cat sitting on the car hood",
"resolution": "720p",
"aspect_ratio": "auto",
"duration": 8
}'Get the result
A video usually takes 1–2 minutes. Once the task has finished (succeeded or failed), get the result either way:
- Webhook (recommended in production): if you passed
webhook_url, the full task is pushed to it once when the task finishes. See Webhooks. - Polling: call
GET /v1/tasks/{task_id}withdata.idat most once every 15 seconds untilstatusissucceededorfailed. Check several tasks at once withidsin List tasks.
| Field | Veo 3.1 |
|---|---|
outputs[0].url | The generated video, an mp4 (H.264, 24 fps; for example 1920×1080 at 1080p 16:9), with sound unless lite sent generate_audio: false. Kept for 15 days after the task finishes. |
outputs[0].preview_url | A silent, compressed copy for previews, at most 600 pixels wide; null if it couldn't be made (use url). See Outputs. |
billing.billed_seconds | The duration. |
input | The parameters you sent; image_urls and generate_audio only if you sent them. |
On failure the frozen amount is returned in full and the reason is in data.error.type, see Errors. Every field is described in The task object.
Media
Images are passed in image_urls as public http(s) URLs. There's no upload endpoint: host the files yourself. URLs are at most 4096 characters, must download directly and can't point to localhost or private, loopback or reserved addresses.
Veo 3.1 reads the images only after the task is submitted for generation, a few seconds after creation, once the cost has been frozen. If one can't be downloaded or read (corrupt or an unsupported format), the task fails with invalid_media and the frozen amount is returned in full. Keep every link reachable until the task finishes, including links that expire.
Pricing
cost = duration × price per second of the variant and resolutionLoading the current prices…
Billed seconds are the duration you ask for (4, 6 or 8). Only variant and resolution change the price per second: the mode, the images and sound in the output (generate_audio) don't.
Example: the image-to-video request above (fast, 1080p, 4 seconds) freezes 4 × the 1080p price per second of fast and is charged the same when it succeeds; if it fails, all of it is returned. How freezing and charging work in general: Billing.
Loading the current prices…
The price is known from the request alone, so you can show it before submitting: read the current prices from GET /v1/products, one prices row per variant and resolution (the row's variant). Prices change, so don't hard-code them; billing.frozen on the retrieved task is what counts.
Errors
Errors and failure reasons specific to Veo 3.1. Every error of the create endpoint: Create a task → Errors; every failure reason: Failure reasons.
Create request errors: the request fails with an HTTP error and no task is created. param names the parameter.
| Status | error.type | When | What to do |
|---|---|---|---|
400 | invalid_request | A required parameter is missing (Field required), or variant, mode, resolution, aspect_ratio or duration isn't an allowed value (for example Input should be 4, 6 or 8 for veo-3.1, or 1080p with lite: Input should be '720p' for veo-3.1 variant lite). | Send every required parameter with an allowed value, see Create a task. |
400 | invalid_request | A rule of the variant or mode is broken: image_urls with text_to_video (image_urls is not allowed for mode text_to_video) or without it in image_to_video; two images with standard or fast (image_urls takes 1 image (the first frame) for variant fast; only lite supports a last frame); a last frame with a duration other than 8 (duration must be 8 when image_urls has a last frame); aspect_ratio: auto anywhere but lite image_to_video (aspect_ratio auto is only allowed for variant lite with mode image_to_video); generate_audio with standard or fast (generate_audio is only allowed for variant lite; standard always has audio). | Fix the parameter param names and send the request again. |
400 | invalid_request | A parameter this model doesn't take (Extra inputs are not permitted). | Remove it. |
Other errors of the create endpoint (401, 402, 429 and so on): Create a task → Errors.
Task failures: the create request succeeded (200) and the task later turned failed. There's no HTTP status: the reason is the task's error.type, from retrieving the task or the webhook.
error.type | When | What to do |
|---|---|---|
insufficient_balance | Your available balance can't cover duration × the price per second. Within a few seconds of creating the task, nothing frozen. | Top up, then create the task again. |
spend_limit_exceeded | This task's cost would go over one of the key's spend limits. Within a few seconds of creating the task, nothing frozen. | Raise the limit in Console → API keys, or wait for it to reset, then create the task again. |
invalid_media | An image can't be downloaded or read (corrupt or an unsupported format). Found when the task is submitted for generation, after the cost was frozen. | Replace the image error.message points to and create the task again. |
content_policy | The prompt or an image breaks the content policy: adult content, or a protected likeness or copyrighted material (for example a photo of a public figure). This includes images turned down by the model provider's moderation. Found when the task is submitted or while it's generated. | Resubmitting it as is fails again: change the prompt or the image. |
generation_failed | Generation failed or timed out. Veo 3.1 is occasionally unavailable for a while and every task fails during that time; we retry automatically before failing the task. | Create the task again later. |
queue_timeout / internal_error | Not started within 30 minutes / an error on our side. | Create the task again later. |
Every failed task has its frozen amount returned in full and billing.charged "0.00".