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

# Cinematic Studio Example

> From a story brief to a finished short film with speaking characters

## Overview

Cinematic Studio makes a short film from a few lines of story: Hooked writes the storyboard, casts the characters, draws and films every scene with a video model that speaks, adds subtitles and music and renders the video. Use it for story-driven ads, short dramas, trailers and anime shorts.

Every request needs `storyBrief` (up to 4,000 characters), `durationSeconds` (10 to 120) and `model`. The create call answers in a few seconds with a `projectId` and status `processing`; the storyboard, the cast and the video come after that (see [Getting the video](#getting-the-video)).

## A 30-second drama

```javascript theme={null}
const response = await fetch('https://api.hooked.so/v1/project/create/cinematic', {
  method: 'POST',
  headers: {
    'x-api-key': process.env.HOOKED_API_KEY,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'The Lighthouse',
    storyBrief: 'A lighthouse keeper finds a message in a bottle during a storm. She reads it aloud, hesitates, then writes an answer and throws the bottle back into the sea.',
    durationSeconds: 30,
    model: 'grok_imagine_video',
    storyTone: 'drama',
    presetId: 'cinematic',
    aspectRatio: 'ratio_16_9',
    musicId: '1',
    webhook: 'https://yoursite.com/hooked-webhook?token=YOUR_SECRET',
    metadata: { campaign: 'storm-teaser' }
  })
});

const { data } = await response.json();
console.log('Project ID:', data.projectId); // status: "processing"
```

## A vertical anime short

`ratio_9_16` makes it vertical for TikTok, Reels and Shorts. `ratio_1_1` is only filmed by Kling and Seedance.

```python theme={null}
import os
import requests

response = requests.post(
    "https://api.hooked.so/v1/project/create/cinematic",
    headers={"x-api-key": os.environ["HOOKED_API_KEY"]},
    json={
        "storyBrief": "Two rival street racers have to team up when their cars break down in the middle of the desert.",
        "durationSeconds": 45,
        "model": "kling_3_0",
        "storyTone": "action",
        "presetId": "anime",
        "aspectRatio": "ratio_9_16",
        "webhook": "https://yoursite.com/hooked-webhook?token=YOUR_SECRET",
    },
)

print(response.json()["data"]["projectId"])
```

## Your own dialogue, word for word

With `preserveExactStoryText: true` the storyboard keeps the lines in your brief instead of rewriting them. `language` is `en` (default) or `es`.

```bash theme={null}
curl -X POST "https://api.hooked.so/v1/project/create/cinematic" \
  -H "x-api-key: $HOOKED_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "storyBrief": "Lucía mira por la ventana y dice: \"Mañana me voy\". Su abuelo sonríe y responde: \"Ya lo sabía\".",
    "durationSeconds": 15,
    "model": "veo_3_fast",
    "presetId": "pixar",
    "language": "es",
    "preserveExactStoryText": true
  }'
```

## Casting your own characters

Characters you saved in the dashboard (**Studio → Characters**) can play in the film: list them with [`GET /v1/character/list`](/api-reference/character/list) and send their ids in `characterIds` (up to 6) and name them in the brief as `@Name`. They keep their look, their voice and their photos. An id that is not your team's answers `400`.

## Picking a model

The model films every scene with its own audio, so the cast speaks. Every model below can be used; Seedance models can't film the photorealistic styles (`realistic`, `cinematic`), and that pair answers `400`.

| `model` | Notes |
| - | - |
| `grok_imagine_video` | Strong motion and audio; 4 to 15 s per scene |
| `veo_3_fast`, `veo_3`, `veo_3_1_lite` | Google Veo 3.1; 4, 6 or 8 s per scene |
| `kling_3_0` | 5 or 10 s per scene; square too |
| `seedance_2_0`, `seedance_2_0_fast`, `seedance_2_0_mini` | 5 or 10 s per scene; square too; no photorealistic styles |
| `gemini_omni_flash` | Runs on Gemini (BYOK teams need a Gemini key) |

## Getting the Video

The create call answers as soon as the request is checked and the project exists:

```json theme={null}
{
  "success": true,
  "data": { "projectId": "clx9p2k4m0001abcd1234efgh", "status": "processing" },
  "message": "Cinematic successfully created"
}
```

Then the storyboard, the cast portraits, the scene frames, the clips, the subtitles and the render run in the background. Wait for your webhook (`COMPLETED` with `data.url`, or `FAILED` with `videoId: null` if the project fails), or poll the project:

```javascript theme={null}
async function waitForFilm(projectId) {
  while (true) {
    const res = await fetch(`https://api.hooked.so/v1/project/${projectId}`, {
      headers: { 'x-api-key': process.env.HOOKED_API_KEY }
    });
    const { data } = await res.json();

    if (data.video?.status === 'COMPLETED') return data.video.url;
    if (data.status === 'failed' || data.video?.status === 'FAILED') {
      throw new Error(data.video?.message || data.message || 'Video failed');
    }

    await new Promise((resolve) => setTimeout(resolve, 30000)); // every ~30 s
  }
}
```

A 30-second film usually takes several minutes: every scene is filmed by the video model before the final cut is rendered.

## Credits

* **Checked up front.** Before anything is created, the whole run is priced (a portrait per library character without a photo, plus a start frame and a clip for every 6 seconds of `durationSeconds`). A short balance answers `402 INSUFFICIENT_CREDITS` with `creditsNeeded` and `creditsAvailable`, and nothing is created. It is priced again once the storyboard says how many scenes and characters it really has; if the balance no longer covers it, the project fails before anything is charged.
* **Charged in stages.** Portraits once the storyboard is written, scene frames right after, clips when every frame is ready, subtitles at the end (skipped if the balance can't cover them).
* **Refunds on failure.** A portrait, frame or clip that fails is refunded, along with everything paid for that never started (and the portraits, if the run fails before the frames start). Finished pieces stay in your media library.
* **What it has cost.** The project's `usedCredits` adds up every stage charged so far, minus the refunds.

<Note>
  Teams in **bring-your-own-keys** mode are not charged credits, but need their own **OpenRouter** key in Settings → AI keys, and a **Gemini** key for `gemini_omni_flash`. A missing key answers `402` with `code: "missing_credentials"` and the list in `missingProviders`, before anything is created.
</Note>

<Warning>
  Send an [`Idempotency-Key`](/guides/idempotency) header to retry safely: if a request times out or answers 5xx, sending it again with the same key and body returns the first answer instead of creating (and charging) the project twice.
</Warning>

## Tips

<Tip>**Give the story a turn**: a character, a place and something that changes makes a better storyboard than a mood alone</Tip>
<Tip>**Match the tone**: `storyTone` sets genre and pacing (for example `drama`, `comedy`, `thriller`; the full list is in [Get Catalog](/api-reference/catalog/get) with `name=cinematic`)</Tip>
<Tip>**Keep it short to start**: 15 to 30 seconds is two to five scenes, quick to film and cheap to iterate on</Tip>
<Tip>**Original characters only**: briefs about real, identifiable or famous people are refused with a `400`</Tip>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.