API 레퍼런스
프로그래밍 방식으로 AI 동영상을 생성합니다. 프롬프트를 제출하고, 완료를 폴링하고, 직접 동영상 URL을 받으세요 — 모두 REST로.
기본 URL: https://yoh.app로그인하기 전까지 코드 예시에는 플레이스홀더 sk_live_YOUR_API_KEY가 사용됩니다.
빠른 시작
세 번의 API 호출로 첫 번째 동영상 만들기: 생성 → 폴링 → 다운로드.
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 요청은 Bearer 스킴을 사용하여 Authorization 헤더에 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 — 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. |
| 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 |
동영상 생성
동영상 생성 작업 큐 추가
/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, 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 | 선택 | 일관된 캐릭터 비주얼을 위해 기존 시리즈에 연결합니다. |
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, 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 |
|---|---|---|---|
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 |
imageModel | string | a model id, or auto | auto |
modelStrategy | string | auto · fixed · cost_optimized · quality_first · hero_premium | auto |
generationMode | string | DEPRECATED — ignored. Every clip uses unified reference mode. | 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 $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"
},
"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 — every clip is produced in the single unified reference mode, where a multi-reference model receives one categorized bundle: the storyboard sketch and clip image, character sheets, item, background and style images, the previous clip's tail as a video reference, and the character's voice sample as an audio reference.
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 '{"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 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, 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
}'
# 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: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–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 는 평면으로 합성되며 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://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://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://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 키 관리
API 키 관리 엔드포인트는 API 키가 아닌 session authentication(로그인한 사용자 쿠키)를 사용합니다. 브라우저 기반 워크플로에는 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 }오류
모든 오류는 기계 판독 가능한 code를 포함하는 error 객체가 있는 일관된 JSON 형태를 반환합니다.
{ "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 헤더가 포함됩니다. 실패한 작업(모든 재시도 후)은 차감된 크레딧을 자동으로 환불합니다.
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.