# Create a video

> A camera move or fly-through from a single still, priced by model and length.

Source: https://developers.mnml.ai/docs/videos

`POST https://api.mnml.ai/v2/videos`

## Example request

**curl**

```bash
curl -X POST https://api.mnml.ai/v2/videos \
  -H "Authorization: Bearer $MNML_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "job_id": "10482",
    "model": "v2.0-flash",
    "duration_seconds": 10,
    "camera_movement": "orbit-right"
  }'
```

**TypeScript**

```ts
const res = await fetch("https://api.mnml.ai/v2/videos", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.MNML_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "job_id": "10482",
    "model": "v2.0-flash",
    "duration_seconds": 10,
    "camera_movement": "orbit-right"
  }),
});

const data = await res.json();
```

**Python**

```python
import os, requests

res = requests.post(
    "https://api.mnml.ai/v2/videos",
    headers={"Authorization": f"Bearer {os.environ['MNML_API_KEY']}"},
    json={
      "job_id": "10482",
      "model": "v2.0-flash",
      "duration_seconds": 10,
      "camera_movement": "orbit-right"
    },
)

data = res.json()
```

## Example response (202)

```json
{
  "success": true,
  "data": {
    "id": "10485",
    "status": "queued",
    "charged_usd": 0.99,
    "credits_charged": 100,
    "replayed": false,
    "notes": []
  }
}
```

Returns `202` straight away with the job and what it charged. Poll [GET /v2/jobs/{id}](https://developers.mnml.ai/docs/jobs) for the result; a job that fails is refunded. A clip takes a few minutes.

> [!WARNING]
> Video needs an account that has topped up its API wallet. On an account that never has, it returns `403 PLAN_REQUIRED` and nothing is charged.

| Model | Per clip |
| --- | --- |
| v2.0 · Flash (`v2.0-flash`) | $0.99 per 10s clip |
| v2.0 · Cinematic (`v2.0`) | $1.98 per 10s clip |

## Headers

- `Authorization` (string, required): `Bearer mk_live_…`: your API key. See [Authentication](https://developers.mnml.ai/docs/authentication).
- `Content-Type` (string, required): `application/json`, or `multipart/form-data` to send a file.
- `Idempotency-Key` (string): Your own key, 1 to 255 visible ASCII characters. A retry with the same key and request within 24 hours returns the first answer instead of running again.

## Body

- `image` (string): The JPEG, PNG or WebP to work on, up to 15 MB: a public `https://` link we fetch, a `data:image/…;base64,` URI, or bare base64. With multipart form data, attach the file itself (`-F image=@house.jpg`). Send one of `image` or `job_id`.
- `job_id` (string): A finished job of yours: work on its output without downloading it first.
- `model` (string, default `v2.0 · Flash`): A video model’s id or name from `video_models` in [GET /v2/engines](https://developers.mnml.ai/docs/engines).
- `duration_seconds` (integer, default `10`): The clip’s length. The lengths each model offers, and their prices, are in `video_models`.
- `camera_movement` (string, default `static`): `static`, `auto`, a preset such as `orbit-right`, or up to two moves: `"dolly-in,tilt-up"`.
- `motion` (string, default `subtle`): How much moves in the scene: `subtle`, `balanced` or `dynamic`.
- `prompt` (string): What should happen in the clip.
- `end_frame` (string | object): A last frame to move towards: the image itself, as `image` is sent, or an object naming it.
  - `image` (string): The frame: a public `https://` link we fetch, a `data:image/…;base64,` URI, or bare base64. With multipart form data, attach the file itself. Send one of `image` or `job_id`.
  - `job_id` (string): The frame, as a finished job of yours.
- `cinematic` (boolean): v1.1 only: the Cinematic variant, which needs `end_frame`.
- `webhook_url` (string): A public `https://` URL to send this job’s settle event to, signed like every [webhook](https://developers.mnml.ai/docs/webhooks), on top of your account’s endpoints.

## Response

On success (`202`): Started. A clip takes minutes; poll GET /v2/jobs/{id} for it. The JSON is `{ "success": true, "data": … }`, and inside `data`:

- `id` (string, required): The job. Read it at [GET /v2/jobs/{id}](https://developers.mnml.ai/docs/jobs).
- `status` (string, required): The job’s status when it was accepted, usually `queued`. Values: `queued`, `processing`, `succeeded`, `failed`, `canceled`.
- `charged_usd` (number, required): What this call charged, in US dollars. A job that fails is refunded.
- `replayed` (boolean, required): True when this is the stored answer to an earlier identical request with the same `Idempotency-Key`: nothing ran or was charged again. See [Idempotency](https://developers.mnml.ai/docs/idempotency).
- `notes` (string[], required): What was accepted but not applied, in plain words: a setting that does not fit the engine, a seed it does not take. Empty when everything applied.

**Response headers (5)**

- `X-Request-Id` (string): This request, for support and the console log.
- `RateLimit-Limit` (integer): Requests this key may make per minute.
- `RateLimit-Remaining` (integer): Requests left in the current minute.
- `RateLimit-Reset` (integer): Seconds until the current minute resets.
- `Idempotent-Replayed` (string): Present, as `true`, when this is the stored answer to an earlier request.

## Status codes

| Status | Meaning |
| --- | --- |
| 202 | Started. A clip takes minutes; poll GET /v2/jobs/{id} for it. |
| 401 | The API key is missing, malformed, unknown or revoked. |
| 402 | The balance does not cover this call. |
| 409 | The Idempotency-Key was reused for a different request, or is still in progress. |
| 429 | A limit was reached: requests per minute, or a daily or concurrency limit. |
| Other | Any other error, in the same envelope. |

Every error answers `{ "success": false, "error": { "code", "message" } }`. What each code means, and what to do, is on [Errors](https://developers.mnml.ai/docs/errors).
