---
name: hubfluencer-create
description: Generate a finished, post-ready video ad/short with Hubfluencer (prompt in, MP4 out). Use when the user asks to make an ad, a short, or a social clip, or to turn a product/idea into a video. Drives Hubfluencer end-to-end via the @hubfluencer/mcp tools or the REST API.
---

# Make videos with Hubfluencer

Hubfluencer turns a text prompt into a finished, post-ready MP4 (AI footage, voice-over, music, captions). You drive it via the `@hubfluencer/mcp` tools or, if MCP is not wired up, directly against the REST API with `bash` + `curl`.

## Connect (one-time)

The user buys credits and creates an access token in the Hubfluencer app: **Settings -> Access tokens**, with the **"Generate videos"** scope (`video:generate` + `video:read`). The token is supplied to the MCP server via the `HUBFLUENCER_API_TOKEN` env var.

Alternatively, the user can connect without copy-pasting anything: run `npx -y @hubfluencer/mcp login` (it prints a URL and a short code; the user approves the connection in the signed-in Hubfluencer app). A scoped token is saved to `~/.hubfluencer/credentials.json` and the MCP server picks it up automatically.

**Never invent a token. Never echo a token in your output.**

## The happy path: one shot

The simplest path is the `make_video` tool — prompt in, finished MP4 out. It creates the project, starts generation, polls to completion, and downloads the file.

```
make_video({ prompt: "A 15s ad for a cold-brew coffee brand, energetic, gen-z", save_path: "./cold-brew-ad.mp4" })
```

- `kind` defaults to `auto`: a fast single-clip short for simple prompts, or a multi-scene editor ad for story / multi-scene prompts.
- Override with `kind: "short"` or `kind: "editor"` when the user is specific.
- Generation takes a few minutes. That is normal — do not assume failure.
- If `make_video` returns `terminal: false`, the render is still going. Call `wait_for_completion({ slug, kind })` again until it finishes.
- **Don't ship a bare short.** For a short, pass `headline` (the on-screen TITLE), `subheadline` (secondary title), and a fitting `music_vibe`/`theme` right in the `make_video` call — these are SHORTS fields (`theme` applies to both kinds; `headline`/`subheadline`/`music_vibe` are ignored for editor). For the user's own product image, brand logo, or closing card, the one-shot can't attach files — use the granular path. If no title/assets were given, infer a sensible `headline`/`subheadline` from the prompt, or ask.

## The granular path: control and recovery

Use this when you need more control, or to recover after an interruption:

1. `get_credits` — check the balance before spending.
2. `create_short` or `create_editor_ad` — create the draft (0 credits).
3. `generate_short` (shorts) or the autopilot flow (editor ads) — start the render.
4. `wait_for_completion({ slug, kind })` — poll until terminal.
5. `download_result` — save the MP4.

**Short creative fields** (`create_short`, also carried by `make_video`): `headline` (on-screen TITLE, ≤160), `subheadline` (secondary title, ≤200), `music_vibe` (Upbeat default / Cinematic / Minimal / Luxury / Playful / Jazz), `theme` (none / realistic default / cinematic / anime / sci_fi / fantasy / noir / superhero / horror / mockumentary / sports / gaming / retro_80s / minimalist / cyberpunk), plus `text_position` (top/center/bottom), `text_animation` (reveal/typewriter/fade_in/pop/bounce), `font_family`, `music_instruments`. Populate at least the title/subtitle and a vibe. (REST: send these in the `POST /api/shorts` body, mapping the last four to `short_text_position` / `short_text_animation` / `short_font_family` / `music_instruments`.)

Helpful extras: `get_status` (one-off status check), `list_voices` (pick a voice-over voice), `list_projects` (see existing work).

## Granular control: author the full ad yourself (no autopilot)

When you want to direct every step — write the scenario, set the exact scene count, hand-write each scene's prompt and the narration — drive the editor step by step instead of autopilot. Autopilot (`create_editor_ad`) stays the default one-shot path; this is the controlled path, and it runs entirely agent-driven (no human approval step in the middle):

1. `create_editor_draft { product_prompt, language?, theme?, voice_id?, export_aspect_ratio?, project_intent? }` — creates an editor project **without** starting autopilot (0 credits). Returns a `slug`. (REST: `POST /api/editor`.)
2. `generate_scenario { slug, segments_count? }` — let the model draft a scenario (**1 AI assist**, `segments_count` 3..10, server default 5) — **or** `set_scenario { slug, scenario_prompt }` to write your own (free, 1..50000 chars).
3. `get_editor { slug }` — **the REVIEW step.** Read back `scenario_prompt`, `segments[]`, `narration_script`/`narration_status`, `music`, `latest_render`, `segments_count`, `ai_assist_quota`. Decide what to change before spending credits.
4. `set_scene_count { slug, count }` — grow/shrink the storyboard (1..20). Each AI scene is a fixed **8s**, so this sets the ad's length (`count × 8s`); uploaded clips keep their own length. Only deletes **trailing un-generated** scenes; never a completed or in-progress one.
5. `set_segment_prompt { slug, segment_id, prompt }` per scene — hand-write each scene (free, 1..2000 chars).
6. `generate_segment { slug, segment_id }` one at a time, **or** `generate_all_segments { slug }` to run every pending scene **sequentially in position order** (each finishes before the next, so visual continuity carries forward). **5 credits per scene.** Stop on the first 402 and report.
7. `set_narration_script { slug, script }` (write your own, free, ≤12000 chars) **or** `generate_narration { slug }` (**1 AI assist**). Then write a music `description` (the ≤1200-char direction).
8. `generate_voice { slug, voice_id }` (**3 credits**; `voice_id` **required** — pick one from `list_voices`) and `generate_music { slug, description, mood?, genre?, tempo?, instruments? }` (**5 credits**).
9. `render { slug }` (**0 credits** — only auto-charges any still-ungenerated scenes) → `wait_for_completion({ slug, kind: "editor" })` → `download_result`.

