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

# Animate a photo

> Turn a finished Quick Shoot photo into a 5 to 15 second video.

[← UGC videos](/ugc-videos/overview)

**Endpoint:** `POST https://makelocalads.com/api/v1/videos`
**Permission:** `videos:generate`

<Card title="Open in LocalAds" icon="arrow-up-right" href="https://makelocalads.com/app/a1516a75-9be5-461a-93af-c1896a0a3127?tab=studio&type=video-ads&section=library">
  The Animate dialog, from any finished photo in the product's Video Ads
  library.
</Card>

Turn a completed Quick Shoot photo into a short video. The choices match the
app's Animate dialog: 5 to 15 seconds, an aspect ratio, and an optional
prompt. An animation costs 2 credits per second of its length; a 10 second
animation costs 20.

## Usage notes

* The product's plan must include Video Ads.
* `max_credits` refuses the request with `422` before anything is charged if
  the animation would cost more. A render that fails is refunded.
* Send an `Idempotency-Key` so a retry after a network failure never starts
  and charges for a second animation.

## Quick start

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl --request POST \
    --url https://makelocalads.com/api/v1/videos \
    --header "Authorization: Bearer $LOCALADS_API_KEY" \
    --header "Content-Type: application/json" \
    --header "Idempotency-Key: animate-throw-1" \
    --data '{
      "type": "animation",
      "source_creative_id": "3f2a1b0c-9d8e-4f7a-b6c5-d4e3f2a1b0c9",
      "duration_seconds": 10,
      "aspect_ratio": "9:16",
      "prompt": "Slow push-in as steam rises from the cup."
    }'
  ```

  ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const response = await fetch("https://makelocalads.com/api/v1/videos", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.LOCALADS_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": "animate-throw-1",
    },
    body: JSON.stringify({
      type: "animation",
      source_creative_id: "3f2a1b0c-9d8e-4f7a-b6c5-d4e3f2a1b0c9",
      duration_seconds: 10,
      aspect_ratio: "9:16",
      prompt: "Slow push-in as steam rises from the cup.",
    }),
  });

  const video = await response.json();
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  import os
  import requests

  response = requests.post(
      "https://makelocalads.com/api/v1/videos",
      headers={
          "Authorization": f"Bearer {os.environ['LOCALADS_API_KEY']}",
          "Content-Type": "application/json",
          "Idempotency-Key": "animate-throw-1",
      },
      json={
          "type": "animation",
          "source_creative_id": "3f2a1b0c-9d8e-4f7a-b6c5-d4e3f2a1b0c9",
          "duration_seconds": 10,
          "aspect_ratio": "9:16",
          "prompt": "Slow push-in as steam rises from the cup.",
      },
  )

  video = response.json()
  ```
</CodeGroup>

## Input fields

<ParamField body="type" type="string" required>
  `animation`. Use `ugc` for a
  [UGC voiceover video](/ugc-videos/create-a-ugc-video) instead.
</ParamField>

<ParamField body="source_creative_id" type="string" required>
  A completed Quick Shoot photo, or a video made from one. Find photo ids
  with `GET /products/{product_id}/creatives?type=photoshoot`.
</ParamField>

<ParamField body="duration_seconds" type="integer">
  The length in seconds, 5 to 15. Defaults to `10`.
</ParamField>

<ParamField body="aspect_ratio" type="string">
  `9:16` (Portrait), `16:9` (Landscape), or `1:1` (Square). Defaults to
  `9:16`.
</ParamField>

<ParamField body="prompt" type="string">
  Optional motion direction, 1 to 2,000 characters.
</ParamField>

<ParamField body="max_credits" type="integer">
  Refuse the request with `422 credit_limit_exceeded`, before anything is
  charged, if it would cost more than this many credits.
</ParamField>

<Accordion title="Complete JSON schema">
  ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
  {
    "type": "object",
    "additionalProperties": false,
    "required": ["type", "source_creative_id"],
    "properties": {
      "type": { "type": "string", "const": "animation" },
      "source_creative_id": { "type": "string", "format": "uuid" },
      "duration_seconds": { "type": "integer", "minimum": 5, "maximum": 15, "default": 10 },
      "aspect_ratio": { "type": "string", "enum": ["9:16", "16:9", "1:1"], "default": "9:16" },
      "prompt": { "type": "string", "minLength": 1, "maxLength": 2000 },
      "max_credits": { "type": "integer", "minimum": 1 }
    }
  }
  ```
</Accordion>

## Response

`202 Accepted`, with the video and a `Location` header. Poll
`GET /videos/{video_id}` with backoff until `status` is `completed` or
`failed`; see [Polling](/concepts/polling). The response shape is the same as
a UGC video's, with `type: "animation"` and the source photo in
`source_creative_id`.

## Errors

| Status | Code | Meaning |
| - | - | - |
| `404` | `creative_not_found` | The source photo is not available to you |
| `422` | `request_rejected` | The source is not a completed Quick Shoot photo |
| `422` | `plan_required` | Your plan does not include Video Ads |
| `422` | `insufficient_credits` | Not enough credits. Nothing was charged |
| `422` | `credit_limit_exceeded` | The animation would cost more than `max_credits` |

See [Errors and retries](/concepts/errors) for the error format.
