API リファレンス
プログラムで AI 動画を生成します。プロンプトを送信し、完了をポーリングして、直接動画 URL を受け取ります — すべて REST で完結。
ベース URL: https://www.yoh.appサインインするまで、コード例はプレースホルダー sk_live_YOUR_API_KEY を使用します。
クイックスタート
3 回の 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 リクエストは、Bearer スキームを使用して Authorization ヘッダーに API キーを含める必要があります。
Authorization: Bearer sk_live_YOUR_API_KEYAPI キーは sk_live_ で始まります。API Keys page からキーを作成・管理できます。 /api-keys
API キーは秘密にしてください。クライアント側のコードや公開リポジトリに公開しないでください。キーが漏洩した場合は、すぐに API キーページから取り消してください。
スコープとクレジット
各 API キーには、アクセス可能なエンドポイントを制御する 1 つ以上のスコープが付与されます。ストーリーのテキスト生成はジョブあたり 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 を使用してすべてのクリップを 1 つの合成 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:readPOST /api/v1/stories/generate を呼び出した後にこのエンドポイントをポーリングします。ストーリーの長さや renderVideo が有効かどうかによって、ジョブには通常 8–15 分かかります。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 | ステータスが completed の場合の絶対動画 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 ページあたりの結果数(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 を保存しておけば、生成時に assets 配列の seriesAssetId としてその id を渡すだけで参照できます。動画ごとに画像リンクを作り直す必要はありません。渡された画像は当社ストレージに再ホストされるため、元のアップロード URL が失効しても id は使い続けられます。
| フィールド | 型 | 説明 |
|---|---|---|
type | string | character、scene、item、reference のいずれか。 |
name | string | 表示名。以降の生成で照合に使われるキーでもあります。 |
imageUrl | string | 画像の公開 URL。代わりに imageBase64 も指定できます。 |
itemType | string | 小道具のみ:prop、product、logo。logo は平面として合成され、立体的な奥行きは付きません。 |
isHeroProduct | boolean | 小道具のみ:この広告の主役となる商品。全クリップに登場します。 |
isPrimaryBackground | boolean | シーンのみ:すべてのアンカーショットで使い回すスタジオ/セット。 |
- アセットを 1 件送るか、"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 は顧客に対するあなた自身の ID です。同じ ID で二度呼び出しても、新しく作らず既存のアカウントを返すので、リトライは安全です。API キーが返るのは初回作成時の一度だけです。当社はハッシュのみを保存し再表示できないため、受け取った時点で保管してください。新しいアカウントの残高はゼロから始まります。
| フィールド | 型 | 説明 |
|---|---|---|
externalId | string | 必須。この顧客に対するあなたの ID(テナント ID など)。プラットフォーム内で一意である必要があります。 |
name | string | 任意のラベル。あなた自身のレポート用です。 |
管理対象アカウントのキーを交換する
/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 や購読期間 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 を自作せず、製品が備えるスタイルを適用するためのものです。字幕のスタイルはセリフ1行ごとに設定されます。 |
| キャラクターとセット | 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 を返し、エージェントは REST API と同じく get_job でポーリングします。
- .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-writeAPI キー管理
API キー管理エンドポイントは API キーではなく session authentication(ログイン済みユーザーの Cookie)を使用します。ブラウザベースのワークフローには API Keys page を使用するか、有効なセッションでバックエンドからプログラムでこれらのエンドポイントを呼び出してください。 /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 キーごとに、生成エンドポイントは 1 分あたり 5 リクエストまで。読み取りとクレジットを消費しないクリップ編集は 60 リクエストまでです。制限に達した場合、レスポンスには待機する秒数を含む Retry-After ヘッダーが含まれます。失敗したジョブ(全リトライ後)は差し引かれたクレジットを自動的に返還します。
SNSへの投稿
完成した動画を接続済みアカウントへ直接送れます。ひとつの認証情報で「生成 → レンダリング → 投稿」までを自動化できます。まずアプリ内でアカウントを接続してください。このエンドポイントはそこへ投稿します。
動画を投稿する
/api/v1/social/publishscope: social:publish202 と { postId, status: "publishing" } を返し、アップロードはバックグラウンドで進みます。下のステータスエンドポイントをポーリングしてください。代わりにアップロード完了まで待たせたい場合は wait: true を渡します(数分かかることがあります)。 動画 URL は、このストーリーの正常に完了したレンダリング結果または元のアップロード動画と一致する必要があります。YouTube のストリームは公開アドレスのみを許可し、既存の 512 MB 制限が適用されます。
socialAccountIdstringstoryIdstringvideoUrlstringplatformstringcaptionstringyoutubeobjectdurationSecnumberaspectRatiostringfileSizeBytesnumberwaitboolean投稿ステータスを確認する
/api/v1/social/publish/:postIdscope: social:publishライフサイクル:
publishing→published|failed.