# Videos

Source: https://aiand-docs-staging.pages.dev/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
GET    /v1/videos/{video_id}/thumbnail
```

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

MiniMax H3 video generation runs on hardware in Japan.

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–30. **16–30 seconds is experimental; quality may vary.**                                                    |
| `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 a PNG, JPEG, or WebP file with `purpose: "vision"`, **at most 30 MiB**. Video generation has a lower input limit than the Files API, which accepts uploads up to 100 MiB.

```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 files to short-lived signed URLs immediately before generation; 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, per-file sizes, and the total size are validated at request time. A reference video or audio clip longer than the video you request is trimmed: only its first part, as long as the requested `seconds`, is used. For an 8-second video, that is the first 8 seconds of each clip. For best results, use clips of 2–15 seconds.

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

These 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.
- **At most 12 references in total.** Within the per-type limits above, `references` may hold no more than 12 files altogether.
- **At most 256 MiB in total.** Within the per-file limits above, all input files of one request may add up to no more than 256 MiB. A file used in two slots counts twice.
- **Input files need an hour left.** A frame or reference file that expires within the next hour is rejected. Upload it again.
- **Each slot accepts certain formats.** First and last frames and reference images: PNG, JPEG, or WebP. Reference videos: MP4, MOV, or WebM. Reference audio: WAV, MP3, FLAC, OGG, M4A, or audio in an MP4 or WebM file. Other formats, such as GIF or HEIC, are rejected.
- **Input files must not be empty.** A frame or reference file of 0 bytes is rejected.
- **Images must be between 1:4 and 4:1.** A frame or reference image whose long side is more than four times its short side is rejected.
- **Images must have a standard, readable header.** JPEGs must be 8-bit baseline or progressive; multi-picture JPEGs (such as some iPhone photos) and CMYK JPEGs are rejected. Ultra HDR photos are accepted. The image header must lie within the first 64 KiB; very large metadata can push it past that. Convert such files to a standard PNG or JPEG.
- **Images must be still.** Animated PNG and WebP files are rejected.

A request that breaks one of these rules is rejected with `400 invalid_value`. Where possible, `param` names the input at fault.

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,
  "thumbnail": false
}
```

`status` is one of `moderating`, `translating`, `queued`, `in_progress`, `completed`, `failed`, or `canceled`. No job ends as `canceled` today. 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`.

### Thumbnail

A completed job usually also has a JPEG still, 480 pixels wide, taken from near the start of the clip. `thumbnail` is `true` when the job has one to download; otherwise this returns `404`, or `400` if the job isn't `completed`:

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

The thumbnail is free and is deleted together with the video.

## 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. Jobs from everyone in the organization are listed; add `mine=true` to list only the jobs you (or your API key's owner) created.

## List video models

Authentication is optional. With an API key, prices are shown in your organization's billing currency; without one, prices are shown in USD. Requests without a key are rate-limited to 600 per minute per IP, one budget shared by every catalog endpoint callable without a key, including [`GET /v1/models`](/api/models/).

```bash
curl https://api.aiand.com/v1/videos/models
```

With an API key:

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

Each entry carries per-second pricing, one row per output resolution:

```json
{
  "object": "list",
  "data": [
    {
      "id": "minimaxai/minimax-h3",
      "object": "video_model",
      "name": "MiniMaxAI/MiniMax-H3",
      "description": null,
      "pricing": [
        {
          "resolution": "768p",
          "per_second": "0.055000",
          "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 or times out is never charged. A job cannot be canceled once submitted.

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_value`              | 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                   |
| `403`  | `zero_data_retention`        | Your organization is on [zero data retention](/organizations/zero-data-retention/), which refuses video generation |
| `402`  | `insufficient_credits`       | Balance does not cover the quoted cost of this render                                                     |
| `402`  | `monthly_budget_reached`     | Your organization's remaining monthly budget does not cover this render plus renders already in flight    |
| `404`  | `model_not_found`            | Unknown model                                                                                             |
| `404`  | `model_access_denied`        | Your organization does not have access to this model                                                      |
| `429`  | `concurrency_limit_exceeded` | You already have the maximum number of renders in progress for your plan                                  |
| `429`  | `rate_limit_exceeded`        | More than 10 submit attempts in one minute from your organization; retry after `Retry-After` seconds      |
| `409`  | `conflict`                   | The job is still running, or is under review and cannot be deleted                                        |
| `503`  | `provider_unavailable`       | Video generation is temporarily unavailable for this model                                                |

An admitted job can also end `failed`, for example with one of these error codes. Failed jobs are never charged.

- `engine_unavailable`: video generation was unavailable. Resubmit later.
- `engine_timeout`: the render did not finish within the time limit. Resubmit; contact support if it persists.
- `engine_rejected`: the video could not be generated. Retrying with the same inputs will fail the same way; contact support if it persists.

## 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>
