> ## Documentation Index
> Fetch the complete documentation index at: https://docs.viggle.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Create a render

> Create and retrieve a video with V1.

Upload a character image and a motion video in one request. The API immediately returns an asynchronous Render; poll it until it is ready.

## Inputs

* `image`: a character image file, or `image_url`
* `motion_video`: a driving video file, or `motion_video_url`
* `background_mode`: `original` (default), `solid`, or `transparent`
* `bg_color`: an `R,G,B` color; only use it with `solid`

## Complete example

<CodeGroup>
  ```python Python theme={null}
  import os
  import time
  import requests

  base_url = "https://apis.viggle.ai/v1"
  headers = {"Authorization": f"Bearer {os.environ['VIGGLE_API_KEY']}"}

  with open("character.png", "rb") as image, open("motion.mp4", "rb") as motion:
      response = requests.post(
          f"{base_url}/renders",
          headers=headers,
          files={"image": image, "motion_video": motion},
      )
  response.raise_for_status()
  render = response.json()

  while render["status"] not in {"ready", "failed", "cancelled"}:
      time.sleep(3)
      response = requests.get(
          f"{base_url}/renders/{render['id']}", headers=headers
      )
      response.raise_for_status()
      render = response.json()

  if render["status"] != "ready":
      error = render.get("error", {})
      raise RuntimeError(f"{error.get('code')}: {error.get('message')}")

  print(render["video_url"])
  ```

  ```javascript JavaScript theme={null}
  const baseUrl = "https://apis.viggle.ai/v1";
  const form = new FormData();
  form.append("image", imageFile);
  form.append("motion_video", motionVideoFile);

  let render = await fetch(`${baseUrl}/renders`, {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.VIGGLE_API_KEY}` },
    body: form,
  }).then((response) => response.json());

  while (!["ready", "failed", "cancelled"].includes(render.status)) {
    await new Promise((resolve) => setTimeout(resolve, 3000));
    render = await fetch(`${baseUrl}/renders/${render.id}`, {
      headers: { Authorization: `Bearer ${process.env.VIGGLE_API_KEY}` },
    }).then((response) => response.json());
  }

  if (render.status !== "ready") throw new Error(render.error?.message);
  console.log(render.video_url);
  ```

  ```bash cURL theme={null}
  curl -X POST "https://apis.viggle.ai/v1/renders" \
    -H "Authorization: Bearer $VIGGLE_API_KEY" \
    -F "image=@character.png" \
    -F "motion_video=@motion.mp4"
  ```
</CodeGroup>

## Response

```json theme={null}
{
  "id": "render_123abc",
  "status": "queued",
  "progress": 0,
  "created_at": "2026-07-17T10:00:00Z"
}
```

When processing, `stage` may be `analyzing`, `rendering`, or `finishing`. A completed job has `status: "ready"` and includes `video_url`. In transparent mode it also includes `alpha_url`.

<Card title="Async jobs and failures" icon="clock" href="/v1/production/async-jobs">
  Learn terminal states, polling, and result retention.
</Card>
