---
title: "Animate a still"
description: "Turn one still image into one video."
url: https://docs.roasos.com/workflows/animate-a-still
---

# Animate a still

Animate a still turns one image that your workspace owns into one image-to-video generation. Use it on a product photo, on a static ad, or on any still that you already made with roasOS.

One call makes one video. There is no fan-out, and there is no `variants` field.

## 1. Choose the still

Send exactly one source.

| Field       | Use it for                                                   |
| ----------- | ------------------------------------------------------------ |
| `image_url` | A still on `https://cdn.roasos.com` that your workspace owns |
| `upload_id` | The id that `POST /v1/uploads` gave you                      |

The still must be 10 MB or smaller. Use `image/jpeg`, `image/png` or `image/webp`. Refer to [Reference media and uploads](/concepts/reference-media).

Add an optional `product_id` to hold a saved product steady. Its images ride as style references.

## 2. Write a prompt, or let roasOS write one

Send a `prompt` that says what moves. Name one primary movement.

```
The bottle stays still. The steam rises slowly from the cup behind it. The light moves a little across the label.
```

Set `auto_prompt: true` to skip that step. roasOS then analyzes the still first and derives the prompt from the analysis.

<Callout type="warn">
  `auto_prompt` bills one still analysis on top of the video. That is 1 extra credit. Refer to [Analyze an ad](/workflows/analyze-an-ad).
</Callout>

Send `prompt`, or set `auto_prompt` to `true`. A request with neither is refused.

## 3. Choose the model

The default is `seedance-2.5` (since 2026-10-04). Set `model` to change it.

| Model          | Use it for                                                                                                                                |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `seedance-2.5` | The default. It takes its shape from the still, accepts a still that shows a person, and renders one take of up to 30 seconds with sound. |
| `kling-4`      | A still with product references (`product_id`), or a length that Seedance does not offer.                                                 |

When you send no `model`, roasOS uses `seedance-2.5`. Two exceptions use `kling-4`: a request with `product_id`, because Seedance cannot combine a first frame with product references, and a `duration_seconds` that Seedance does not offer (for example `7`). roasOS never changes a `model` that you sent. With no `duration_seconds`, the clip is 5 seconds.

The two models accept different options.

| Field              | `kling-4`                                                              | `seedance-2.5`                                         |
| ------------------ | ---------------------------------------------------------------------- | ------------------------------------------------------ |
| `duration_seconds` | Each whole second from `3` to `30`                                     | `4`, `5`, `6`, `8`, `10`, `12`, `15`, `20`, `25`, `30` |
| `aspect_ratio`     | Taken from the still; with `product_id`: `9:16`, `16:9`, `1:1`, `21:9` | Taken from the still                                   |
| `resolution`       | `720p`, `1080p`; the default is `1080p`                                | `480p`, `720p`, `1080p`; the default is `720p`         |
| `prompt` length    | 2,500 characters                                                       | 10,000 characters                                      |

## 4. Quote

```bash
curl -X POST https://api.roasos.com/v1/workflows/static-to-video \
  -H "Authorization: Bearer roasos_sk_..." \
  -H "Idempotency-Key: animate-serum-hero-001" \
  -H "Content-Type: application/json" \
  -d '{
    "image_url": "https://cdn.roasos.com/workspaces/.../serum-hero.png",
    "prompt": "The bottle stays still. The steam rises slowly behind it.",
    "duration_seconds": 5,
    "quote_only": true
  }'
```

```json
{
  "data": {
    "kind": "quote",
    "quote_id": "f70b2d51-...",
    "credits": 15,
    "expires_at": "2026-08-21T12:09:04.000Z",
    "pricing_version": "customer-pricing@2026-10-30.v14",
    "confirmation": {
      "model": "seedance-2.5",
      "mode": "image-to-video",
      "duration_seconds": 5,
      "generate_audio": true
    }
  }
}
```

A 5-second Seedance 2.5 clip at 720p costs 15 credits, and a 10-second clip costs 30. On `kling-4` at 1080p, the same clips cost 15 and 29 (10 and 19 at 720p). The full table is on the [Kling 4.0](/models/video/kling-4) page and on the [Seedance 2.5](/models/video/seedance-2.5) page.

## 5. Confirm

Send the same body with the `quote_id`. Use the same idempotency key.

```bash
curl -X POST https://api.roasos.com/v1/workflows/static-to-video \
  -H "Authorization: Bearer roasos_sk_..." \
  -H "Idempotency-Key: animate-serum-hero-001" \
  -H "Content-Type: application/json" \
  -d '{
    "image_url": "https://cdn.roasos.com/workspaces/.../serum-hero.png",
    "prompt": "The bottle stays still. The steam rises slowly behind it.",
    "duration_seconds": 5,
    "quote_id": "f70b2d51-..."
  }'
```

The answer is `201` with the standard job envelope.

```json
{
  "data": {
    "id": "1e9c4a70-...",
    "kind": "video",
    "model": "seedance-2.5",
    "status": "queued",
    "credits_reserved": 15,
    "created_at": "2026-08-21T11:59:41Z",
    "links": {
      "self": "/v1/jobs/1e9c4a70-...",
      "status": "/v1/jobs/1e9c4a70-.../status",
      "result": "/v1/jobs/1e9c4a70-.../result",
      "events": "/v1/jobs/1e9c4a70-.../events"
    }
  }
}
```

## 6. Poll the job

```bash
curl https://api.roasos.com/v1/jobs/1e9c4a70-.../status \
  -H "Authorization: Bearer roasos_sk_..."
```

Animate a still is an ordinary job, so it carries the five job statuses. Poll every few seconds until the status is `completed`, `failed` or `canceled`. Send a `webhook_url` in the confirm request when you do not want to poll. Refer to [Jobs](/concepts/jobs) and to [Webhooks](/concepts/webhooks).

## 7. Read the result

```bash
curl https://api.roasos.com/v1/jobs/1e9c4a70-.../result \
  -H "Authorization: Bearer roasos_sk_..."
```

```json
{
  "data": { "url": "https://cdn.roasos.com/workspaces/.../serum-hero.mp4" }
}
```

## Errors

| Code                                  | Cause                                                                                                            |
| ------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `invalid_request`                     | Two sources, no source, or no `prompt` and no `auto_prompt`                                                      |
| `reference_media_not_workspace_owned` | The still is not workspace-owned media                                                                           |
| `provider_rejects_face_references`    | The provider of `seedance-2.5` refused a face in the images it received. roasOS refunds the reservation in full. |
| `insufficient_credits`                | The balance does not cover the quote                                                                             |
| `quote_expired`                       | The quote is older than 10 minutes; quote again under a new idempotency key                                      |

Refer to [Errors](/concepts/errors).

## From an agent

The MCP tool is `roasos_static_to_video`. The quote rule is the same. Refer to [Connect an AI agent (MCP)](/mcp).
