---
title: "Generate a video"
description: "Start a video job with ai.video, let the host finish it, and get a labelled file back."
section: "guides"
status: "Planned for 0.6"
spec: "ai-video"
canonical: "https://mainsdk.dev/docs/guides/generate-a-video"
updated: "2026-10-28"
---

# Generate a video

> Start a video job with ai.video, let the host finish it, and get a labelled file back.

Status: Planned for 0.6 · Canonical: https://mainsdk.dev/docs/guides/generate-a-video · Updated: 2026-10-28

You will start a video as a durable job, wait for it to finish, and read the file it wrote. It takes about 10 minutes and needs no provider account for the first run.

## You need

- `files` set up, because the finished clip is written there (set `files.maxSize` large enough for a clip).
- For a real clip, a video provider key as a secret, such as `secret:OPENAI_API_KEY`.

## 1. Configure

```yaml title="main.yaml"
ai:
  models: [openai/*]
  video: { driver: openai, max-seconds: 8 }   # openai | google | gateway
  prices:
    openai/sora-2: { video: { "1280x720": "$0.10/s" } }   # a model with no price cannot be called
```

## 2. Start a job

```ts title="app/api/clip/route.ts"
import { ai } from "mainsdk";

export async function startClip(user: { id: string }) {
  const job = await ai.video.generate({
    model: "openai/sora-2",
    prompt: "A paper boat on a stream",
    seconds: 4,
    feature: "promo",
    user,
    maxCost: "$1",   // refused before anything is submitted when the worst case is higher
  });
  return Response.json({ id: job.id, state: job.state });   // queued; nothing waits for the provider
}
```

The request returns at once. A durable workflow submits the job, waits, fetches the output into `files` and finishes, even across a restart.

## 3. Read the result

```ts title="app/api/clip/[id]/route.ts"
import { ai } from "mainsdk";

export async function readClip(id: string) {
  const job = await ai.video.get(id);   // queued, running, ready, failed or cancelled
  return job.state === "ready" ? Response.json({ file: job.file, cost: job.cost }) : Response.json({ state: job.state });
}
```

`ai.video.cancel(id, { reason })` stops the job at the provider and records any cost already billed.

## Check it worked

Run with `driver: fake` and ask for more seconds than `max-seconds`: the call fails with `MAIN-AI-0050` and nothing is sent. A job id from another tenant fails with `MAIN-AI-0051`. A finished clip carries the `ai-generated` label, and with `ai.credentials.signer` set it also carries a signed content credential that `ai.credentials.verify` reports as `verified`.

## Next

- [AI calls](https://mainsdk.dev/docs/interfaces/ai)
- [Call a model](https://mainsdk.dev/docs/guides/call-a-model)
