API 參考文件
以程式方式生成 AI 影片。提交提示詞、輪詢完成狀態,並取得直接影片連結 — 全部透過 REST 完成。
基礎 URL: https://www.yoh.app登入前,程式碼範例將使用佔位符 sk_live_YOUR_API_KEY。
快速開始
透過三個 API 呼叫建立你的第一部影片:生成 → 輪詢 → 下載。
Sign in and create a key at API Keys. Choose stories:generate and stories:read for generation and polling, then copy the full key immediately — it is shown only once. Keep it in a server environment variable or secret manager. Your account needs enough credits; creating a key does not add credits.
curl https://www.yoh.app/api/v1/credits \
-H "Authorization: Bearer sk_live_YOUR_API_KEY"Submitting a generation request authorizes its credit charge; API integrations do not need an interactive price confirmation. To check cost first, POST the same body to /api/v1/stories/estimate. Optionally send X-Max-Generation-Credits with your maximum whole-number credits: exceeding it returns HTTP 409 before charging. Use the canonical host shown here; redirects from yoh.app can drop your Authorization header.
curl -X POST https://www.yoh.app/api/v1/stories/generate \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: YOUR_UNIQUE_REQUEST_ID" \
-d '{ "prompt": "A dragon teaches a young knight how to fly", "contentType": "cinematic_story", "aspectRatio": "16:9", "scope": "full", "renderVideo": true }'Replace YOUR_UNIQUE_REQUEST_ID with a unique ID for this operation. Reuse that ID and the identical body when retrying a queued generation request after a network failure. A new ID starts a new operation. Direct image/music requests may return a conflict on replay; inspect the asset before retrying. Poll the returned job every 20 seconds until completed or failed. A failed job is terminal; an error on a queued job can describe an automatic retry. For a text-only draft, use scope: "story" and omit renderVideo.
{
"jobId": "cmnk8wu2q0001q3vpmqv93nmy",
"status": "queued",
"pollUrl": "/api/v1/stories/jobs/cmnk8wu2q0001q3vpmqv93nmy"
}curl https://www.yoh.app/api/v1/stories/jobs/cmnk8wu2q0001q3vpmqv93nmy \
-H "Authorization: Bearer sk_live_YOUR_API_KEY"{
"status": "completed", "progress": 100,
"storyId": "cmnk8xxnx000004lbd7siief7",
"videoUrl": "https://cdn.example.com/video/clip.mp4",
"error": null
}就這樣。videoUrl 是可直接下載、串流或分享的公開存取 URL。
身份驗證
所有 API 請求都必須在 Authorization 標頭中使用 Bearer 方案包含你的 API 金鑰。
Authorization: Bearer sk_live_YOUR_API_KEYAPI 金鑰以 sk_live_ 開頭。你可以從 API Keys page 建立和管理金鑰。 /api-keys
請妥善保管你的 API 金鑰。不要在客戶端程式碼或公開儲存庫中暴露它們。如果金鑰外洩,請立即從 API 金鑰頁面撤銷它。
範圍與點數
每個 API 金鑰被授予一個或多個控制其可存取端點的範圍。故事文字生成每個工作消耗 55 點數;若範圍還包含片段影片或最終轉檔(包括預設的 full 範圍),這些費用會另外預先扣除。如果工作在所有重試後失敗,點數將被退還。
| 範圍 | 授予存取權限 |
|---|---|
| stories:generate | POST /api/v1/stories/generate, POST /stories/:id/assets, POST /stories/:id/storyboard, POST /stories/:id/videos, POST /stories/:id/render, POST /stories/:id/clips/:clipId/image, POST /stories/:id/clips/:clipId/video, POST /stories/:id/clips/:clipId/video-edit, POST /stories/:id/characters/:assetId/sheet, POST /stories/:id/soundtrack, POST /stories/:id/assets/:assetId/image, POST /stories/:id/clips/:clipId/regenerate, POST /stories/:id/continue, POST /stories/:id/clips-all, POST /stories/:id/clips/:clipId/grid-reference — the clip edits: POST/PATCH /stories/:id/clips, PATCH/DELETE /stories/:id/clips/:clipId — and the story-asset edits: PATCH /stories/:id/characters|scenes|items/:assetId. Also implies assets:write and series:write, so keys issued before those scopes existed keep working. |
| stories:read | GET /api/v1/stories, GET /api/v1/stories/:id, GET /api/v1/stories/jobs/:id, GET /api/v1/credits, POST /api/v1/stories/estimate, POST /api/v1/stories/analyze-prompt. Implies series:read and assets:read. |
| stories:write | The MCP server's editing tools (POST /api/mcp): clip content and dialogue, timeline moves/trims/captions, freeform overlays, cast and set edits. Spends no credits, so a key with this scope but WITHOUT stories:generate can edit a story and physically cannot pay to generate anything. Implied by stories:generate; implies assets:write. |
| assets:read | GET .../characters, .../scenes, .../items, .../clips, GET /api/v1/series/:id/assets |
| assets:write | POST /api/v1/series/:id/assets, PATCH/DELETE /api/v1/series/:id/assets/:assetId, POST /api/v1/uploads |
| series:read | GET /api/v1/series, GET /api/v1/series/:id |
| series:write | POST /api/v1/series, PATCH /api/v1/series/:id |
| social:publish | POST /api/v1/social/publish, GET /api/v1/social/publish/:postId |
| accounts:manage | GET/POST /api/v1/accounts, GET/POST /api/v1/accounts/:accountId/credits. For platforms that serve many customers: provision an account per customer and fund it from your own balance. Implied by nothing — an existing key never gains it. |
生成影片
排入影片生成工作
/api/v1/stories/generatescope: stories:generate立即提交提示詞並返回 jobId。影片以非同步方式生成。輪詢 GET /api/v1/stories/jobs/:jobId 以追蹤進度。
請求本體
| 欄位 | 型別 | 狀態 | 說明 |
|---|---|---|---|
prompt | string | 必填 | 故事描述,最多 5000 個字元。 |
aspectRatio | string | 選填 | 16:9、9:16、1:1、21:9 之一。預設:16:9。 |
renderVideo | boolean | 選填 | false — 返回第一個 AI 片段(快速,約 10 分鐘)。true — 透過 Remotion 將所有片段輸出成一個合成 MP4(約 12–15 分鐘)。 |
scope | string | 選填 | story (text only) or full (text, images and clip videos; default). Use renderVideo: true with full to include the final MP4 and its render fee. Other values return 400 before charging. For assets, storyboard, videos or render on an existing story, use the separate /api/v1/stories/:storyId endpoints below. |
title | string | 選填 | 故事的顯示名稱。 |
contentType | string | 選填 | 故事格式分類。可選 cinematic_story、news_analysis、persona_channel、ad_spot。預設:cinematic_story。 |
locale | string | 選填 | 語言代碼,例如 en、zh-TW。預設:en。 |
seriesId | string | 選填 | 附加到現有影集以保持一致的角色視覺效果。 |
assets | array | 選填 | The cast and props for this story — reference assets already in your series library, or attach one-off assets inline. See the table below. |
continuityMode | string | 選填 | With a seriesId: how much of the previous episodes this one carries. episodic_continuous (default) picks up open threads, finale resolves them, one_off_episode keeps series plot out entirely. |
isFinale | boolean | 選填 | Implied by continuityMode: "finale"; you rarely need both. |
allowMultiStory | boolean | 選填 | Let one request split into several episodes when the prompt clearly carries more than one. Default false — N episodes means N charges, so it is opt-in. |
seriesContinuityContext | object | 選填 | Escape hatch. Supplying it replaces the continuity we assemble from your series (open threads plus the last three episode recaps), so an incomplete object produces a weaker episode than sending nothing. Prefer continuityMode. |
assets[] 選填
This array ATTACHES assets to one generation — it is not where an asset is defined. It carries only what is read inline; the full asset shape (character type and role, voice, scene lighting and mood, item material and condition) lives in the series library, which accepts all of it and which the pipeline reads directly. Reference the library by id rather than restating an asset here. Each entry either references an asset in your series library by seriesAssetId (upload it once via POST /api/v1/series/:seriesId/assets and reuse it forever), or defines a one-off asset inline with type + name. The two forms mix freely in one array. Whatever you attach is described to the writer, and its image is reused instead of a new one being generated — so a character keeps their face and a product keeps its packaging.
| 欄位 | 型別 | 說明 |
|---|---|---|
seriesAssetId | string | Reference an existing library asset. Everything else is optional when this is set. |
type | string | character · scene · item · reference. Required for an inline asset. |
name | string | Required for an inline asset. Also how the writer refers to it — name it in your prompt for it to appear. |
description | string | Overrides the library description for this story only. |
imageUrl | string | Image for an inline asset. Re-hosted on our storage. |
itemType | string | Items only: prop · product · logo. A logo is composited flat, never given 3D depth. |
isHeroProduct | boolean | ad_spot: the advertised product — appears in every clip. Fills heroItemId for you. |
isPrimaryBackground | boolean | news_analysis / persona_channel: the set reused across every shot. Fills primaryBackgroundAssetId for you. |
useAsReferenceFrame | boolean | Use this image as the first frame / reference for image-to-video on clips featuring the asset. |
gender | string | Characters only: surfaced to the writer. |
age | string | Characters only: surfaced to the writer. |
personalityTraits | string[] | Characters only: surfaced to the writer. |
timeOfDay | string | Scenes only, e.g. dusk. |
weather | string | Scenes only, e.g. rain. |
persist | boolean | Accrue an inline asset into the series library so later stories can reference it by id. Default false. |
Characters, scenes and items only enter the story when the prompt names them — attaching a whole series library does not drag its entire cast into every episode. The two pin flags are the exception: a pinned hero product or primary background is guaranteed regardless of whether the script says its name.
globalConfig 選填
Optional nested object controlling duration, output format, and model selection. Every field has a default; pass only what you want to override. The model fields (videoModel, imageModel, characterImageModel, modelStrategy) may also be passed at the top level as a convenience. Unknown model ids degrade to auto rather than failing the request.
This is a superset of the studio's own controls, not a mirror of them. resolution, qualityTier, modelStrategy and seed are API-only — the studio expresses the same choices through its quality picker. Two fields are advisory: they are shown to the writer as context but nothing enforces them, and they are marked below.
| 欄位 | 型別 | Values | Default |
|---|---|---|---|
storyInputMode | string | auto · develop · follow_script (auto decides whether the prompt is an idea to develop or a finished script to follow verbatim) | auto |
durationSec | number | 15–300 (total seconds) | 60 |
aspectRatio | string | 16:9 · 9:16 · 1:1 · 21:9 | 16:9 |
resolution | string | 480p · 720p · 1080p | 720p |
pace | string | slow · normal · fast | normal |
structure | string | auto · 3_act · 4_act · montage | auto |
qualityTier | string | draft · balanced · premium (premium selects the advanced model lane) | balanced |
stylePreset | string | preset slug, or auto | auto |
platformTarget | string | ADVISORY — shown to the writer as context; nothing enforces it. youtube · tiktok · instagram · x | youtube |
safetyMode | string | ADVISORY — shown to the writer as context; not a content filter. standard · strict | standard |
seed | string | any string — shared across clips for continuity (API-only) | — |
videoModel | string | a model id, or auto | auto |
videoEditModel | string | video editing only — muapi/sd25-video-edit (Seedance 2.5 Video Edit), or auto. Omit to keep the existing editing recipe. | auto |
imageModel | string | storyboard frames + scene/item images — a model id, or auto (GPT Image 2) | auto |
characterImageModel | string | character portraits, sheets and appearance variants — a model id, or auto (Nano Banana 2) | auto |
modelStrategy | string | auto · fixed · cost_optimized · quality_first · hero_premium | auto |
generationMode | string | DEPRECATED — ignored. The selected model determines frame or reference inputs. | auto |
locale | string | en · zh-TW · ja · ko · th · id (also accepted top-level) | en |
contentTypeConfig 選填
Optional per-contentType creative controls. Fields are validated against the selected content type; omitted fields fall back to sensible defaults. Pass the object matching your contentType. The two pin ids below are usually easier to set as isHeroProduct / isPrimaryBackground on an assets[] entry — an id you write here wins if you send both.
| contentType | Fields |
|---|---|
| cinematic_story | (no per-type fields — format craft is built in) |
| news_analysis | primaryBackgroundAssetId, primaryBackgroundName (pin a studio scene across anchor shots) |
| persona_channel | primaryBackgroundAssetId, primaryBackgroundName (pin the persona's set) |
| ad_spot | heroItemId, heroProductName, heroTagline, heroKeyBenefit (the advertised product) |
Other optional fields
| 欄位 | 型別 | 說明 |
|---|---|---|
storyQualityConfig | object | { enableStyleAdaptedCharacters?: boolean, directClipImageGeneration?: boolean } — quality-rollout toggles. Omit to use pipeline defaults. |
style | object | Style-consistency overrides: cinematicStyle, era, colorPalette, lightingStyle, filmGrain, lensCharacter, materialEmphasis, styleCategory, negativeHints, stylePreset. |
seriesContinuityContext | object | Series continuity context for episodic generation. Used together with seriesId. |
Fields that aren't part of the selected contentType's schema are silently ignored — cinematic_story takes no per-type fields at all, so pass contentTypeConfig only for ad_spot, news_analysis, or persona_channel.
curl -X POST https://www.yoh.app/api/v1/stories/generate \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "A 30s spot for a titanium water bottle that keeps ice for 24h",
"contentType": "ad_spot",
"scope": "full",
"renderVideo": true,
"globalConfig": {
"durationSec": 30,
"resolution": "1080p",
"modelStrategy": "hero_premium"
},
"contentTypeConfig": {
"heroProductName": "Northpeak Flask",
"heroTagline": "Ice, 24 hours later.",
"heroKeyBenefit": "Vacuum-sealed titanium keeps ice frozen for a full day"
}
}'回應 202 Accepted
{ "jobId": "cmnk8wu2q0001q3vpmqv93nmy", "status": "queued", "pollUrl": "/api/v1/stories/jobs/..." }Step-by-Step Endpoints
After generating a story with scope: "story", use these endpoints to generate storyboard images, clip videos, and the final rendered MP4 as separate steps.
/api/v1/stories/:storyId/assetsGenerate the cast and world: character portraits + 2×2 character sheets, plus scene and item images. Run this before /storyboard for the best face consistency. Skips assets that already have images.
/api/v1/stories/:storyId/storyboardGenerate storyboard grid images for all clips. Skips clips that already have images.
/api/v1/stories/:storyId/videosGenerate video for each clip. Skips clips that already have a video URL.
/api/v1/stories/:storyId/renderStitch all clip videos + music + captions into a final MP4.
One Clip at a Time
The batch endpoints above only fill in what's missing — /storyboard skips clips that already have an image and /videos skips clips that already have a video. To re-roll a shot you don't like, target it directly. These are the same operations as the editor's per-clip regenerate buttons, and they overwrite whatever that clip already has.
/api/v1/stories/:storyId/clips/:clipId/image(Re)generate the storyboard image for ONE clip. Optional body: prompt (art direction for this shot) and imageModel.
/api/v1/stories/:storyId/clips/:clipId/video(Re)generate the video for ONE clip. Optional body: videoModel. There is no mode to pick. The selected model locks the input mode: frame models use the current clip image as start and the next clip image as end; reference models receive their supported, named image, video and voice references. Missing required clip images are rejected before charging. The final clip uses start-only where supported.
estimatedCredits) | Scope: stories:generate/api/v1/stories/:storyId/clips/:clipId/regenerateRebuild ONE clip end to end — a fresh storyboard image, then a fresh video from it — as a single job. The right call after changing a clip's description or cast: regenerating only the video would run it against the old image. Optional body: prompt (art direction for the new image).
breakdown | Scope: stories:generate/api/v1/stories/:storyId/clips/:clipId/grid-referenceClip authoring supports description for scene and image context, and optional motionDescription for video movement and timed beats. Video generation uses both. When motion is empty, video generation automatically creates and saves timed motion first. Send null to prepare new motion on the next video run. Draw a multi-panel continuity reference sheet for one clip. The panels are derived from the clip's time beats — an authored beat track when motionDescription carries one, otherwise the beat plan its generation length implies — so the sheet mirrors that shot rather than a fixed set of generic moments. Two panels per beat, snapped to a supported grid layout. Runs inline and returns the grid URL.
/api/v1/stories/:storyId/clips-allDraw every missing storyboard image and then generate every missing clip video, as ONE job. Running /storyboard and /videos separately lets the video pass start while images are still missing. Both phases only fill gaps, so re-running after a partial failure is safe.
breakdown | Scope: stories:generate/api/v1/stories/:storyId/continueWrite a NEW storyline branching off an existing clip — an alternative continuation the story keeps alongside the original, which is untouched. Required body: branchFromClipId. Optional prompt (where the story goes) and clipCount. TEXT only — the new clips arrive with no images or video, so drawing them is a separate, separately-priced step.
/api/v1/stories/:storyId/rendersThe story's finished videos, newest first, with latestSuccessful called out so a failed render is never mistaken for the current cut. A story keeps every render. Optional ?limit (max 50).
/api/v1/stories/:storyId/clips/:clipId/video-editRewrite a clip's existing video from an instruction — video-to-video, so subject, composition and motion carry over. Required body: prompt ("make it night and rainy", "push the camera in slowly"). Optional durationSeconds (4–15); omit it and the edit follows the source's own length. The clip must already have a video, and the result becomes a new selectable version rather than replacing the old one.
# Regenerate clip 3's image, then its video — the rest of the story is untouched
curl -X POST https://www.yoh.app/api/v1/stories/STORY_ID/clips/CLIP_ID/image \
-H "Authorization: Bearer sk_live_YOUR_API_KEY"
# Poll jobId until completed
curl -X POST https://www.yoh.app/api/v1/stories/STORY_ID/clips/CLIP_ID/video \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"videoModel":"bytedance/seedance-2.0"}'
# Poll jobId until completed → the clip's videoUrl is replacedEditing the Story
Direct the story instead of only re-rolling it: rewrite a shot, retime it, add a beat, reorder, or cut one. These are immediate (no job to poll) and cost no credits. A clip's description provides scene and image context; optional motionDescription provides video choreography. Video uses both — edit the appropriate field, then call that clip's /image or /video to re-shoot it with the new direction.
/api/v1/stories/:storyId/clips/:clipIdEdit a clip. Send only what you want to change: title, description, motionDescription, duration, generationDuration, resolution, transitionStyle, sceneId, characterIds, itemIds, dialogue, audioFx.
Media columns (imageUrl, videoUrl, generation history) are deliberately not writable — those belong to the pipelines. Use the clip's /image and /video endpoints to change them.
/api/v1/stories/:storyId/clipsInsert a clip. title and description required; place it with afterClipId or beforeClipId (omit both to append). Following clips shift down automatically. The new clip has no image or video yet.
/api/v1/stories/:storyId/clipsReorder. Body: { "clips": [{ "id": "...", "order": 0 }, …] } — send every clip with a contiguous 0-based order.
/api/v1/stories/:storyId/clips/:clipIdCut a clip; the remaining orders close up. A story must keep at least one clip.
# 1. Rewrite the direction + retime the shot (immediate, free)
curl -X PATCH https://www.yoh.app/api/v1/stories/STORY_ID/clips/CLIP_ID \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"description": "The keeper climbs the stairs as the lamp flickers behind him.",
"duration": 7
}'
# 2. Re-shoot it with the new direction
curl -X POST https://www.yoh.app/api/v1/stories/STORY_ID/clips/CLIP_ID/image \
-H "Authorization: Bearer sk_live_YOUR_API_KEY"
curl -X POST https://www.yoh.app/api/v1/stories/STORY_ID/clips/CLIP_ID/video \
-H "Authorization: Bearer sk_live_YOUR_API_KEY"# Step 1: Generate story text (~1-2 min)
curl -X POST https://www.yoh.app/api/v1/stories/generate \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt":"A samurai cat guards a cherry blossom tree","contentType":"cinematic_story","scope":"story"}'
# Poll jobId until completed → get storyId
# Step 2: Generate storyboard images (~3-5 min)
curl -X POST https://www.yoh.app/api/v1/stories/STORY_ID/storyboard \
-H "Authorization: Bearer sk_live_YOUR_API_KEY"
# Poll until completed
# Step 3: Generate clip videos (~1-3 min per clip)
curl -X POST https://www.yoh.app/api/v1/stories/STORY_ID/videos \
-H "Authorization: Bearer sk_live_YOUR_API_KEY"
# Poll until completed
# Step 4: Render final MP4 (~1-2 min)
curl -X POST https://www.yoh.app/api/v1/stories/STORY_ID/render \
-H "Authorization: Bearer sk_live_YOUR_API_KEY"
# Poll until completed → videoUrl輪詢工作狀態
取得工作狀態
/api/v1/stories/jobs/:jobIdscope: stories:read呼叫 POST /api/v1/stories/generate 後輪詢此端點。工作通常需要 8–15 分鐘,取決於故事長度以及是否啟用了 renderVideo。建議每 15–30 秒 輪詢一次。
工作狀態生命週期
queuedgenerating_storygenerating_assetsgenerating_videosrendering_videocompleted或failedWhich statuses you see depends on the scope you requested — a story job goes straight from generating_story to completed. A transient failure sends the job back to queued with error set to a retry message; that is not terminal, so treat only completed and failed as final states.
回應欄位
| 欄位 | 型別 | 說明 |
|---|---|---|
jobId | string | 唯一工作識別碼。 |
status | string | 目前狀態。參見上方的生命週期。 |
progress | number | 0–100 整數,表示完成百分比。 |
storyId | string|null | 故事生成完成後設定。用於取得素材。 |
renderVideo | boolean | 是否請求合成最終 MP4。 |
videoUrl | string|null | 狀態完成時的絕對影片 URL。可以是合成 MP4(renderVideo: true)或第一個片段的 AI 影片(renderVideo: false)。 |
error | string|null | 人類可讀的錯誤訊息。失敗時會設定;工作重試時也會設定(此時狀態回到 queued)。成功時始終為 null。 |
createdAt / startedAt / completedAt | string (ISO) | 時間戳記。 |
故事
列出故事
/api/v1/storiesscope: stories:read返回你的故事分頁列表。
查詢參數
| 參數 | 預設值 | 說明 |
|---|---|---|
page | 1 | 頁碼(≥ 1)。 |
limit | 20 | 每頁結果數(1–100)。 Capped at 100. |
status | — | 按故事狀態過濾。 |
This is the only endpoint that returns a pagination envelope alongside data:
{
"data": [ { "id": "cmnk9cibl0004q3vpijkqfwl4", "title": "...", "status": "completed" } ],
"pagination": { "page": 1, "limit": 20, "total": 137 }
}取得故事
/api/v1/stories/:storyIdscope: stories:read返回單個故事的完整元數據,包括其角色、場景、物品和片段的數量。
{
"id": "cmnk9cibl0004q3vpijkqfwl4",
"title": "A robot chef cooks in a futuristic kitchen",
"status": "completed", "aspectRatio": "9:16", "locale": "en",
"counts": { "characters": 3, "scenes": 4, "items": 6, "clips": 6 },
"createdAt": "2026-04-04T11:37:04.662Z"
}素材概覽
工作完成後,你可以使用工作返回的 storyId 取得為故事生成的所有素材。所有素材端點都需要 assets:read 範圍。
GET /api/v1/stories/:id/characters角色 — 名稱、描述、角色、imageUrl、語音信息
GET /api/v1/stories/:id/scenes場景 — 描述、時間、天氣、光線、imageUrl
GET /api/v1/stories/:id/items道具/物品 — 描述、重要性、imageUrl
GET /api/v1/stories/:id/clips片段 — 旁白、時長、videoUrl、imageUrl、audioUrl
角色
列出角色
/api/v1/stories/:storyId/charactersscope: assets:read{
"data": [{
"id": "char_abc123", "name": "Chef Axon", "role": "protagonist",
"description": "A robot chef with a warm personality",
"imageUrl": "https://cdn.example.com/characters/chef-axon.jpg",
"voiceName": "Neutral-EN", "order": 0
}]
}片段與影片
列出片段
/api/v1/stories/:storyId/clipsscope: assets:read每個片段都是一個有自己 AI 生成影片、靜態圖片和音訊的場景片段。所有 URL 欄位都是絕對路徑且可公開存取。
{
"data": [{
"id": "clip_001", "order": 0, "narration": "In the year 2157...",
"duration": 8,
"videoUrl": "https://cdn.example.com/generated/video/clip1.mp4",
"imageUrl": "https://cdn.example.com/generated/image/clip1.jpg",
"audioUrl": null, "status": "completed"
}]
}當 renderVideo 為 false 時,工作的 videoUrl 指向第一個片段的影片。使用此端點取得每個單獨片段的影片 URL。
系列素材庫
上傳一次,永久引用
/api/v1/series/:seriesId/assetsscope: assets:write與工作室素材面板寫入的是同一個素材庫。上傳角色、背景、道具或風格參考後保留回傳的 id,之後生成時把該 id 當作 assets 陣列中的 seriesAssetId 引用即可,不需要每次重新產生圖片連結。你提供的圖片會重新託管在我們的儲存空間,因此即使你自己的上傳網址過期,該 id 仍然有效。
| 欄位 | 型別 | 說明 |
|---|---|---|
type | string | character、scene、item 或 reference。 |
name | string | 顯示名稱,也是後續生成用來比對的鍵值。 |
imageUrl | string | 圖片的公開網址;也可改傳 imageBase64。 |
itemType | string | 僅限道具:prop、product 或 logo。logo 會以平面方式合成,不會加上 3D 立體效果。 |
isHeroProduct | boolean | 僅限道具:這支廣告主打的商品,會出現在每一個鏡頭。 |
isPrimaryBackground | boolean | 僅限場景:每個主播鏡頭都會重複使用的攝影棚/場景。 |
- 可傳入單一素材,或用 "assets" 陣列一次寫入最多 100 筆。
- 預設以名稱完全相同來合併,因此重複上傳同一份素材目錄是冪等的。可加上 ?mergeStrategy=fuzzy 容許改名比對,或用 insert 一律新增。
- 在道具上設定 isHeroProduct(ad_spot)或在場景上設定 isPrimaryBackground(news_analysis / persona_channel)即可釘選。生成時系統會自動解析,你不需要手動填寫 heroItemId。
| 範圍 | 授予存取權限 |
|---|---|
GET | /api/v1/series |
POST | /api/v1/series |
GET | /api/v1/series/:seriesId |
PATCH | /api/v1/series/:seriesId |
GET | /api/v1/series/:seriesId/assets?type=character |
POST | /api/v1/series/:seriesId/assets |
PATCH | /api/v1/series/:seriesId/assets/:assetId |
DELETE | /api/v1/series/:seriesId/assets/:assetId |
POST | /api/v1/uploads |
PATCH | /api/v1/stories/:storyId/characters/:assetId |
PATCH | /api/v1/stories/:storyId/scenes/:assetId |
PATCH | /api/v1/stories/:storyId/items/:assetId |
POST | /api/v1/stories/estimate |
GET | /api/v1/credits |
End to end — put a brand's logo in an ad, and keep the id for every ad after it:
curl -X POST https://www.yoh.app/api/v1/series \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"title":"Acme brand films","styleGuide":{"cinematicStyle":"clean product cinematography"}}'
# → { "series": { "id": "series_abc", ... } }curl -X POST https://www.yoh.app/api/v1/series/series_abc/assets \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "item",
"name": "Acme Logo",
"itemType": "logo",
"description": "Wordmark, orange on white",
"imageUrl": "https://your-admin.example.com/tmp/logo.png"
}'
# → { "asset": { "id": "series-item-...", "imageUrl": "https://...blob..." } }
# That imageUrl is ours now — your temporary link can expire.curl -X POST https://www.yoh.app/api/v1/stories/generate \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "A 30-second spot for the Acme cordless drill",
"contentType": "ad_spot",
"seriesId": "series_abc",
"assets": [
{ "seriesAssetId": "series-item-...", "isHeroProduct": true }
]
}'
# → { "jobId": "...", "pollUrl": "/api/v1/stories/jobs/..." }Step 2 happens once per asset, not once per video. Step 3 repeats forever with the same id — and because the asset is pinned, the logo is guaranteed in every clip even if the script never says its name. To check the cost before committing, POST the same body to /api/v1/stories/estimate.
影集素材
為影集註冊角色圖片
/api/v1/series-assets/ensure-character-imagescope: assets:writePOST /api/v1/series-assets/ensure-character-image 仍可使用且行為不變,但它只支援角色。建議改用此端點。
為影集上傳並保存角色參考圖片,使 AI 在該影集所有故事中使用一致的面孔/外觀。此操作是 idempotent 的 — 使用相同角色名稱多次呼叫是安全的。
| 欄位 | 型別 | 說明 |
|---|---|---|
seriesId | string | 要附加角色的影集 ID。 |
characterName | string | 角色名稱(在影集中用作唯一鍵)。 |
imageUrl | string | 角色參考圖片的可公開存取 URL。 |
受管帳號
為服務大量客戶的平台而設。與其所有人共用一組金鑰與一個點數池——某個用量大的客戶把餘額用光,而且沒人看得出是誰花的——不如為每位客戶開一個帳號,並從你自己的餘額撥款給他們。你的客戶不需要在這裡註冊,也不會付錢給我們;商業關係仍然屬於你。
開立帳號
/api/v1/accountsscope: accounts:manageexternalId 是你自己給客戶的識別碼。用同一個 externalId 呼叫第二次,會回傳既有的帳號而不是再開一個,所以重試是安全的。API 金鑰只會回傳一次,在初次建立時——我們只保存雜湊值,無法再次顯示,請在收到時就妥善保存。新帳號的初始餘額為零。
| 欄位 | 型別 | 說明 |
|---|---|---|
externalId | string | 必填。你為這位客戶使用的識別碼,例如租戶 ID。每個平台內須唯一。 |
name | string | 選填的標籤,供你自己做報表用。 |
更換受管理帳號的 API 金鑰
/api/v1/accounts/:accountId/api-keysscope: accounts:manage若遺失初始金鑰,請使用所屬平台的金鑰呼叫此端點,無須傳送請求本文。可使用帳號 ID 或 externalId。此操作會以單一交易撤銷該帳號先前的金鑰,並僅顯示一次新的 apiKey(201)。請妥善保存。重複呼叫會再次更換金鑰;重試一般帳號建立請求不會替換現有金鑰。
為帳號撥款
/api/v1/accounts/:accountId/creditsscope: accounts:manage把點數從「你的」餘額轉入該帳號——這是轉帳而非贈與,所以你只能發出你實際買過的量。當你自己的餘額不足時會回傳 402。:accountId 可以填我們的帳號 ID,也可以填你的 externalId。
| 欄位 | 型別 | 說明 |
|---|---|---|
amount | integer | 必填。要轉移的點數,必須為正數。 |
idempotencyKey | string | 請填入你的發票或訂閱期間 ID。之後重試或重複送達的呼叫會回傳原本那筆轉帳,而不會重複撥款。 重試時請使用相同的金鑰、收款帳號及金額。即使平台餘額已用盡,重試也不會再次轉帳。更改收款帳號或金額會回傳 409。過長的金鑰會被拒絕,不會截短。 |
description | string | 選填備註,會顯示在點數紀錄中轉帳的雙方。 |
GET /api/v1/accounts 會列出你的所有帳號與各自的餘額和累計花費;GET /api/v1/accounts/:accountId/credits 則讀取單一帳號。受管帳號自己的金鑰只帶有生成相關範圍——它永遠無法自行開立或撥款帳號。
Claude Code(MCP)
Yoh 提供 MCP 伺服器,讓 AI 編碼代理(Claude Code、Claude Desktop 或任何 MCP 用戶端)直接開啟並編輯你的故事:片段內容與對白、時間軸移動與裁切、自由圖層,以及在你每次明確同意後的重新生成與輸出。它遵循與瀏覽器編輯器相同的規則,因此從終端機做的編輯,會精準落在拖曳會到的位置。
連接
在 /api-keys 建立 API 金鑰——若只要編輯而不花費點數,stories:read 與 stories:write 兩個範圍就足夠——然後執行:
claude mcp add --transport http yoh https://www.yoh.app/api/mcp \
--header "Authorization: Bearer sk_live_YOUR_API_KEY"或是提交專案設定檔,讓整個團隊開啟儲存庫時就取得連線,各自以環境變數提供自己的金鑰:
{
"mcpServers": {
"yoh": {
"type": "http",
"url": "https://www.yoh.app/api/mcp",
"headers": { "Authorization": "Bearer ${YOH_API_KEY}" },
"timeout": 900000
}
}
}接著直接對 Claude Code 說:「把第 3 集的節奏收緊——剪掉空拍、讓字幕落在節拍上,然後重新輸出」。在 Claude Code 內執行 /mcp 可檢查連線狀態。
代理可以做什麼
| 工具 | 範圍 | 範例 |
|---|---|---|
| 讀取 | stories:read | list_stories · get_story · get_timeline · get_clip · get_job · get_credits · get_asset_images · get_soundtrack · list_overlays · get_revoice · list_style_presets · list_voices · list_renders · list_audio_assetsget_clip 會附上分鏡圖,加上 withFrames 還會附上從已生成影片取樣的真實畫格——這是代理唯一能看見片段實際樣貌的方式。get_asset_images 對角色、場景與道具做同樣的事。 |
| Template versions | stories:write / stories:read / assets:read | create_template_version · get_template_version · list_template_destinations · list_template_assets · update_template_version · analyze_template_version · get_template_analysis · accept_template_adaptation · quote_template_version · commit_template_version · cancel_template_version · fork_template_versionMap existing or newly created series assets, review wording and language, then create a free draft. REST families: /api/v1/template-version-sessions/:sessionId and /api/v1/template-version-runs/:runId. Template REST OpenAPI schemas and endpoints |
| Template generation ★ | stories:generate | generate_template_versionQuote the run first, show the maximum credits, then confirm. Poll get_template_version for persisted progress and recovery. |
| 建立 ★ | stories:generate | estimate_story · create_story從一段提示寫出全新的故事。estimate_story 免費,且能一次為整個計畫估價,這是逐項報價做不到的。請從 scope="story"(僅文字)開始,而不是會立刻收取影片費用的 "full"。 |
| 影集 | series:read / series:write | list_series · get_series · create_series · update_series · list_series_assets · add_series_asset · delete_series_asset位在單一故事之上的一層:跨集共用的角色、場景與風格。用 create_story 的 seriesId 掛上新故事即可繼承。 |
| 片段與對白 | stories:write | update_clip · update_clips · set_all_clips · reorder_clips · add_clip · delete_clip · duplicate_clip · reorder_clip · set_clip_duration · select_clip_version · add_dialogue_line · update_dialogue_line · remove_dialogue_line · hide_dialogue_line · add_audio_fx · update_audio_fx · remove_audio_fx · update_story · update_caption_settings對白長度超出片段時,會自動延長該片段的長度。文字編輯絕不會改動已經生成的素材。update_clips 可一次編輯多個片段,且各自帶不同的值;set_all_clips 則把同一個值套用到每個片段,並拒絕文字欄位,因為整集共用同一段描述會讓故事變得扁平。任一筆有問題時,兩者都會整批拒絕。reorder_clips 一次寫入整條故事線的順序。 |
| 時間軸 | stories:write | move_clip · resize_clip · trim_clip · reset_clip_timing · close_gaps · move_caption · hide_clip_video · pin_dialogue_read · export_clip_to_freeform · export_caption_to_freeform · remove_soundtrack移動片段會把後面所有片段一起往後推;調整長度以起點為錨,所以拖曳起點等同於裁掉開頭。故事項目無法刪除或分割——請隱藏它們,或匯出成自由圖層。 |
| 圖層 | stories:write | add_overlay · update_overlay · move_overlay · resize_overlay · split_overlay · duplicate_overlay · delete_overlay · delete_freeform_row · upload_media同一列上的圖層不可重疊;位置衝突時會被拒絕,並列出可用的空列。媒體必須提供已託管的 https 來源。 |
| 樣式 | stories:write | list_style_presets · apply_overlay_preset · set_caption_style編輯器本身的文字預設、字幕範本與約 32 種進出場動畫——讓代理套用產品既有的樣式,而不是自己發明 CSS。字幕樣式以每句對白為單位。 |
| 角色與場景 | stories:write | add_character · update_character · delete_character · add_scene · update_scene · delete_scene · add_item · update_item · delete_item刪除角色時會修復所有引用——包括片段選角以及它的對白,這些對白會轉給同名的其他角色或旁白。 |
| 配音指派 | stories:write | list_voices · set_character_voice依故事語言篩選的內建語音,加上帳號中已就緒的複製語音。指派語音免費;已經演出的對白會保留當初生成時使用的語音。 |
| 重新配音 | stories:write | get_revoice · set_revoice_enabled · select_revoice_take · undo_revoice在新配音與原始演出之間切換免費且可逆;兩種版本都會保留。 |
| 生成 ★ | stories:generate | regenerate_clip · regenerate_clip_image · edit_clip_image · enhance_clip_image · generate_clip_video · edit_clip_video · revoice_clip · regenerate_character_sheet · regenerate_asset_image · generate_all_asset_images · generate_soundtrack · generate_storyboard · generate_all_videos · generate_all_clips · generate_clip_grid_reference · continue_story_from_clip · render_story每一組都是「修改」對「重畫」:edit_clip_image 依指示改寫目前的畫面,regenerate_clip_image 則重新畫一張;edit_clip_video 是影片轉影片,會保留主體、構圖與運鏡,generate_clip_video 則從分鏡圖重新生成。enhance_clip_image 依角色設定表把臉部拉回相似度。regenerate_clip 把「先圖後片」合成一個任務——改過片段描述或選角之後就該用它。generate_clip_grid_reference 會畫出連戲參考表,其分鏡格來自該片段自己的時間節拍,而非一組固定的通用時刻。 |
花費點數有雙重把關 標示 ★ 的工具第一次呼叫絕不會花費點數:它會回傳點數報價與一組簽章 confirmToken,代理必須先把報價顯示給你,取得你明確同意後才會繼續。另外,沒有 stories:generate 的金鑰在技術上就無法花費點數——若想要一個只讀取與編輯的代理,請建立僅含 stories:write 的金鑰。
它如何維持安全與正確
- 伺服器會強制執行編輯器的規則(滑動移動、裁切邊界、字幕軌道範圍、圖層列),違規時會回傳允許的範圍,而不是默默地把數值夾住。
- 代理會從 yoh://guide/timeline 資源讀取完整規則,而 /mcp__yoh__edit-story 提示會提供安全的工作流程。
- 生成是非同步的——工具會回傳 jobId,代理再以 get_job 輪詢,與 REST API 相同。
- 在 .mcp.json 設定較長的 timeout(例如 900000),避免長時間輸出在用戶端逾時。
驗證你的連線
儲存庫附有煙霧測試。它會走真實協定、實測各項規則,並驗證花費閘門確實生效——過程完全不花費點數,唯一的寫入也會還原:
node scripts/mcp-smoke.mjs --url https://www.yoh.app/api/mcp --key sk_live_YOUR_API_KEY
# read-only variant:
node scripts/mcp-smoke.mjs --url https://www.yoh.app/api/mcp --key sk_live_YOUR_API_KEY --skip-write管理 API 金鑰
API 金鑰管理端點使用 session authentication(已登入用戶的 Cookie),而非 API 金鑰。使用 API Keys page 進行瀏覽器工作流程,或從後端以有效 Session 以程式方式呼叫這些端點。 /api-keys
列出 API 金鑰
/api/v1/api-keysscope: session{
"data": [{
"id": "key_abc123", "name": "Production",
"keyPrefix": "sk_live_7WH3",
"scopes": ["stories:generate", "stories:read", "assets:read"],
"revokedAt": null, "createdAt": "2026-04-03T10:00:00.000Z"
}]
}建立 API 金鑰
/api/v1/api-keysscope: session// Response — full key shown once
{
"id": "key_abc123",
"key": "sk_live_...",
"name": "My integration",
"keyPrefix": "sk_live_7WH3",
"scopes": ["stories:generate", "stories:read", "assets:read"]
}撤銷 API 金鑰
/api/v1/api-keys/:idscope: session{ "id": "key_abc123", "revoked": true }錯誤代碼
HTTP 409 with error.code = generation_budget_exceeded means your optional X-Max-Generation-Credits cap was exceeded. The response includes quote.credits; nothing was charged. Change the request or explicitly raise your budget to retry. A malformed cap returns 400. Other 409 conflicts can mean an idempotency key was reused with different input or work is already running. Template-version generation uses its own persisted quoteId contract; see its OpenAPI reference.
多數端點會傳回含有機器可讀取 code 的 error 物件。部分專用端點可能傳回字串錯誤或頂層代碼;請一律檢查 HTTP 狀態。
{ "error": { "code": "insufficient_credits", "message": "...", "status": 402 } }| HTTP | 代碼 | 含義 |
|---|---|---|
| 401 | unauthorized | 缺少或無效的 API 金鑰。 |
| 403 | forbidden | API 金鑰沒有所需的範圍。 |
| 404 | not_found | 請求的資源不存在。 |
| 400 | bad_request | 無效的請求本體或參數。 |
| 402 | insufficient_credits | 點數不足,無法執行此操作。 |
| 429 | rate_limited | 請求過多。請查看 Retry-After 標頭。 |
| 500 | internal_error | 意外的伺服器錯誤。 |
速率限制: 每個 API 金鑰的生成端點每分鐘限 5 次請求;讀取與不消耗點數的片段編輯每分鐘限 60 次。達到限制時,回應包含 Retry-After 標頭,其中包含等待的秒數。失敗的工作(所有重試後)自動退還已扣除的點數。
發布到社群
把完成的影片直接推送到已連接的帳號,讓自動化流程用同一組憑證跑完「生成 → 輸出 → 發布」。請先在應用程式內連接帳號,此端點才能發布到這些帳號。
發布影片
/api/v1/social/publishscope: social:publish回傳 202 與 { postId, status: "publishing" },並在背景上傳——請輪詢下方的狀態端點。若想改為等到上傳完成再回應,可傳入 wait: true(可能需要數分鐘)。 影片網址必須符合此故事的成功算繪結果或原始上傳影片。YouTube 串流僅允許公開網路位址,並遵守現有的 512 MB 上傳限制。
socialAccountIdstringstoryIdstringvideoUrlstringplatformstringcaptionstringyoutubeobjectdurationSecnumberaspectRatiostringfileSizeBytesnumberwaitboolean查詢發布狀態
/api/v1/social/publish/:postIdscope: social:publish生命週期:
publishing→published|failed.