# Videos

Source: https://staging-docs.aiand.com/api/videos/

The Videos API generates a video clip from a text prompt, optionally conditioned on uploaded media — one or two images as first/last frames, or reference images, video clips, and audio clips. Rendering takes minutes rather than seconds, so it is an **asynchronous job resource**: you create a job, poll it until it reaches a terminal status, then download the output.

```http
POST   /v1/videos
GET    /v1/videos
GET    /v1/videos/models
GET    /v1/videos/acceptance
POST   /v1/videos/acceptance
GET    /v1/videos/{video_id}
GET    /v1/videos/{video_id}/content
```

Output is MP4 with audio at 768p. Every request must specify `aspect_ratio`.

Japanese scene descriptions are translated to English before generation. The translator is instructed to leave dialogue inside `<d>…</d>` and text depicted in the scene unchanged.

## Agreement

A person must agree to the Video Service Terms in the console playground before they can start a job. After that, an API key used by that same person works. A teammate who has not agreed cannot start a job, even in the same organization.

Creating a job never records agreement. The only way to record it is `POST /v1/videos/acceptance` from a console login. An API key cannot agree.

`GET /v1/videos/acceptance` returns the current terms version and whether this person, in this organization, has agreed to it.

A missing agreement returns `403` with code `agreement_required`. Listing jobs, reading a job, and listing models stay open.

## Create a job

```bash
curl https://api.aiand.com/v1/videos \
  -H "Authorization: Bearer $AIAND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "minimaxai/minimax-h3",
    "prompt": "A paper boat drifting down a rain-soaked gutter at dusk",
    "seconds": 8,
    "aspect_ratio": "16:9"
  }'
```

