เอกสารอ้างอิง API
สร้างวิดีโอ AI ผ่านโปรแกรม ส่งพรอมป์ ตรวจสอบสถานะ แล้วรับ URL วิดีโอโดยตรง — ทั้งหมดผ่าน REST
Base URL: https://yoh.appตัวอย่างโค้ดจะใช้ตัวยึดตำแหน่ง sk_live_YOUR_API_KEY จนกว่าคุณจะลงชื่อเข้าใช้
เริ่มต้นใช้งาน
สร้างวิดีโอแรกของคุณด้วยการเรียก API เพียง 3 ครั้ง: สร้าง → ตรวจสอบสถานะ → ดาวน์โหลด
curl -X POST https://yoh.app/api/v1/stories/generate \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "prompt": "A dragon teaches a young knight how to fly", "aspectRatio": "16:9" }'{
"jobId": "cmnk8wu2q0001q3vpmqv93nmy",
"status": "queued",
"pollUrl": "/api/v1/stories/jobs/cmnk8wu2q0001q3vpmqv93nmy"
}curl https://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 ทั้งหมดต้องแนบ API key ของคุณในส่วนหัว Authorization โดยใช้รูปแบบ Bearer
Authorization: Bearer sk_live_YOUR_API_KEYAPI key จะขึ้นต้นด้วย sk_live_ คุณสามารถสร้างและจัดการ key ได้จากAPI Keys page /api-keys
เก็บ API key ของคุณเป็นความลับ อย่าเปิดเผยไว้ในโค้ดฝั่งไคลเอนต์หรือ repository สาธารณะ หาก key ถูกเปิดเผย ให้เพิกถอนทันทีจากหน้า API Keys
ขอบเขตสิทธิ์ & เครดิต
API key แต่ละอันจะได้รับสิทธิ์หนึ่งขอบเขตขึ้นไปที่ควบคุมว่า endpoint ใดที่เข้าถึงได้ การสร้างข้อความเรื่องราวมีค่าใช้จ่าย 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 /series-assets/ensure-character-image — and the clip edits: POST/PATCH /stories/:id/clips, PATCH/DELETE /stories/:id/clips/:clipId |
| stories:read | GET /api/v1/stories, GET /api/v1/stories/:id, GET /api/v1/stories/jobs/:id |
| assets:read | GET .../characters, .../scenes, .../items, .../clips |
| social:publish | POST /api/v1/social/publish, GET /api/v1/social/publish/:postId |
สร้างวิดีโอ
เพิ่มงานสร้างวิดีโอเข้าคิว
/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 — เรนเดอร์คลิปทั้งหมดรวมเป็น MP4 เดียวผ่าน Remotion (~12–15 นาที) |
scope | string | ไม่บังคับ | story (text only, recommended), assets (+ character/scene/item images), storyboard (+ per-clip images), videos (+ clip videos), render (+ final MP4), or full (all phases). Default: full — which charges video + render credits up front. An unrecognized value falls back to full. |
title | string | ไม่บังคับ | ชื่อที่แสดงของเรื่องราว |
contentType | string | ไม่บังคับ | หมวดหมู่รูปแบบเรื่องราว หนึ่งใน cinematic_story, news_analysis, persona_channel, ad_spot ค่าเริ่มต้น: cinematic_story |
locale | string | ไม่บังคับ | รหัสภาษา เช่น en, zh-TW ค่าเริ่มต้น: en |
seriesId | string | ไม่บังคับ | แนบเข้ากับซีรีส์ที่มีอยู่แล้วเพื่อให้ภาพตัวละครมีความสอดคล้องกัน |
globalConfig ไม่บังคับ
Optional nested object controlling duration, output format, and model selection — the same options the studio UI exposes. Every field has a default; pass only what you want to override. The model fields (videoModel, imageModel, modelStrategy, generationMode) may also be passed at the top level as a convenience. Unknown model ids degrade to auto rather than failing the request.
| ฟิลด์ | ประเภท | Values | Default |
|---|---|---|---|
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 | balanced |
stylePreset | string | preset slug, or auto | auto |
platformTarget | string | youtube · tiktok · instagram · x | youtube |
safetyMode | string | standard · strict | standard |
seed | string | any string (shared across clips for continuity) | — |
videoModel | string | a model id, or auto | auto |
imageModel | string | a model id, or auto | auto |
modelStrategy | string | auto · fixed · cost_optimized · quality_first · hero_premium | auto |
generationMode | string | auto · basic · start_end_frame · reference | 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.
| 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 $BASE/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",
"generationMode": "start_end_frame"
},
"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: generationMode (basic · start_end_frame · reference) and videoModel. Omit generationMode and the clip's own saved mode is used — the same rule the editor's single-clip panel follows.
estimatedCredits) | Scope: stories:generate# Regenerate clip 3's image, then its video — the rest of the story is untouched
curl -X POST $BASE/api/v1/stories/STORY_ID/clips/CLIP_ID/image \
-H "Authorization: Bearer sk_live_YOUR_API_KEY"
# Poll jobId until completed
curl -X POST $BASE/api/v1/stories/STORY_ID/clips/CLIP_ID/video \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"generationMode":"start_end_frame"}'
# 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 is the prompt the generation pipelines read — edit it, 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, duration, generationDuration, generationMode, 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 $BASE/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,
"generationMode": "reference"
}'
# 2. Re-shoot it with the new direction
curl -X POST $BASE/api/v1/stories/STORY_ID/clips/CLIP_ID/image \
-H "Authorization: Bearer sk_live_YOUR_API_KEY"
curl -X POST $BASE/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 $BASE/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 $BASE/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 $BASE/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 $BASE/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ตรวจสอบ endpoint นี้หลังจากเรียก 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 วิดีโอแบบสมบูรณ์เมื่อสถานะเป็น completed อาจเป็น 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 ที่ได้จากงานนั้น endpoint แอสเซ็ตทั้งหมดต้องใช้ขอบเขตสิทธิ์ 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 ทั้งหมดเป็น 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 ของงานจะชี้ไปยังวิดีโอของคลิปแรก ใช้ endpoint นี้เพื่อรับ URL วิดีโอของคลิปแต่ละรายการ
แอสเซ็ตของซีรีส์
ลงทะเบียนภาพตัวละครสำหรับซีรีส์
/api/v1/series-assets/ensure-character-imagescope: stories:generateอัปโหลดและบันทึกภาพอ้างอิงตัวละครสำหรับซีรีส์ เพื่อให้ AI ใช้ใบหน้า/ลุคที่สอดคล้องกันในทุกเรื่องราวของซีรีส์นั้น การดำเนินการนี้เป็นแบบ idempotent — เรียกซ้ำหลายครั้งด้วยชื่อตัวละครเดียวกันได้อย่างปลอดภัย
| ฟิลด์ | ประเภท | คำอธิบาย |
|---|---|---|
seriesId | string | รหัสของซีรีส์ที่จะแนบตัวละครเข้าไป |
characterName | string | ชื่อของตัวละคร (ใช้เป็นคีย์เฉพาะภายในซีรีส์) |
imageUrl | string | URL ที่เข้าถึงได้แบบสาธารณะของภาพอ้างอิงตัวละคร |
จัดการ API Key
endpoint การจัดการ API key ใช้ session authentication (คุกกี้ของผู้ใช้ที่ล็อกอินอยู่) ไม่ใช่ API key ใช้ API Keys page สำหรับขั้นตอนการทำงานผ่านเบราว์เซอร์ หรือเรียก endpoint เหล่านี้ผ่านโปรแกรมจากแบ็กเอนด์ของคุณด้วยเซสชันที่ถูกต้อง /api-keys
รายการ API key
/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 key
/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 key
/api/v1/api-keys/:idscope: session{ "id": "key_abc123", "revoked": true }ข้อผิดพลาด
ข้อผิดพลาดทั้งหมดจะคืนค่าในรูปแบบ JSON ที่สอดคล้องกัน โดยมีอ็อบเจ็กต์ error ที่มี code ที่เครื่องอ่านได้
{ "error": { "code": "insufficient_credits", "message": "...", "status": 402 } }| HTTP | code | ความหมาย |
|---|---|---|
| 401 | unauthorized | ไม่มี API key หรือ API key ไม่ถูกต้อง |
| 403 | forbidden | API key ไม่มีขอบเขตสิทธิ์ที่จำเป็น |
| 404 | not_found | ไม่พบทรัพยากรที่ร้องขอ |
| 400 | bad_request | เนื้อหาคำขอหรือพารามิเตอร์ไม่ถูกต้อง |
| 402 | insufficient_credits | เครดิตไม่เพียงพอสำหรับดำเนินการนี้ |
| 429 | rate_limited | คำขอมากเกินไป ตรวจสอบส่วนหัว Retry-After |
| 500 | internal_error | เกิดข้อผิดพลาดของเซิร์ฟเวอร์ที่ไม่คาดคิด |
ขีดจำกัดอัตราการเรียก: endpoint การสร้างอนุญาต 5 คำขอต่อนาทีต่อ API key การอ่านข้อมูลและการแก้ไขคลิป (ซึ่งไม่ใช้เครดิต) อนุญาต 60 คำขอ เมื่อคุณถึงขีดจำกัด การตอบกลับจะมีส่วนหัว Retry-After ระบุจำนวนวินาทีที่ต้องรอ งานที่ล้มเหลว (หลังจากลองใหม่ครบทุกครั้ง) จะคืนเครดิตที่ถูกหักไปโดยอัตโนมัติ
Publish to Social
Push a finished video straight to a connected account, so an automation can drive generate → render → publish with one credential. Connect the accounts in the app first; this endpoint publishes to them.
Publish a video
/api/v1/social/publishscope: social:publishReturns
202with{ postId, status: "publishing" }and uploads in the background — poll the status endpoint below. Passwait: trueto block until the upload finishes instead (can take minutes).socialAccountIdstringstoryIdstringvideoUrlstringplatformstringcaptionstringyoutubeobjectdurationSecnumberaspectRatiostringfileSizeBytesnumberwaitbooleanPoll publish status
/api/v1/social/publish/:postIdscope: social:publishLifecycle:
publishing→published|failed.