Optional AI helpers along the way (1 assist each): `enhance_prompt`, `suggest_next_scene`, `suggest_music_prompt`.

## Bring your own media (local uploads)

An **editor** project can use the user's **own local files** — video clips on the timeline, or a product / closing / logo image — all **0 credits**. A **short** can take a product image and an end-card poster (no logo overlay — editor-only).

- **MCP:** `upload_video { slug, file_path, add_to_timeline? }` does the whole dance (presign → PUT → poll until processed → optionally append the clip). Then `add_segment_from_upload { slug, upload_id }` to append a ready clip, or `use_asset_for_segment { slug, segment_id, upload_id }` to set a specific scene (also takes `source_segment_id` to reuse a finished scene). Editor images: `set_product { slug, file_path, description? }` (then `set_product_placement { slug, mode: "throughout"|"end" }`), `set_closing_image { slug, file_path }`, `set_logo { slug, file_path }`. Short images: `set_short_product { slug, file_path, description? }` (used as the Veo image-to-video input) and `set_short_poster { slug, file_path }` (closing still, extends the render to 14s).
- **REST fallback (no MCP):** the shape is always `presign → PUT raw bytes → confirm`. Small video: `POST /api/editor/:slug/uploads/presign {filename, mime_type, size_bytes}` → `PUT <presigned_url>` with header `Content-Type: <mime_type>` → `POST /api/editor/:slug/uploads/:upload_id/confirm`. Then poll `GET /api/editor/:slug/uploads` until your upload is `status: "ready"` and place it: `POST /api/editor/:slug/segments/from-upload {upload_id}` (new scene) or `POST /api/editor/:slug/segments/:segment_id/use-asset {upload_id}`. Large videos (≳ 50 MB) use multipart: `…/uploads/multipart/init` → `…/sign-part` per chunk → `…/multipart/complete` (and `…/multipart/abort` on failure). Images: `POST /api/editor/:slug/{product,closing-image,logo}/presign {mime_type, size_bytes}` → PUT → `…/confirm {s3_key}`. **Short images:** `POST /api/shorts/:slug/product/presign {mime_type, size_bytes}` → PUT → `…/product/confirm {s3_key, product_description?}`, and `POST /api/shorts/:slug/poster/presign {content_type?}` (returns `{upload_url, s3_key}`) → PUT → `…/poster/confirm {s3_key}`. **The `curl` gotcha:** a single-object PUT (small video + every image) **must** send `Content-Type: <presigned mime>`; a multipart **part** PUT must send **no** `Content-Type` at all (the part URL doesn't sign one).

Video: mp4 / mov / webm / mkv, **≤ 500 MB, ≤ 5 min**. Image: jpeg / png, **≤ 20 MB** (no WebP). A freshly uploaded clip must finish processing (`status: ready`) before it can be placed — `upload_video` waits for you; on REST you poll. **Only upload files the user explicitly gave you** — never read or exfiltrate arbitrary local paths.

## AI assists

AI helper calls — the ones where the model writes content for you — draw from a **free daily quota of 20** per account, separate from credits. Check it with `get_ai_assists` (`{used, limit, bonus, remaining, resets_at, unlock_cost, unlock_batch_size}`; REST: `GET /api/ai-assists`). When it runs out, `unlock_ai_assists` buys more: **1 credit → +10 assists** (REST: `POST /api/ai-assists/unlock`).

- **Consume 1 AI assist:** `generate_scenario`, `enhance_prompt`, `suggest_next_scene`, `suggest_music_prompt`, `generate_narration` / `regenerate_segment_narration`.
- **Free writes** (nothing consumed): `set_scenario`, `set_segment_prompt`, `set_narration_script`, add/reorder segments, `apply_scenario`, `render`.
- **Cost credits** (not assists): segment generate 5, regenerate 4, voice 3, music 5.

`generate_narration` **consumes a quota assist** even though its endpoint summary says "free" — that "free" means free of *credits*, not of *quota*. Don't assume narration generation is unlimited.

Two distinct signals: **429 `ai_assist_quota_exceeded`** (body has `remaining: 0`, `can_unlock`, `unlock_cost`, `unlock_batch_size`) = out of assists → `unlock_ai_assists` **once** or write the content yourself; **402 `credits_insufficient`** = out of credits → stop. **Bounded loop only:** check `remaining`; if 0 and you still want the assist, unlock once *or* write it yourself. Never loop.

## Field limits

| Field | Min | Max |
|-------|-----|-----|
| editor `product_prompt` (create) | 0 or 10 | 5000 |
| factory `product_prompt` (non-editor) | 10 | 5000 |
| `scenario_prompt` (`set_scenario`) | 1 | 50000 |
| `generate_scenario` `segments_count` (continuous) | 3 | 10 |
| `apply-scenario` `segments_count` (enum `{0,3,5,7,10}`, not a range) | — | — |
| scene count via `set_scene_count` | 1 | 20 |
| segment `prompt` (add/update) | 1 | 2000 |
| segment `narration_text` | — | 5000 |
| factory `narration_script` (`set_narration_script`) | — | 12000 |
| music direction `description` (silently truncated, not 422) | — | 1200 |
| `voice_id` (required, regex `^[A-Za-z0-9_-]+$`) | 1 | 64 |
| `language` (create) | 2 | 10 |
| `product_description` (editor/short product upload) | — | 500 |
| short `headline` (title) / `subheadline` | — | 160 / 200 |
| short `music_vibe` | — | 80 |
| AI assist daily quota | 0 | 20 |

`product_prompt` on create is **empty OR 10..5000** (empty = omit it). The assembled full music prompt (5000) and music timeline lines (240) are server-side internals — don't try to set them.

## Credits

- Creating a draft (`create_short` / `create_editor_ad` / `create_editor_draft`): **0 credits**.
- Rendering a short: **15 credits**.
- Granular editor steps: generate segment **5**, regenerate **4**, batch per-segment (≥3) **4**, voice **3**, music **5**, render **0** (charges only ungenerated scenes). Unlock AI assists = **1 credit → +10**.
- Multi-scene editor ads (autopilot) cost more — preflight with `GET /api/editor/:slug/autopilot/cost` (or trust the MCP tool's preflight) before kicking off.
- Check the balance with `get_credits` (REST: `GET /api/studio/credits`).

## Result files

Result URLs are **presigned and short-lived (~24h TTL)** — they are not permalinks. Download promptly with `download_result` (or `curl`), then hand the local file to the user.

## Failure handling

- **402 `credits_insufficient`** — tell the user the required vs available credits and stop. Do **not** loop or retry.
- **429 `ai_assist_quota_exceeded`** — the daily AI-assist quota is used up (NOT a credit problem; body has `remaining: 0` + `unlock_cost`). `unlock_ai_assists` once or write the content yourself; never loop.
- **409 already-running** — a render is already in progress. Keep polling with `wait_for_completion`; do **not** start a new one.
- **403 `insufficient_scope`** — the token lacks `video:generate`. Tell the user to create a token with the "Generate videos" scope.
- **Long waits** are normal. A few minutes per render is expected.

## What NOT to do

- Do **not** auto-publish to TikTok / Instagram. Publishing needs a human-linked social account. Return the finished MP4 **plus a ready-to-paste caption** so the user posts it themselves.
- Do not echo or log the access token.
- Do not invent endpoints — the OpenAPI spec is at `GET /api/openapi`.

## REST fallback (no MCP)

If the MCP tools are not available, drive the same API directly. Base URL `https://hubfluencer.com`, all paths under `/api`, auth header `Authorization: Bearer <token>`.

```bash
# Check credits
curl -s https://hubfluencer.com/api/studio/credits \
  -H "Authorization: Bearer $HUBFLUENCER_API_TOKEN"

# Preflight an editor ad's cost
curl -s https://hubfluencer.com/api/editor/$SLUG/autopilot/cost \
  -H "Authorization: Bearer $HUBFLUENCER_API_TOKEN"

# Check the AI-assist quota
curl -s https://hubfluencer.com/api/ai-assists \
  -H "Authorization: Bearer $HUBFLUENCER_API_TOKEN"
```

Granular editor REST equivalents (reads need `video:read`, generation/spend needs `video:generate`): `POST /api/editor` (create) → `POST /api/editor/:slug/generate-scenario` (1 assist) or `PATCH /api/editor/:slug/scenario {scenario_prompt}` → `GET /api/editor/:slug` (review) → add/`DELETE /api/editor/:slug/segments/:id` → `PATCH /api/editor/:slug/segments/:id {prompt}` → `POST /api/editor/:slug/segments/:id/generate` (5cr) or `POST /api/editor/:slug/batch-generate` → `PATCH /api/editor/:slug {narration_script}` (≤12000) or `POST /api/editor/:slug/generate-narration` (1 assist) → `POST /api/editor/:slug/generate-voice {voice_id}` (3cr) + `POST /api/editor/:slug/generate-music {prompt}` (5cr, ≤1200 direction) → `POST /api/editor/:slug/render` (0cr). Unlock more assists with `POST /api/ai-assists/unlock` (1 credit → +10). 429 = quota exhausted, 402 = credits short.

The full schema (every endpoint and field) lives at `https://hubfluencer.com/api/openapi`.