| Field                 | Required | Notes                                                                             |
| --------------------- | -------- | --------------------------------------------------------------------------------- |
| `model`               | yes      | A model id from [`GET /v1/videos/models`](#list-video-models)                     |
| `prompt`              | yes      | Up to 7000 characters                                                             |
| `seconds`             | yes      | Clip duration, 4–15                                                               |
| `aspect_ratio`        | yes      | One of `Auto`, `21:9`, `16:9`, `4:3`, `1:1`, `3:4`, `9:16`                       |
| `image_reference`     | no       | One or two images (`purpose: "vision"`) with unique `first_frame`/`last_frame` roles (image-to-video) |
| `references`          | no       | Reference media to condition on (reference-to-video): `images`, `videos`, `audio`. Cannot be combined with `image_reference` |

### First and last frame (image-to-video)

Upload each image first and use the returned `file_id`. Each image must be `purpose: "vision"` and **under 30 MiB** — the engine rejects larger inputs even though the Files API accepts uploads up to 100 MB.

```bash
curl https://api.aiand.com/v1/files \
  -H "Authorization: Bearer $AIAND_API_KEY" \
  -F "purpose=vision" \
  -F "file=@first.jpg"
```

Then submit one or two file references in customer-defined order:

```bash
curl https://api.aiand.com/v1/videos \
  -H "Authorization: Bearer $AIAND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "minimaxai/minimax-h3",
    "prompt": "A cinematic scene inspired by the references, natural motion",
    "seconds": 5,
    "aspect_ratio": "16:9",
    "image_reference": [
      { "file_id": "file-first", "role": "first_frame" },
      { "file_id": "file-last", "role": "last_frame" }
    ]
  }'
```

The API validates that every file belongs to your organization and has `purpose: "vision"`. It converts the stored R2 objects to short-lived signed URLs immediately before engine submission; public URLs are never required. Either role may be used alone, so a single `first_frame` does not require a `last_frame`. When both are supplied, their roles must be unique.

### Reference images, video, and audio (reference-to-video)

To condition on reference media instead of frames, upload each file with the matching purpose — `vision` for images, `video` for clips, `audio` for sound — and pass the `file_id`s inside a `references` object:

```bash
curl https://api.aiand.com/v1/videos \
  -H "Authorization: Bearer $AIAND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "minimaxai/minimax-h3",
    "prompt": "A cinematic scene inspired by the references, natural motion",
    "seconds": 5,
    "aspect_ratio": "16:9",
    "references": {
      "images": [{ "file_id": "file-img1" }],
      "videos": [{ "file_id": "file-clip1" }],
      "audio":  [{ "file_id": "file-aud1" }]
    }
  }'
```

Counts and per-file sizes are validated at request time; video and audio references must also be 2–15 seconds long (enforced by the engine).

| Reference | Purpose  | Max count | Max size | Duration |
| --------- | -------- | --------- | -------- | -------- |
| image     | `vision` | 8         | 30 MiB   | —        |
| video     | `video`  | 2         | 50 MiB   | 2–15 s   |
| audio     | `audio`  | 3         | 15 MiB   | 2–15 s   |

Two rules apply:

- **Frames and references cannot be mixed.** A request uses `image_reference` **or** `references` — never both. They are separate request shapes.
- **Audio needs an anchor.** `references` must include at least one image or video; audio alone is rejected.

The response is a job object with `status: "moderating"`:

```json
{
  "id": "video_9f2c1b7e4a3d4e8fa1b0c9d8e7f6a5b4",
  "object": "video",
  "model": "minimaxai/minimax-h3",
  "prompt": "A paper boat drifting down a rain-soaked gutter at dusk",
  "seconds": 8,
  "status": "moderating",
  "error": null,
  "cost": "0.64000000",
  "currency": "usd",
  "created_at": 1755302400,
  "completed_at": null
}
```

`status` is one of `moderating`, `translating`, `queued`, `in_progress`, `completed`, `failed`, or `canceled`. Treat any status you do not recognize as still running — new intermediate stages may be added. `error` is populated only when the job failed.

## Input moderation

Every job starts as `moderating` while its prompt and reference media are checked against our content policy. This normally takes a few seconds — up to about a minute when references are attached — and allowed jobs move to `translating` while the prompt is translated to English, then to `queued`, and render as usual. A request that violates the policy fails with error code `moderation_blocked` and is never billed:

```json
{
  "status": "failed",
  "error": {
    "code": "moderation_blocked",
    "message": "Your request was rejected by our content moderation system."
  }
}
```

If the moderation system itself is unavailable, the job fails closed with error code `moderation_unavailable` — nothing was judged and nothing is billed, so resubmit later. Likewise, if a Japanese prompt cannot be translated, the job fails closed with error code `translation_unavailable` — nothing is billed, so resubmit later.

## Poll for completion

```bash
curl https://api.aiand.com/v1/videos/video_9f2c1b7e4a3d4e8fa1b0c9d8e7f6a5b4 \
  -H "Authorization: Bearer $AIAND_API_KEY"
```

A typical clip takes several minutes. Poll every 10–30 seconds until `status` is terminal — polling faster does not make a render finish sooner.

## Download the output

Once `status` is `completed`, fetch the bytes. The response is the raw MP4, not JSON:

```bash
curl https://api.aiand.com/v1/videos/video_9f2c1b7e4a3d4e8fa1b0c9d8e7f6a5b4/content \
  -H "Authorization: Bearer $AIAND_API_KEY" \
  -o clip.mp4
```

Requesting content for a job that is not `completed` returns `400`.

## List your jobs

```bash
curl "https://api.aiand.com/v1/videos?limit=20" \
  -H "Authorization: Bearer $AIAND_API_KEY"
```

Returns newest first. `limit` defaults to 20 and caps at 100; pass `after=<video_id>` to page.

## List video models

```bash
curl https://api.aiand.com/v1/videos/models \
  -H "Authorization: Bearer $AIAND_API_KEY"
```

Each entry carries per-second pricing in your billing currency, one row per output resolution:

```json
{
  "object": "list",
  "data": [
    {
      "id": "minimaxai/minimax-h3",
      "object": "video_model",
      "name": "MiniMaxAI/MiniMax-H3",
      "description": "MiniMax H3",
      "pricing": [{ "resolution": "768p", "per_second": "0.080000", "currency": "usd" }]
    }
  ]
}
```

<Aside type="note">
  Video models do not appear in [`GET /v1/models`](/api/models/), which lists chat models only. The
  two catalogs are deliberately separate — a video model cannot be passed to a chat endpoint.
</Aside>

## Billing

A render is priced as `per_second × seconds` and **charged only when it completes**. A job that fails, times out, or is canceled is never charged.

Because a render costs dollars and cannot be aborted part-way, its exact cost is fixed when the job is submitted: `per_second × requested seconds`. A job is refused unless your balance covers that quote plus the quoted costs of renders already in flight. This is stricter than the chat endpoints, which admit any request while the balance is above zero.

`cost` and `currency` contain that quote from the first response. The balance is deducted only after the render completes successfully.

## Errors

| Status | Code                         | Meaning                                                                                            |
| ------ | ---------------------------- | -------------------------------------------------------------------------------------------------- |
| `400`  | `invalid_request_error`      | Bad body, out-of-range `seconds`, invalid or oversized references, or content requested before completion |
| `403`  | `agreement_required`         | This person has not agreed to the current Video Service Terms in the console playground            |
| `402`  | `insufficient_credits`       | Balance does not cover the quoted cost of this render                                              |
| `404`  | `model_not_found`            | Unknown model, or one your plan is not entitled to                                                 |
| `429`  | `concurrency_limit_exceeded` | You already have the maximum number of renders in progress for your plan                           |
| `409`  | `conflict`                   | The job is still running, or is under review and cannot be deleted                                 |

## Retention and deletion

A finished job — the generated video and the job record, including its prompt — is deleted
automatically **30 days** after it finishes. Download anything you want to keep before then.

`DELETE /v1/videos/{video_id}` deletes one immediately:

```bash
curl -X DELETE https://api.aiand.com/v1/videos/video_abc123 \
  -H "Authorization: Bearer $AIAND_API_KEY"
```

```json
{ "id": "video_abc123", "object": "video", "deleted": true }
```

This is permanent and cannot be undone. A job that is still running has to finish first — poll it
until its status is terminal, then delete.

<Aside type="note">
  Input files you uploaded through the Files API are separate: they expire 30 days after **upload**,
  on their own schedule, and deleting a video does not delete them. Our record that a submission was
  moderated is kept for the full 30 days regardless of when the video itself is deleted.
</Aside>
