# Videos

> POST /v1/videos, polling, content download, list, and delete.

URL: https://boelabs.com/en/docs/ai-gateway/videos
Language: English

Create an asynchronous video generation job, poll its status, and retrieve the completed asset.
Configure a video deployment and object storage before using this endpoint.

## Example request

Set `GATEWAY` to the gateway origin and `API_KEY` to your virtual key. Replace the example
public model with one from [Model discovery](https://boelabs.com/docs/ai-gateway/models-discovery).

```bash
curl -X POST "$GATEWAY/v1/videos" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "model": "video-default", "prompt": "a serene mountain landscape at sunset", "duration": 8, "aspect_ratio": "16:9", "resolution": "720p" }'
```

`POST /v1/videos` creates an async generation job and returns a local `video_*` id. Poll that local id
with `GET /v1/videos/{video_id}`. Download the generated bytes with
`GET /v1/videos/{video_id}/content`; the default `variant` is `video`.

## Request parameters

There is no industry standard for video generation, so the gateway defines its own surface and
stays accept-compatible with OpenAI's Sora shape:

| Parameter | Notes |
| --- | --- |
| `model`, `prompt` | Required. |
| `task` | Optional explicit workflow: `text_to_video`, `image_to_video`, `reference_to_video`, `edit`, or `extend`. Models declare which tasks they support. |
| `duration` | Length in seconds (integer). `seconds` (string or int) is the OpenAI-style alias; send one or the other. |
| `size` | Exact `WIDTHxHEIGHT`. Interchangeable with `aspect_ratio` + `resolution`; send one form. |
| `aspect_ratio` / `resolution` | `16:9`, `9:16`, ... / `360p`–`4K`. Reverse-mapped to an exact size for providers that need one. |
| `input_references` | Array of `{type: "image_url"\|"audio_url"\|"video_url", ...}` parts. `input_reference` (single, `image_url` or `file_id`) is the OpenAI-style alias. |
| `frame_images` | First/last frame conditioning (`frame_type: "first_frame"\|"last_frame"`) for models that support it. |
| `seed`, `generate_audio` | Forwarded only when the model profile declares support. |
| `quality` | Gateway extension on the shared ladder: `auto`, `low`, `medium`, `high`, `xhigh`, or `max` (`native` is still accepted and normalized to `max`). Reconciled with the model's declared `qualities` under the [parameter policy](https://boelabs.com/docs/ai-gateway/parameter-policy#quality-on-image-and-video-generation), and forwarded only when the profile declares rungs. |
| `user` | Gateway-side attribution only. It is stored with the job and **never** forwarded upstream. |
| `extra_body` | Provider-specific passthrough merged into the upstream request body. |

Every parameter is validated against the model profile before routing, so unsupported parameters fail
fast with `unsupported_parameter` instead of a provider error mid-pool.

Image, audio, and video references accept either public HTTPS URLs or base64 `data:` URLs. AI Gateway
downloads every HTTPS reference with SSRF protection, validates its MIME type and binary limits, and
sends a `data:` URL to the selected upstream; remote URLs are never delegated to providers. The
combined decoded media limit is 50 MB. Large data URLs are redacted before the request body is
persisted. Multipart uploads (the OpenAI SDK's file-typed `input_reference`) are not supported — use
the JSON reference forms.

Image generation itself has no attachment field. Image edits accept multipart image files; AI Gateway
streams those files into request-scoped temporary storage, validates them as PNG/JPEG/WebP, and only
then invokes an upstream adapter.

## Storage

Videos use the gateway-wide object storage layer, configured with one connection string:
`OBJECT_STORAGE_URL=file:.source/object-storage` for development, or
`OBJECT_STORAGE_URL=s3://key:secret@host/bucket?region=auto` for S3, R2 or MinIO. The
gateway stores generated video bytes for 24 hours by default (`VIDEOS_ASSET_RETENTION_HOURS=24`) and
keeps metadata in Postgres.

## Model profiles

Video-capable deployments declare `catalogEntry.operations["video.generate"]`. The profile controls
accepted tasks, durations, sizes (with per-provider `aspectRatio`/`resolution` mappings), `quality`,
reference support (`supportsImageUrl`, `supportsAudioUrl`, `supportsVideoUrl`, `supportsFileId`,
`supportsFrameImages`, `maxInputReferences`), and `supportsSeed`/`supportsGenerateAudio`.

Adapters expose video generation through transports: `videos` (OpenAI-shaped Videos API),
`videos_async` (async job API: submit `/videos`, poll `/videos/{jobId}`, download
`/videos/{jobId}/content`, delete `/videos/{jobId}`), `generate_videos` (Google Veo long-running
operations), and `interactions` (Google Interactions API). Adapter defaults pick the common path;
use `transportOverrides` when a model needs another transport.

### Gemini Omni Flash

`gemini-omni-1.1-flash` uses the Google `interactions` transport. Configure its deployment with
`transportOverrides: { "video.generate": "interactions" }`.

- Text-to-video may omit `task` or set `text_to_video`.
- Image-to-video and first/last-frame interpolation use image references or `frame_images`.
- Reference-to-video uses `task: "reference_to_video"` and image/video references.
- Editing and extension require `task: "edit"` or `task: "extend"` plus exactly one `video_url`.

The integration intentionally does not use Google's Files API. Inputs are materialized as validated
inline data, and completed output is fetched inline directly into AI Gateway object storage without
being persisted in Postgres. Consequently, the stock profile exposes 360p and 720p only, rejects
`file_id`, and limits each input asset to 20 MB. Uploaded videos for edit/extend remain subject to
Google's 10-second and regional restrictions.

Stateful continuation is also intentionally unavailable. `previous_interaction_id` is blocked in
`extra_body`; accepting a raw provider id would bypass AI Gateway ownership and deployment-affinity
checks. A future implementation should expose a local source video id instead.

Omni bills video output by token (`videoOutputCentsPerMTokens`; 360p is 1,931 and 720p is 5,792 tokens
per second). A virtual key with a token or budget limit reserves an upper bound at submit, from the
requested duration and size (the longest duration and densest size when omitted). When the job
reaches a terminal state the reservation is settled to the usage Google reports, or released if the
job failed. A completed job whose provider reports no usage is charged the estimate for its duration
and size. Settlement is recorded in the job's `billing`, and an unsettled reservation expires with its
lease. A deployment with no pricing still fails budget-capped virtual keys closed with
`budget_pricing_unavailable`.

## Lifecycle

- `GET /v1/videos` lists videos visible to the caller (virtual keys see their own; the master key sees all).
- `GET /v1/videos/{video_id}` refreshes upstream status when needed.
- `GET /v1/videos/{video_id}/content` streams bytes from object storage and supports byte ranges (including suffix ranges).
- `DELETE /v1/videos/{video_id}` deletes stored assets, marks the metadata deleted, and best-effort deletes the upstream job where the provider supports it.

A background poller advances unfinished jobs (`VIDEO_JOB_POLL_INTERVAL_MS`, batch size
`VIDEO_JOB_POLL_BATCH_SIZE`) and fails jobs that exceed `VIDEO_JOB_MAX_RUNTIME_MINUTES`. Jobs are
claimed atomically, so multiple gateway instances can poll safely.

## Next steps

- [Creating deployments](https://boelabs.com/docs/ai-gateway/creating-deployments#custom-video) — custom video profiles.
- [Providers → OpenAI-compatible](https://boelabs.com/docs/ai-gateway/providers-openai-compatible) — compatible APIs and transport overrides.

