---
title: Video Generation
description: How to generate videos with Sume models via the asynchronous /v1/videos API.
---

Sume supports video generation from text prompts (and optional reference
images) via a dedicated asynchronous API. You can find the supported models,
their capabilities, and pricing from `GET /v1/videos/models`.

This surface follows the [OpenRouter Video Generation
API](https://openrouter.ai/docs/guides/overview/multimodal/video-generation)
field-for-field, so a client written against their docs works here after
changing the base URL and the API key. The handful of places Sume differs are
collected in [Sume differences](#sume-differences).

## Model Discovery

You can find video generation models in several ways:

### Via the Video Models API

Use the dedicated video models endpoint to list all available video generation
models along with their supported parameters:

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

The response returns a `data` array where each model includes:

```json
{
  "data": [
    {
      "id": "seedance-2",
      "canonical_slug": "seedance-2",
      "name": "Seedance 2.0",
      "description": "Seedance 2.0 — text/image/reference-to-video with optional audio.",
      "created": 1767225600,
      "supported_resolutions": ["480p", "720p", "1080p"],
      "supported_aspect_ratios": ["21:9", "16:9", "4:3", "1:1", "3:4", "9:16"],
      "supported_sizes": null,
      "supported_durations": [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
      "supported_frame_images": ["first_frame", "last_frame"],
      "supported_input_references": ["image_url", "video_url", "audio_url"],
      "generate_audio": true,
      "seed": false,
      "pricing_skus": {
        "per-1000-video-tokens": "0.0154"
      },
      "allowed_passthrough_parameters": [],
      "hugging_face_id": null
    }
  ]
}
```

| Field                            | Description                                                                       |
| -------------------------------- | --------------------------------------------------------------------------------- |
| `id`                             | Model slug to use in generation requests                                          |
| `canonical_slug`                 | Permanent model identifier                                                        |
| `supported_resolutions`          | List of supported output resolutions (e.g., `720p`, `1080p`)                      |
| `supported_aspect_ratios`        | List of supported aspect ratios (e.g., `16:9`, `9:16`)                            |
| `supported_sizes`                | List of supported pixel dimensions (e.g., `1280x720`), or `null`                  |
| `supported_durations`            | Supported video lengths in whole seconds                                          |
| `supported_frame_images`         | Which `frame_type` values the model accepts                                       |
| `supported_input_references`     | Which `input_references` types the model accepts                                  |
| `generate_audio`                 | Whether the model can generate an audio track                                     |
| `seed`                           | Whether the model accepts a `seed`                                                |
| `pricing_skus`                   | Pricing information per SKU                                                       |
| `allowed_passthrough_parameters` | Provider-specific parameters that can be passed through via the `provider` option |

Use this endpoint to check which resolutions, aspect ratios, and durations are
supported by each model before submitting a generation request. Limits are not
uniform: `seedance-2.5` accepts 4–30 seconds at 480p/720p/1080p, `wan-3.0`
accepts 2–30 seconds, and `minimax-h3` accepts 5–15 seconds at native 480p/768p
(768p is first-class, not 720p; 2K/4K upscales are priced if requested). Every
other catalog model tops out at 15 seconds. `seedance-2` also offers 1080p.
MiniMax H3 Max (`minimax-h3-max`) is gated until GA and is not listed.

### Via the Models API

You can also use the [public model catalog](/models) to discover video
generation models:

```bash
curl "https://api.sume.com/v1/models" \
  -H "Authorization: Bearer $SUME_API_KEY"
```

### On the Models Page

Visit the [Models page](/models) and look for models that list `video` as an
output modality.

## How It Works

Unlike text or image generation, video generation is **asynchronous** because
generating video takes significantly longer. The workflow is:

1. **Submit** a generation request to `POST /v1/videos`
2. **Receive** a job ID and polling URL immediately
3. **Poll** the polling URL (`GET /v1/videos/{jobId}`) until the status is `completed`
4. **Download** the video from the content URL (`GET /v1/videos/{jobId}/content`)

## API Usage

### Submitting a Video Generation Request

```python title="Python"
import requests
import time

url = "https://api.sume.com/v1/videos"
headers = {
    "Authorization": f"Bearer {SUME_API_KEY}",
    "Content-Type": "application/json",
}

payload = {
    "model": "seedance-2",
    "prompt": "A golden retriever playing fetch on a sunny beach with waves crashing in the background",
}

# Step 1: Submit the generation request
response = requests.post(url, headers=headers, json=payload)
result = response.json()

job_id = result["id"]
polling_url = result["polling_url"]
print(f"Job submitted: {job_id}")
print(f"Status: {result['status']}")

# Step 2: Poll until completion
while True:
    time.sleep(30)  # Wait 30 seconds between polls
    poll_response = requests.get(polling_url, headers=headers)
    status = poll_response.json()

    print(f"Status: {status['status']}")

    if status["status"] == "completed":
        # Step 3: Download the video
        content_url = status["unsigned_urls"][0]
        video_response = requests.get(content_url)
        with open("output.mp4", "wb") as f:
            f.write(video_response.content)
        print("Video saved to output.mp4")
        break
    elif status["status"] == "failed":
        print(f"Generation failed: {status.get('error', 'Unknown error')}")
        break
```

```typescript title="TypeScript (fetch)"
const headers = {
  Authorization: `Bearer ${SUME_API_KEY}`,
  'Content-Type': 'application/json',
};

// Step 1: Submit the generation request
const response = await fetch('https://api.sume.com/v1/videos', {
  method: 'POST',
  headers,
  body: JSON.stringify({
    model: 'seedance-2',
    prompt:
      'A golden retriever playing fetch on a sunny beach with waves crashing in the background',
  }),
});

const result = await response.json();
const jobId = result.id;
const pollingUrl = result.polling_url;
console.log(`Job submitted: ${jobId}`);
console.log(`Status: ${result.status}`);

// Step 2: Poll until completion
while (true) {
  await new Promise((resolve) => setTimeout(resolve, 30000)); // Wait 30 seconds
  const pollResponse = await fetch(pollingUrl, { headers });
  const status = await pollResponse.json();

  console.log(`Status: ${status.status}`);

  if (status.status === 'completed') {
    // Step 3: Download the video
    const contentUrl = status.unsigned_urls[0];
    console.log(`Video ready: ${contentUrl}`);
    break;
  } else if (status.status === 'failed') {
    console.error(`Generation failed: ${status.error ?? 'Unknown error'}`);
    break;
  }
}
```

```bash title="cURL"
# Step 1: Submit the generation request
curl -X POST "https://api.sume.com/v1/videos" \
  -H "Authorization: Bearer $SUME_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "seedance-2",
    "prompt": "A golden retriever playing fetch on a sunny beach with waves crashing in the background"
  }'

# Response:
# {
#   "id": "<job_id>",
#   "polling_url": "https://api.sume.com/v1/videos/<job_id>",
#   "status": "pending",
#   "model": "seedance-2"
# }

# Step 2: Poll for status
curl "https://api.sume.com/v1/videos/<job_id>" \
  -H "Authorization: Bearer $SUME_API_KEY"

# Step 3: Once status is "completed", download from unsigned_urls[0]
```

### Request Parameters

| Parameter          | Type    | Required | Description                                                                                                             |
| ------------------ | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------- |
| `model`            | string  | Yes      | The model to use for video generation (e.g., `seedance-2.5`), or `sume/auto` to let Sume pick                            |
| `prompt`           | string  | Yes      | Text description of the video to generate                                                                               |
| `duration`         | integer | No       | Duration of the generated video in seconds                                                                              |
| `resolution`       | string  | No       | Resolution of the output video (e.g., `720p`, `1080p`)                                                                  |
| `aspect_ratio`     | string  | No       | Aspect ratio of the output video (e.g., `16:9`, `9:16`, `3:2`)                                                          |
| `size`             | string  | No       | Exact pixel dimensions in `WIDTHxHEIGHT` format (e.g., `1280x720`). Interchangeable with `resolution` + `aspect_ratio`   |
| `frame_images`     | array   | No       | Images for first/last frames (image-to-video)                                                                           |
| `input_references` | array   | No       | Reference images for style guidance (reference-to-video)                                                                |
| `generate_audio`   | boolean | No       | Whether to generate audio alongside the video. Defaults to the model's audio capability                                 |
| `seed`             | integer | No       | Seed for deterministic generation (not guaranteed by all providers)                                                     |
| `callback_url`     | string  | No       | URL to receive a webhook notification when the job completes. Must be HTTPS                                             |
| `provider`         | object  | No       | Provider-specific passthrough configuration                                                                             |

### Supported Resolutions

- `480p`
- `720p`
- `768p`
- `1080p`
- `1K`
- `2K`
- `4K`

Each model advertises the subset it accepts in `supported_resolutions`.

### Supported Aspect Ratios

- `16:9` — Widescreen landscape
- `9:16` — Vertical/portrait
- `1:1` — Square
- `4:3` — Standard landscape
- `3:4` — Standard portrait
- `3:2` — Photography landscape
- `2:3` — Photography portrait
- `21:9` — Ultra-wide
- `9:21` — Ultra-tall

Each model advertises the subset it accepts in `supported_aspect_ratios`.

### Using Images

There are two ways to provide images, each triggering a different generation
mode:

- **`frame_images`** — Specifies first or last frame images for
  **image-to-video** generation. Each entry must include a `frame_type` of
  `first_frame` or `last_frame`.
- **`input_references`** — Provides style or content reference images for
  **reference-to-video** generation. The model uses these as visual guidance
  rather than exact frames.

If both fields are provided, `frame_images` takes precedence and the request is
treated as image-to-video.

#### Image-to-Video (frame_images)

```json
{
  "model": "seedance-2",
  "prompt": "A character walking through a forest",
  "frame_images": [
    {
      "type": "image_url",
      "image_url": {
        "url": "https://example.com/first-frame.png"
      },
      "frame_type": "first_frame"
    }
  ],
  "resolution": "1080p"
}
```

#### Reference-to-Video (input_references)

```json
{
  "model": "seedance-2",
  "prompt": "A colossal solar flare beside a planet",
  "input_references": [
    {
      "type": "image_url",
      "image_url": {
        "url": "https://example.com/style-ref.png"
      }
    }
  ],
  "resolution": "1080p"
}
```

Only models whose `supported_input_references` lists a type accept that type.
Audio and video references are honored by Seedance 2.0 only.

### Provider-Specific Options

You can pass provider-specific options using the `provider` parameter. Options
are keyed by provider slug, and only the options for the matched provider are
forwarded:

```json
{
  "model": "seedance-2",
  "prompt": "A time-lapse of a flower blooming",
  "provider": {
    "options": {}
  }
}
```

Use the [Video Models API](#via-the-video-models-api) to check which passthrough
parameters each model supports via the `allowed_passthrough_parameters` field.
In v1 that list is empty for every model, so `provider.options` entries are
rejected rather than silently dropped — see
[Sume differences](#sume-differences).

## Response Format

### Submit Response (202 Accepted)

When you submit a video generation request, you receive an immediate response
with the job details:

```json
{
  "id": "job_01HXYZ",
  "polling_url": "https://api.sume.com/v1/videos/job_01HXYZ",
  "status": "pending",
  "model": "seedance-2"
}
```

### Poll Response

When polling the job status, the response includes additional fields as the job
progresses:

```json
{
  "id": "job_01HXYZ",
  "generation_id": "job_01HXYZ",
  "polling_url": "https://api.sume.com/v1/videos/job_01HXYZ",
  "status": "completed",
  "model": "seedance-2",
  "unsigned_urls": ["https://api.sume.com/v1/videos/job_01HXYZ/content?index=0"],
  "usage": {
    "cost": 0.25,
    "is_byok": false
  }
}
```

### Job Statuses

| Status        | Description                                     |
| ------------- | ----------------------------------------------- |
| `pending`     | The job has been submitted and is queued        |
| `in_progress` | The video is being generated                    |
| `completed`   | The video is ready to download                  |
| `failed`      | The generation failed (check the `error` field) |
| `cancelled`   | The job was canceled before it finished         |

### Downloading the Video

Once the job status is `completed`, the `unsigned_urls` array contains URLs to
download the generated video content. You can also use the content endpoint
directly:

```bash
curl "https://api.sume.com/v1/videos/{jobId}/content?index=0" \
  -H "Authorization: Bearer $SUME_API_KEY" \
  --output video.mp4
```

The `index` query parameter defaults to `0` and can be used if the model
generates multiple video outputs.

## Webhooks

Instead of polling for job status, you can receive a webhook notification when a
video generation job completes. Pass `callback_url` in the request body; Sume
POSTs to it once the job reaches a terminal state.

Sume signs the raw JSON body and sends `x-sume-webhook-timestamp` and
`x-sume-webhook-signature` headers. The payload is Sume's standard job webhook
envelope, not OpenRouter's `video.generation.*` envelope — see
[Sume differences](#sume-differences) and the
[webhooks guide](/api/reference) for the exact shape and verification steps.

## Sume differences

Everything above matches the OpenRouter Video Generation API. These are the
only deltas.

| Area                 | OpenRouter                                                        | Sume                                                                                                                    |
| -------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| Base path            | `https://openrouter.ai/api/v1/videos`                             | `https://api.sume.com/v1/videos` (no `/api` segment)                                                                    |
| Auth                 | `Authorization: Bearer $OPENROUTER_API_KEY`                       | `Authorization: Bearer $SUME_API_KEY`                                                                                   |
| Model ids            | `org/slug` (e.g. `google/veo-3.1`)                                 | bare catalog ids (e.g. `seedance-2`). Sume's published contract never carries a provider-org prefix                      |
| Auto routing         | no generate-time auto                                             | `model: "sume/auto"` lets Sume pick the family. Responses echo `sume/auto`; the family that ran is never disclosed       |
| `size`               | accepted where the model advertises `supported_sizes`             | every v1 model reports `supported_sizes: null`, so `size` returns `400 unsupported_parameter`. Use `resolution` + `aspect_ratio` |
| `provider.options`   | forwarded to the matched upstream provider                        | v1 runs a single backend per model, so a non-empty `provider.options` returns `400 unsupported_parameter`               |
| `seed`               | accepted by many models                                           | no v1 model accepts `seed`; each reports `seed: false` and rejects the field                                            |
| Webhook envelope     | `video.generation.*` events, `X-OpenRouter-Signature`             | Sume's standard job webhook envelope with `x-sume-webhook-signature`                                                    |
| Idempotency          | none on this route                                                | send `Idempotency-Key` to make retries safe; a replay returns the original job                                          |
| Job lifecycle        | polling URL only                                                  | the same job is also visible at `GET /v1/jobs/{id}/status` and `GET /v1/jobs/{id}/result`                               |
| Zero Data Retention  | video generation is ZDR-ineligible                                | Sume has no ZDR toggle; see the [privacy docs](/the-basics)                                                              |
| Billing              | credits                                                           | workspace USD balance, reserved on submit at provider list × 1.10. `usage.cost` is the Sume billable amount             |

### `sume/auto`

`sume/auto` is a Sume-only addition. Send it as `model` when you do not want to
pin a family:

```json
{
  "model": "sume/auto",
  "prompt": "A vertical UGC-style product clip on a desk, natural light",
  "aspect_ratio": "9:16",
  "duration": 5
}
```

Resolution is a pure function of the normalized request and the catalog
version, so an idempotent replay prices and routes identically. The poll
response reports `"model": "sume/auto"` — Sume does not disclose which family
served the request, and you should not build on any observable trait of the
output to infer it.

### Legacy `/v1/video-router/*`

`POST /v1/video-router/generate` and `GET /v1/video-router/models` still work
unchanged and create the same jobs, with the same model ids. The difference is
the wire: Video Router returns Sume's `{ "data": ... }` job envelope and takes
Sume's flat `image_url` / `reference_image_urls` fields, while `/v1/videos`
returns the response shape documented above. Because the model vocabulary is
shared, migrating is a path-and-body change with no id remapping. New
integrations should use `/v1/videos`.

## Best Practices

- **Detailed Prompts**: Provide specific, descriptive prompts for better video
  quality. Include details about motion, camera angles, lighting, and scene
  composition
- **Appropriate Resolution**: Higher resolutions take longer to generate and
  cost more. Choose the resolution that fits your use case
- **Polling Interval**: Use a reasonable polling interval (e.g., 30 seconds) to
  avoid excessive API calls. Video generation typically takes 30 seconds to
  several minutes depending on the model and parameters
- **Error Handling**: Always check the job status for `failed` state and handle
  the `error` field appropriately
- **Reference Images**: When using reference images, ensure they are high
  quality and relevant to the desired video output

## Troubleshooting

**Job stays in `pending` for a long time?**

- Video generation can take several minutes depending on the model, resolution,
  and server load
- Continue polling at regular intervals

**Generation failed?**

- Check the `error` field in the poll response for details
- Ensure your prompt is appropriate and within model guidelines
- Check that any reference images are accessible over public HTTPS and in
  supported formats

**Model not found?**

- Use the [Video Models API](#via-the-video-models-api) to find available video
  generation models
- Verify the model id is correct (e.g., `seedance-2`) — Sume uses bare catalog
  ids, not `org/slug`
