---
name: frameloop-video-creation-assistant
version: 1.0.0
title: AI Video Creator (via frameloop.ai)
description: Operate the FrameLoop Public API for AI Video creation, credit estimation, render orchestration, and status tracking. Use when the user asks to create videos from script or idea input, estimate credits, trigger renders, poll status, or check account credits.
license: MIT
author: Karunakar <kaga@frameloop.ai>
homepage: https://frameloop.ai
repository: https://frameloop.ai
keywords: frameloop, api, ai video generation, faceless video, social media marketing, video marketing, rendering, automation
metadata:
  openclaw:
    requires:
      env:
        - FRAMELOOP_API_KEY
    primaryEnv: FRAMELOOP_API_KEY
---

# FrameLoop Public API Assistant

Autonomously create and render AI-generated videos via the [FrameLoop](https://frameloop.ai) API.

## Setup

1. Create a FrameLoop account at [frameloop.ai](https://frameloop.ai)
2. Generate an API key (Settings → API)
3. Store your API key in workspace `.env`:
   ```
   FRAMELOOP_API_KEY=flk_live_xxxxx
   ```

## Auth

All requests use Bearer token:
```
Authorization: Bearer <FRAMELOOP_API_KEY>
```

Base URL: `https://frameloop.ai`

## Core Workflow

### 1. Fetch Options

You MUST call this first. Use the response to select valid values for `voiceProvider`, `voiceId`, `visualStyle`, `aspectRatio`, `videoType`, `music.id` (or legacy `musicId`), `storyType`, `animationModel` (when `videoType` is `ai_animated_video`), `animationResolution` (720p or 1080p when `videoType` is `ai_animated_video`; 1080p requires Standard tier or higher), and (when setting captions) `captionOptions` (`styles`, `alignments`, `fontSizeScale`, `highlightColor`, `textCase` rules). NEVER invent or guess these values.

**Voice provider constraint:** Use `XI_LABS` if the script content is over 3,000 characters or if the idea `duration` exceeds 120 seconds. `GEMINI` is not supported for long-form content and will return a `VALIDATION_INVALID_FIELD` error.

```
GET /api/v1/options
```

Returns `requestFields` (supported enums), `captionOptions` (caption styles, alignments, size constraints), `voiceOptions` (keyed by provider), and `musicOptions` (`auto` — AI-recommended system track from the script; `system` — explicit library tracks; `custom` — user-uploaded tracks).

### 1b. Characters (optional, consistent visuals)

Characters live in your **library** (same as Character Store). On **`POST /api/v1/project`**, reference them **only by id**:

```json
"characters": [{ "id": "<from GET /api/v1/characters>" }]
```

You cannot send inline `name` / `description` / `referenceImage` on the public API — create library rows first, then pass ids.

**Image model:** When `videoType` is `ai_image_video`, set `imageModel` (`standard` / `advanced` / `premium` / `max`) so it supports your roster: `standard` — no characters; `advanced` — up to 2; `premium` — up to 14; `max` — up to 16. Omit `imageModel` to skip this roster constraint. For `ai_animated_video`, still scenes use a **base image model fixed by `animationModel`** (standard, advanced, premium, max), with automatic upgrades when reference images exceed limits—`imageModel` on the payload does not choose the still generator. `stock_video` ignores characters for generation.

**Animation model:** When `videoType` is `ai_animated_video`, set `animationModel` to exactly one value from `requestFields.animationModel` in `GET /api/v1/options` (`standard`, `advanced`, `premium`, or `max`). This selects both the motion (I2V) tier and the default still-image backend for scene frames:

- **`standard`** — default/budget motion (Seedance). Typical **minimum ~4s** billed per animated scene when timing would be shorter.
- **`advanced`** — richer motion with **sound effects** in the clip (audio always on).
- **`premium`** — Cinematic motion with native audio.
- **`max`** - Highest-tier motion with sound.

Use `POST /api/v1/project/estimate` for exact credits. Omit `animationModel` to use **`standard`**.

**Animation resolution:** When `videoType` is `ai_animated_video`, optionally set `animationResolution` to `720p` (default) or `1080p` from `requestFields.animationResolution`. **1080p** uses higher animation credits per second and requires a **Standard** (or higher) subscription; requests from Basic/Free tiers receive `SUBSCRIPTION_FEATURE_BLOCKED`.

**List**

```
GET /api/v1/characters
```

**Create** (paid plans only; image must be **JPEG, PNG, or WebP**):

- JSON — `Content-Type: application/json`  
  `{ "name": "<string>", "imageUrl": "https://..." }` — server downloads the URL (HTTPS only; SSRF-hardened).
- Multipart — `Content-Type: multipart/form-data` with fields `name` (string) and `image` (file).

Response includes `{ id, name, imageUrl }` for use in `characters` on project create.

**Delete** (free a slot)

```
DELETE /api/v1/characters/{id}
```

**Errors**

- `CHARACTER_FREE_TIER_BLOCKED` (402) — library not available on free tier.
- `CHARACTER_LIMIT_REACHED` (409) — plan storage limit; details include `used`, `limit`, `tier`.

### 2. Estimate Credits

Send the exact payload shape used for project creation. No project is created and no credits are charged.

```
POST /api/v1/project/estimate
Body: {
  "inputType": "script",
  "content": "<your script text>",
  "voiceProvider": "<from options>",
  "voiceId": "<from options>",
  "visualStyle": "<from options>",
  "aspectRatio": "9:16",
  "videoType": "ai_image_video",
  "idempotencyKey": "<uuid>",
  "pacing": "medium",
  "captions": {
    "enabled": true,
    "style": "<from captionOptions.styles[].id>",
    "alignment": "bottom",
    "highlightColor": "#FDB000",
    "fontSizeScale": 1,
    "textCase": "default"
  }
}
```

Returns `estimatedCredits`, `hasSufficientCredits`, and `availableCredits`.

### 3. Create Project

```
POST /api/v1/project
Body: {
  "title": "<optional>",
  "inputType": "script",
  "content": "<your script text>",
  "voiceProvider": "<from options>",
  "voiceId": "<from options>",
  "narrationMode": "single_track",
  "music": {
    "id": "<from options: musicOptions.system[].id, musicOptions.custom[].id, no_music, or auto>",
    "volume": 0.15,
    "startTime": 0,
    "enableFadeIn": false,
    "fadeIn": 1.5,
    "enableFadeOut": false,
    "fadeOut": 1.5
  },
  "visualStyle": "<from options>",
  "aspectRatio": "9:16",
  "videoType": "ai_image_video",
  "idempotencyKey": "<uuid>",
  "captions": { "enabled": false }
}
```

Optional: omit `captions` for defaults, or pass `captions` with fields from `GET /api/v1/options` `captionOptions` (all caption fields are optional; use `enabled: false` to disable burned-in captions).

**Characters:** Optional `characters` array: **only** `{ "id": "<library id>" }` per entry (see §1b). Create entries with `POST /api/v1/characters` before referencing them.

**Music:** Prefer the nested `music` object (track + editor settings). Omitted `music` fields use product defaults (volume 0.15, startTime 0, fadeIn/fadeOut 1.5, fades disabled). Legacy top-level `musicId` / `customMusicUrl` still work but are **mutually exclusive** with `music` — send one style only.

For `inputType: "idea"`, also include `duration` (number, seconds), `language`, and `storyType` (from options):

```
POST /api/v1/project
Body: {
  "inputType": "idea",
  "content": "<your idea text>",
  "duration": 30,
  "language": "en",
  "storyType": "<from options>",
  "voiceProvider": "<from options>",
  "voiceId": "<from options>",
  "visualStyle": "<from options>",
  "aspectRatio": "9:16",
  "videoType": "ai_image_video",
  "idempotencyKey": "<uuid>"
}
```

Returns `projectId` — store this for all subsequent calls.

### 4. Poll Project Status

```
GET /api/v1/project/<projectId>
```

Returns status: `queued`, `in_progress`, `complete`, `failed`. Only proceed to render when status is `complete`.

When status is **`complete`**, `data` includes **`scenes`**: each scene has `id`, `sceneNo`, `dialogueText`, and **`media`** (array; primary item only for now). Video items include `type`, `url`, and **`stockVideoId`** when the clip is stock footage (use this id with stock suggestions / PATCH). For `queued`, `in_progress`, or `failed`, the `scenes` field is omitted. No stock-provider API calls are made on this route.

#### Stock suggestions (per scene)

```
GET /api/v1/project/<projectId>/scenes/<sceneId>/stock-suggestions
```

Returns `data.sceneId` and **`data.suggestions`**: each item is `{ type: "video", stockVideoId, keyword, url }`. Use **`stockVideoId`** when calling PATCH for stock replacement. `url` is a convenience playback link.

#### Update scene media, music, and captions (batch)

```
PATCH /api/v1/project/<projectId>
Body: {
  "actions": [
    {
      "action": "replace_scene_media",
      "sceneId": "<scene id from GET scenes>",
      "media": {
        "type": "video",
        "stockVideoId": 12345678
      }
    },
    {
      "action": "replace_scene_media",
      "sceneId": "<other scene id>",
      "media": {
        "type": "video",
        "url": "https://example.com/my-video.mp4"
      }
    }
  ]
}
```

- **Stock:** set **`stockVideoId`** only (from `stock-suggestions` or `GET` scene `media.stockVideoId`). Server fetches full stock metadata (SD/HD) and stores a proper stock `VideoElement`.
- **Custom:** set **`url`** only (your hosted video). Stored as an uploaded-style clip (`src` only; no required SD/HD pair).
- Provide **exactly one** of `stockVideoId` or `url` per replace action, not both.

**Change background music** (only after `GET /api/v1/project/<projectId>` reports `status: "complete"`):

```
PATCH /api/v1/project/<projectId>
Body: {
  "actions": [
    { "action": "set_music", "music": { "id": "auto" } }
  ]
}
```

Or use an external mp3 (same rules as create — do not combine `music.id` with `music.customMusicUrl`):

```
PATCH /api/v1/project/<projectId>
Body: {
  "actions": [
    { "action": "set_music", "music": { "customMusicUrl": "https://example.com/track.mp3" } }
  ]
}
```

- **`music`:** partial update (like captions). Include `music.id` or `music.customMusicUrl` to change the track, and/or volume/startTime/fade fields. Omitted settings keep stored values. Legacy `musicId` / `customMusicUrl` on the action still work but cannot be combined with `music`.
- **Track ids:** use `musicOptions.auto.id` (typically `auto`), a track from `musicOptions.system` / `musicOptions.custom`, or `no_music`. `auto` uses the project's stored transcript summary (no extra summarization).
- **At most one** `set_music` per request. If the project is not yet `complete`, `set_music` returns `failed` for that action (no DB write).
- On success, the result may include **`resolvedMusic`** (full merged state: `src`, `resolvedVia`, volume, fades, etc.) in addition to `resolvedMusicUrl`.
- Re-render the project if you need the new music in the exported MP4.

**Change captions** (only after `GET /api/v1/project/<projectId>` reports `status: "complete"`). Same fields as create body `captions`; include **at least one** property — omitted fields keep their current values. **At most one** `set_captions` per request.

```
PATCH /api/v1/project/<projectId>
Body: {
  "actions": [
    {
      "action": "set_captions",
      "captions": {
        "enabled": true,
        "style": "NOAH",
        "alignment": "bottom",
        "highlightColor": "#FDB000",
        "fontSizeScale": 1,
        "textCase": "default"
      }
    }
  ]
}
```

Use `captionOptions` from `GET /api/v1/options` for allowed `style`, `alignment`, `fontSizeScale`, and `textCase` values. Re-render to bake caption changes into the MP4.

Response `data.results` lists one entry per input action, in order. Each item includes **`action`**: `replace_scene_media` results have `sceneId`, `status`, optional `message`; `set_music` results have `status`, optional `message`, and on success may include `resolvedMusicUrl`, `resolvedVia`, `wasAutoFallbackToNoMusic`, and `resolvedMusic` (full merged music state); `set_captions` results have `status`, optional `message`, and on success may include `resolvedCaptions` (full merged caption settings after the patch).

### 5. Start Render

```
POST /api/v1/project/<projectId>/render
Body: {
  "idempotencyKey": "<uuid>",
  "webhookUrl": "https://your-server.com/webhook"  // optional
}
```

Returns `renderTaskId` — store alongside `projectId`.

### 6. Poll Render Status

```
GET /api/v1/project/<projectId>/render
```

Returns status: `not_started`, `queued`, `processing`, `completed`, `failed`, `cancelled`. When `completed`, `outputUrl` is present.

### 7. Check Account Credits

```
GET /api/v1/account/credits
```

Returns `subscription`, `topup`, and `total` credit balances.

## Idempotency Rules

- Generate a unique `idempotencyKey` for every distinct action using a UUID (e.g. `crypto.randomUUID()`) or a timestamp-action-hash format (e.g. `1740220800000-create-abc123`). Never reuse a key across different actions. Store each key alongside its `projectId`/`renderTaskId` for safe retries.
- Replaying the same key + payload returns a prior snapshot (`200`). A new action returns `201`.
- Reusing a key with a different payload returns `IDEMPOTENCY_CONFLICT`.

## Error Codes

Account/auth: `AUTH_INVALID_API_KEY`, `AUTH_KEY_REVOKED`, `CREDITS_INSUFFICIENT`, `RATE_LIMIT_EXCEEDED`

Request/task: `VALIDATION_INVALID_FIELD`, `PROJECT_UNSUPPORTED_TASK_TYPE`, `CONCURRENCY_LIMIT_EXCEEDED`, `RENDER_ALREADY_IN_PROGRESS`, `RESOURCE_NOT_FOUND`, `IDEMPOTENCY_CONFLICT`, `INTERNAL_ERROR`

## Clarification Before Creating

Before calling `POST /api/v1/project`, confirm any payload fields that are ambiguous or not specified by the user. Ask concisely in a single message grouping all missing details. Do not assume defaults silently for these fields:

- **`inputType`** — is this a finished script or a rough idea to expand?
- **`content`** — what is the script or idea text?
- **`aspectRatio`** — what format? (vertical `9:16` for social shorts, `16:9` for landscape, `1:1` for square)
- **`videoType`** — should visuals use AI-generated images (`ai_image_video`), AI animation (`ai_animated_video`), or stock footage (`stock_video`)?
- **`animationModel`** *(only when `videoType` is `ai_animated_video`)* — `standard` (default), `advanced`, `premium`, or `max` per `GET /api/v1/options`; picks motion tier **and** base still-image model; use estimate for credits.
- **`animationResolution`** *(only when `videoType` is `ai_animated_video`)* — `720p` (default) or `1080p` per `GET /api/v1/options`; 1080p costs more per second and requires Standard+.
- **`imageModel`** *(when `videoType` is `ai_image_video`)* — optional; if set with `characters`, roster must fit the tier. For `ai_animated_video` it does not select the still generator.
- **`voiceProvider` / `voiceId`** — any voice preference? If unsure, show a focused list of relevant options, depending on video type, from `GET /api/v1/options` for the user to choose.
- **`music` / track** — should the video have background music? Prefer `music: { id: ... }`. If the user wants the API to pick a system track from the script tone, use `musicOptions.auto.id` (typically `auto`). If they want a specific track, use an id from `musicOptions.system` (or `musicOptions.custom`). If no music, use `no_music`. Do not combine `music.id` with `music.customMusicUrl`.
- **`duration`** *(idea input only)* — how long should the video be, in seconds?
- **`language`** *(idea input only)* — what language should the narration be in?
- **`narrationMode`** — always use `single_track` unless user specifies per-scene track option.
- **`captions`** *(optional)* — omit for defaults; if the user wants captions off, set `captions.enabled` to `false`. Otherwise pick `style` from `captionOptions.styles`, `alignment` from `captionOptions.alignments`, `fontSizeScale` between 0 and 2 in steps of 0.05, `highlightColor` as hex or a CSS color string, and `textCase` from `captionOptions.textCase.values` (`default`, `uppercase`, `lowercase`, `titlecase`). `textCase` has no effect when `style` is `BEAST` (BEAST is always uppercase).

Only ask for fields that are genuinely unclear from context. If the user's prompt makes a field obvious, do not ask about it.

## Operational Defaults

- You MUST call `GET /api/v1/options` first. NEVER invent or guess a `voiceId`, `aspectRatio`, `visualStyle`, `music.id` (or legacy `musicId`), `storyType`, `animationModel`, `animationResolution`, `captions.style`, or `captions.textCase`. Only use exact values returned by the options endpoint (including `musicOptions.auto.id` for AI-chosen music, or a concrete id from `musicOptions.system` / `musicOptions.custom`).
- Use the estimate endpoint before project creation for cost-sensitive flows.
- When polling project or render status, wait a minimum of 10 seconds between requests. Use exponential backoff starting at 10s (10s → 20s → 40s → 80s). Stop polling after reaching a terminal state (`complete`/`failed` for projects; `completed`/`failed`/`cancelled` for renders).
- Treat `failed` and `cancelled` as terminal states requiring manual intervention.
- Persist `projectId`, `renderTaskId`, and each `idempotencyKey` for safe retries.
