# Videostew.ai API·MCP

MCP: `https://videostew.ai/mcp` · HTTP: `https://videostew.ai/api/v1` · 인증: OAuth 2.1(MCP, `https://videostew.ai/.well-known/oauth-authorization-server`) 또는 `Authorization: Bearer <키>`

## 씬·컷 프로젝트 저작

MCP와 HTTP API는 프로젝트 5개·에셋 4개·음성/추출 5개, 총 14개 도구를 공유합니다. MCP 주소는 https://videostew.ai/mcp, HTTP 주소는 https://videostew.ai/api/v1입니다.

list_projects에서 워크스페이스를 확인하고 create_project로 빈 프로젝트를 만듭니다. 새 프로젝트의 확정 버전은 0입니다.

저장 버전은 /{projectId}/v/{version}에서 씬·컷 시점에 따라 이미지·영상·나레이션을 함께 재생합니다. render_version으로 저장 버전을 음성 포함 MP4로 추출하고 get_job에서 진행·다운로드 주소를 받습니다.

## 필요한 씬과 코드만 읽기

get_project는 기본적으로 공통 설정과 씬 목록 한 페이지를 반환합니다. sceneIds를 지정하면 해당 씬 본문만 읽고 전체 씬 목록은 기본 생략합니다.

코드는 includeCode=true와 startLine·lineCount로 필요한 부분만 읽습니다. codeRange의 totalLines로 남은 범위를 확인합니다. 목록은 offset·limit과 nextOffset으로 이어 읽습니다.

씬 본문은 id·code·params와 cuts 또는 duration입니다. 컷은 id·text(선택)·speech(선택)·duration을 가집니다. list_voices로 목소리·모델을 조회하고 generate_speech로 음성을 생성합니다. 완료된 get_job의 reference를 컷 speech에 넣고 duration.media를 speech.id로 정하면 실측 길이에 맞춰 계산합니다. 씬 코드는 export default async function setup(app, PIXI, ctx)에서 update(t)를 반환하며, ctx의 params·cuts·theme와 씬 로컬 시간 t를 사용합니다.

## 씬 코드 실행 계약과 재료

- 씬 실행 계약:
- code는 export default async function setup(app, PIXI, ctx)에서 동기 update(t)를 반환하는 ES 모듈입니다. app은 프로젝트 크기의 PixiJS 8.6.6 Application이며 ticker는 정지돼 있습니다. setup에서 표시 객체를 만들고 app.stage에 추가합니다. 화면 캡처는 update(t) 뒤 app.render()로 실행합니다.
- ctx는 width, height, fps, duration(초), params, theme, cuts, random(seed), asset(reference)을 제공합니다. cuts의 각 항목은 id, text(선택), start, end, duration, mediaStart이며 모든 시간은 씬 로컬 초입니다. 컷 길이는 정수 프레임으로 반올림해 합산하고 씬 duration이 됩니다.
- 씬·컷 id는 소문자 영문·숫자로 시작하고 소문자 영문·숫자·대시만 사용하며 최대 64자입니다. 첫 글자부터 적용되는 형식은 /^[a-z0-9][a-z0-9-]{0,63}$/입니다.
- update(t)는 0 ≤ t < ctx.duration에서 순서와 무관하게 같은 화면을 만듭니다. 프레임마다 t로 위치·투명도·필터 상태를 계산합니다. ctx.random(seed: 정수) → () => number는 난수 생성 함수를 반환하며 그 함수를 호출할 때마다 [0, 1) 값을 반환합니다. 예: const next = ctx.random(123); const x = next(); 같은 시드는 같은 수열입니다. 파티클 등 난수 재료는 setup에서 준비하거나 시간에서 결정적으로 계산할 수 있습니다.
- 씬은 역할·전달 목적의 경계이며 같은 내용의 박자 변화는 씬 안의 컷으로 전개합니다. 바뀔 문구·수치·항목은 params에 두고 발화에 맞출 동작은 ctx.cuts의 시점을 사용합니다. 씬·컷 개수나 연출 기법을 고정하지 않습니다.
- 이미지·영상은 list_assets/get_asset으로 찾거나 import_asset/generate_asset으로 확보합니다. 생성·가져오기는 에셋 ID를 반환하며 get_asset으로 queued/running/ready/failed를 조회합니다. ready의 reference={$asset,sha256}를 씬 params에 그대로 두세요. 에셋 ID·만료 URL을 코드에 박지 마세요. ctx.asset(ctx.params.hero)는 준비된 {texture,width,height,duration?,time}을 반환하며 new PIXI.Sprite(media.texture)로 그릴 수 있습니다.
- 영상은 소리 없이 재생하며 자동 재생 시계를 사용하지 않습니다. 기본 time은 매 update(t) 호출의 t입니다. 컷에 맞추려면 update 안에서 media.time=t-ctx.cuts[0].mediaStart처럼 지정할 수 있습니다. 실행기가 0..소재 끝으로 제한하고 프레임을 준비한 뒤 화면을 그립니다. duration:{media:에셋 번호,pad:[앞,뒤]}는 같은 씬 params에서 참조한 영상의 실측 길이를 사용합니다. 영상 원본 fps와 프로젝트 출력 fps는 별개입니다.
- 같은 영상을 다음 컷·씬에 이어 쓸 때는 원본 재생 위치도 이어갑니다. 사용 구간의 원본 시작 초를 params에 두고 media.time=ctx.params.sourceStart+t-해당구간시작으로 계산하세요. 다음 구간은 앞 구간이 사용한 원본 끝에서 시작하도록 params를 작성합니다. 매 씬의 0초로 재시작하거나 원본 길이가 부족한 상태를 마지막 프레임 정지로 채워 연속 재생처럼 보이게 하지 마세요. 원본 시작 위치도 저장 버전과 씬 입력에 고정하므로 순서와 무관한 탐색·추출이 같습니다.
- list_voices에서 목소리를 고르고 generate_speech({text,voiceId,model:'eleven_v4'})로 나레이션을 생성합니다. get_job의 성공 결과 reference를 컷 speech에, 생성한 원고 그대로 text에 둡니다. duration:{media:음성작업ID,pad:[앞,뒤]}는 그 음성의 실측 길이로 컷을 계산합니다. 씬의 발화 연출은 ctx.cuts의 start/end/mediaStart에 연결하세요. 생성 음성 파일은 내부 파생 결과로 보존하며 이미지·영상 에셋 목록에 넣지 않습니다.
- 현재 실행기는 아래 고정 라이브러리·서체와 명시적으로 참조한 파일만 제공하며 외부 네트워크 리소스와 임의 패키지 import는 준비하지 않습니다. 사용자에게 결과를 보여 주거나 검토·수정을 안내할 때는 항상 /{projectId}/edit 편집 주소를 제공합니다. 특정 씬은 ?scene={sceneId}, 특정 시점은 ?t={초}로 지정합니다. 둘을 함께 쓰면 t는 씬 로컬 초, t만 쓰면 프로젝트 전체 초입니다. 주소를 열면 해당 시점에 멈추며 같은 편집기에서 AI 대화로 이어 수정합니다. render_version/get_job은 저장한 버전의 MP4를 추출합니다. 씬 사이 상태 전달·독립 디자인 검수는 후속입니다.
- 현재 실행 환경에 준비된 서체(fontFamily 이름 그대로; 재료는 계속 확장):
- 'Pretendard' 100~900: 한글·영문 산세리프 본문·제목
- 'Noto Serif KR' 200~900(가변): 한글 명조/세리프
- 'Hahmlet' 100~900(가변): 한글 디스플레이 세리프, 개성 있는 굵은 제목
- 'Black Han Sans' 400: 한글 아주 굵은 고딕 디스플레이
- 'Do Hyeon' 400: 한글 좁고 둥근 디스플레이
- 'Gasoek One' 400: 한글 초굵은 디스플레이
- 'Playfair Display' 400~900, italic 포함: 영문 고대비 디스플레이 세리프
- 'Instrument Serif' 400, italic 포함: 영문 에디토리얼 세리프
- 'Bebas Neue' 400, 'Anton' 400: 영문 좁은 대문자 디스플레이
- 'Space Grotesk' 300~700: 영문 그로테스크
- 'JetBrains Mono' 100~800: 숫자·코드 고정폭
- 영문 전용 서체의 한글은 다른 서체로 대체되므로 한글 문구에는 한글 서체를 쓴다.
- 필터: PIXI 내장 필터와 pixi-filters 6.1.5 전체(PIXI.filters.GlowFilter, AdvancedBloomFilter, BloomFilter, MotionBlurFilter, ZoomBlurFilter, RGBSplitFilter, GlitchFilter, CRTFilter, OldFilmFilter, ShockwaveFilter, GodrayFilter, DropShadowFilter, OutlineFilter, PixelateFilter, DotFilter, AsciiFilter, BulgePinchFilter, TwistFilter, ReflectionFilter, ColorOverlayFilter, AdjustmentFilter, KawaseBlurFilter, TiltShiftFilter 등).
- 씬 사이 전환 재료:
- 들어오는 씬의 transition에 { name, duration: 초, params?: { ... } }를 지정합니다. 생략하면 즉시 교체하며 첫 씬에서는 전환이 실행되지 않습니다.
- 전환은 들어오는 씬의 로컬 0초부터 시작합니다. 앞 씬의 마지막 프레임과 들어오는 씬의 현재 프레임을 합성합니다. 프로젝트·씬·컷 길이는 늘이거나 겹치지 않으며 duration은 들어오는 씬 길이 이하여야 합니다.
- 기본 전환: fade, fade-black, fade-white, push-left, push-right, push-up, push-down, cover-left, cover-right, cover-up, cover-down, uncover-left, uncover-right, uncover-up, uncover-down, wipe-left, wipe-right, wipe-up, wipe-down, whip-pan-left, whip-pan-right, whip-pan-up, whip-pan-down, cube-left, cube-right, cube-up, cube-down, flip-left, flip-right, flip-up, flip-down, split-h, split-v, barn-door-h, barn-door-v, iris-in, iris-out, clock-wipe, radial-wipe, venetian-blinds, checker-wipe, zoom-in, zoom-out, spin, page-peel.
- GL 전환(이름의 대소문자 보존; 괄호는 생략 가능한 파라미터의 기본값):
- BookFlip {}
- Bounce {"shadow_colour":[0,0,0,0.6],"shadow_height":0.075,"bounces":3}
- ButterflyWaveScrawler {"amplitude":1,"waves":30,"colorSeparation":0.3}
- CrossZoom {"strength":0.4}
- DoomScreenTransition {"bars":30,"amplitude":2,"noise":0.1,"frequency":0.5,"dripScale":0.5}
- DreamyZoom {"rotation":6,"scale":1.2}
- FilmBurn {"Seed":2.31}
- GlitchDisplace {}
- GlitchMemories {}
- GridFlip {"size":[4,4],"pause":0.1,"dividerWidth":0.05,"bgcolor":[0,0,0,1],"randomness":0.1}
- InvertedPageCurl {}
- Mosaic {"endx":2,"endy":-1}
- StereoViewer {"zoom":0.88,"corner_radius":0.22}
- Swirl {}
- WaterDrop {"amplitude":30,"speed":30}
- ZoomInCircles {}
- cube {"persp":0.7,"unzoom":0.3,"reflection":0.4,"floating":3}
- displacement {"strength":0.5}
- doorway {"reflection":0.4,"perspective":0.4,"depth":3}
- flyeye {"size":0.04,"zoom":50,"colorSeparation":0.3}
- hexagonalize {"steps":50,"horizontalHexagons":20}
- kaleidoscope {"speed":1,"angle":1,"power":1.5}
- luma {}
- luminance_melt {"direction":false,"l_threshold":0.8,"above":false}
- morph {"strength":0.1}
- perlin {"scale":4,"smoothness":0.01,"seed":12.9898}
- pixelize {"squaresMin":[20,20],"steps":50}
- ripple {"amplitude":100,"speed":50}
- swap {"reflection":0.4,"perspective":0.2,"depth":3}
- luma와 displacement는 실행기가 제공하는 고정 시드 기본 마스크·변위 맵을 씁니다. 사용자 이미지 맵 바인딩은 아직 제공하지 않습니다.
- 내용과 씬 사이 관계에 맞는 재료를 골라 쓰며 모든 경계에 전환을 넣을 의무는 없습니다. 씬 내부의 자유로운 Pixi 연출을 이 목록으로 제한하지 않습니다.

## Codex·Claude Code에서 씬 파일로 작업하기

현재 파일 MCP는 저장소의 apps/web/scripts/scene-mcp.ts를 Node 24로 실행하는 stdio 입구입니다. 클라이언트 컴퓨터에서 씬 파일 전용 작업 디렉터리를 지정합니다. 원격 연결의 워크스페이스 API 키는 MCP 프로세스 환경의 VIDEOSTEW_API_TOKEN으로 전달하며 모델 인자에 넣지 않습니다.

같은 도구를 제공합니다. get_project가 선택한 씬 전체를 파일로 받아 files[].sceneFile을 반환합니다. files[].modified=true이면 보존한 로컬 편집이 조회한 서버 본문과 다릅니다. sha는 서버 기준 해시입니다. 에이전트가 그 파일의 필요한 부분을 수정한 뒤 update_project의 insert·replace에 sceneFile 경로만 보냅니다. 프로그램이 씬 전체를 전송하므로 LLM이 코드 전체를 다시 출력하지 않습니다. import_asset은 작업 디렉터리 안의 filePath를 받아 바이트를 전송하고, get_asset은 준비된 파일을 받아 filePath로 돌려줍니다.

이미 편집 중인 로컬 파일은 재조회로 덮어쓰지 않습니다. 명시적 저장 전에는 수정 파일을 update_project로 전송합니다. 내용의 역할·연출 목적에 맞게 씬을 나누되 코드 크기로 창작 표현을 제한하지 않습니다.

로컬 MCP 실행 명령:

```sh
node apps/web/scripts/scene-mcp.ts --directory ./scenes --url https://videostew.ai/mcp
```

## 씬 단위 수정과 명시적 저장

update_project는 expectedSeq를 기준으로 changes 묶음을 원자적으로 적용합니다. insert·replace는 씬 전체, remove·move는 씬 ID를 받습니다. settings는 바꿀 meta 또는 theme 전체 값을 받습니다.

에이전트는 로컬 씬 파일을 부분 수정하고 프로그램이 씬 전체를 전송합니다. 씬 코드 전체를 LLM이 재출력하지 않습니다. 서버는 기존 씬을 읽거나 코드 패치를 적용하지 않고 구조·권한·수정 기준을 검사한 뒤 저장합니다. 프로젝트 전체는 다시 보내지 않습니다.

수정 적용은 작업 상태의 seq를 증가시키지만 버전을 만들지 않습니다. 사용자의 명시적 저장이나 벌크 생산 완료 때 save_version으로 현재 내용을 고정합니다. 저장에는 본문을 보내지 않습니다.

변경 요청마다 UUID requestId를 만들고 같은 요청의 재전송에는 같은 값을 사용합니다. 같은 requestId의 다른 내용은 conflict입니다. version_conflict가 나면 필요한 최신 내용을 다시 읽고 수정안을 검토합니다.

프로그램이 전송하는 수정된 씬 전체:

```json
{
  "projectId": 12,
  "requestId": "00000000-0000-4000-8000-000000000001",
  "expectedSeq": 1,
  "changes": [
    {
      "op": "replace",
      "scene": {
        "id": "intro",
        "code": "export default async function setup(app, PIXI, ctx) { return t => {}; }",
        "params": {
          "title": "수정한 제목"
        },
        "duration": 3
      }
    }
  ]
}
```

## 인증과 권한

API 키 또는 OAuth 접근 토큰을 Authorization: Bearer 헤더로 보냅니다. 웹 호출은 로그인 쿠키를 사용합니다. 모든 입구가 현재 워크스페이스 참여 권한과 인증 범위를 확인합니다.

키는 워크스페이스 하나에 속하며 현재 권한 범위를 넘을 수 없습니다. 개발 서버의 데모 인증은 운영 빌드에 포함되지 않습니다.

## 기능 목록

| 구분 | MCP 도구 | HTTP | 하는 일 |
|---|---|---|---|
| 프로젝트 | `list_projects` | `GET /api/v1/projects` | 프로젝트 목록 |
| 프로젝트 | `create_project` | `POST /api/v1/projects` | 프로젝트 생성 |
| 프로젝트 | `get_project` | `GET /api/v1/projects/{projectId}` | 프로젝트·씬 조회 |
| 프로젝트 | `update_project` | `PATCH /api/v1/projects/{projectId}` | 프로젝트 작업 상태 수정 |
| 프로젝트 | `save_version` | `POST /api/v1/projects/{projectId}/versions` | 명시적 버전 저장 |
| 에셋 | `list_assets` | `GET /api/v1/assets` | 에셋 목록·검색 |
| 에셋 | `get_asset` | `GET /api/v1/assets/{assetId}` | 에셋·생성 결과 조회 |
| 에셋 | `import_asset` | `POST /api/v1/assets/imports` | 이미지·영상 가져오기 |
| 에셋 | `generate_asset` | `POST /api/v1/assets/generations` | 이미지·영상 생성 |
| 음성·추출 | `list_voices` | `GET /api/v1/voices` | ElevenLabs 목소리 조회 |
| 음성·추출 | `generate_speech` | `POST /api/v1/speech` | 나레이션 생성 |
| 음성·추출 | `get_job` | `GET /api/v1/jobs/{jobId}` | 음성·추출 작업 조회 |
| 음성·추출 | `render_version` | `POST /api/v1/projects/{projectId}/renders` | 저장 버전 MP4 추출 |
| 음성·추출 | `list_renders` | `GET /api/v1/projects/{projectId}/renders` | 프로젝트 추출 목록 |

### list_projects — 프로젝트 목록

`GET /api/v1/projects` · 읽기 전용

접근 가능한 워크스페이스와 씬·컷 프로젝트 목록을 읽습니다. 본문 코드는 반환하지 않습니다.

| 입력 | 위치 | 타입 | 필수 | 설명 |
|---|---|---|---|---|
| `workspaceId` | query | `integer` |  | 워크스페이스 번호 |
| `limit` | query | `integer` |  | 기본 30 |
| `offset` | query | `integer` |  | 기본 0 |

입력 JSON Schema:

```json
{"type":"object","properties":{"workspaceId":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"워크스페이스 번호"},"limit":{"default":30,"type":"integer","minimum":1,"maximum":100},"offset":{"default":0,"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}
```

결과 JSON Schema:

```json
{"type":"object","properties":{"workspaces":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"워크스페이스 번호"},"name":{"type":"string"},"permissions":{"type":"string"}},"required":["id","name","permissions"],"additionalProperties":false}},"projects":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$","description":"프로젝트 ID(UUID)"},"workspaceId":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"워크스페이스 번호"},"title":{"type":"string"},"version":{"type":"integer","minimum":0,"maximum":9007199254740991},"seq":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["id","workspaceId","title","version","seq"],"additionalProperties":false}},"total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["workspaces","projects","total"],"additionalProperties":false}
```

### create_project — 프로젝트 생성

`POST /api/v1/projects`

빈 씬·컷 프로젝트 또는 같은 워크스페이스의 저장 버전을 기반으로 새 작업 상태를 만듭니다. baseProjectId·baseVersion을 함께 지정하면 해당 버전의 meta·theme·씬·음성 참조를 코드 재출력 없이 그대로 복사합니다. 기반의 미저장 수정과 이후 변경은 전파하지 않습니다. 새 프로젝트의 버전은 0이며 명시적 save_version 전에는 확정 버전이 없습니다.

| 입력 | 위치 | 타입 | 필수 | 설명 |
|---|---|---|---|---|
| `workspaceId` | body | `integer` |  | 워크스페이스 번호 |
| `requestId` | body | `string` | 예 | 요청의 고유 ID. 같은 요청을 재전송할 때는 같은 값을 사용합니다. |
| `title` | body | `string` | 예 |  |
| `meta` | body | `object` |  |  |
| `theme` | body | `object` |  |  |
| `baseProjectId` | body | `string` |  | 프로젝트 ID(UUID) |
| `baseVersion` | body | `integer` |  |  |

입력 JSON Schema:

```json
{"type":"object","properties":{"workspaceId":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"워크스페이스 번호"},"requestId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"요청의 고유 ID. 같은 요청을 재전송할 때는 같은 값을 사용합니다."},"title":{"type":"string","minLength":1,"maxLength":200},"meta":{"type":"object","properties":{"width":{"type":"integer","minimum":16,"maximum":3840,"multipleOf":2},"height":{"type":"integer","minimum":16,"maximum":3840,"multipleOf":2},"fps":{"anyOf":[{"type":"number","const":10},{"type":"number","const":15},{"type":"number","const":24},{"type":"number","const":25},{"type":"number","const":30},{"type":"number","const":50},{"type":"number","const":60}]}},"required":["width","height","fps"],"additionalProperties":false},"theme":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}},"baseProjectId":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$","description":"프로젝트 ID(UUID)"},"baseVersion":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"required":["requestId","title"],"additionalProperties":false,"$defs":{"__schema0":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"$ref":"#/$defs/__schema0"}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}]}}}
```

결과 JSON Schema:

```json
{"type":"object","properties":{"project":{"type":"object","properties":{"id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$","description":"프로젝트 ID(UUID)"},"workspaceId":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"워크스페이스 번호"},"title":{"type":"string"},"version":{"type":"integer","minimum":0,"maximum":9007199254740991},"seq":{"type":"integer","minimum":0,"maximum":9007199254740991},"meta":{"type":"object","properties":{"width":{"type":"integer","minimum":16,"maximum":3840,"multipleOf":2},"height":{"type":"integer","minimum":16,"maximum":3840,"multipleOf":2},"fps":{"anyOf":[{"type":"number","const":10},{"type":"number","const":15},{"type":"number","const":24},{"type":"number","const":25},{"type":"number","const":30},{"type":"number","const":50},{"type":"number","const":60}]}},"required":["width","height","fps"],"additionalProperties":false},"theme":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}},"scenes":{"maxItems":1000,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,63}$"},"sha":{"type":"string","pattern":"^[a-f0-9]{64}$"},"description":{"type":"string"},"cutCount":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["id","sha","cutCount"],"additionalProperties":false}},"sceneCount":{"type":"integer","minimum":0,"maximum":9007199254740991},"nextOffset":{"anyOf":[{"type":"integer","minimum":0,"maximum":9007199254740991},{"type":"null"}]},"state":{"type":"string","enum":["working","saved"]}},"required":["id","workspaceId","title","version","seq","meta","theme","scenes","sceneCount","nextOffset","state"],"additionalProperties":false}},"required":["project"],"additionalProperties":false,"$defs":{"__schema0":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"$ref":"#/$defs/__schema0"}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}]}}}
```

### get_project — 프로젝트·씬 조회

`GET /api/v1/projects/{projectId}` · 읽기 전용

기본은 공통 설정과 씬 목록 한 페이지를 읽습니다. sceneIds를 지정하면 선택한 씬 본문만 읽고 전체 씬 목록은 생략합니다. 코드는 includeCode=true일 때 지정 줄 범위만 반환합니다. version을 지정하면 불변 저장본을 읽습니다. 읽은 seq를 수정 기준으로 유지합니다.

| 입력 | 위치 | 타입 | 필수 | 설명 |
|---|---|---|---|---|
| `projectId` | path | `string` | 예 | 프로젝트 ID(UUID) |
| `version` | query | `integer` |  |  |
| `sceneIds` | query | `string[]` |  | 기본 [] |
| `includeTiming` | query | `boolean` |  | true이면 모든 씬과 참조 에셋의 실측 길이로 정수 프레임 시점을 계산해 timing으로 반환합니다.. 기본 false |
| `includeSceneList` | query | `boolean` |  | 기본은 sceneIds가 없을 때만 목록 포함 |
| `offset` | query | `integer` |  | 기본 0 |
| `limit` | query | `integer` |  | 기본 50 |
| `includeCode` | query | `boolean` |  | 기본 false |
| `startLine` | query | `integer` |  | 기본 1 |
| `lineCount` | query | `integer` |  | 기본 120 |

입력 JSON Schema:

```json
{"type":"object","properties":{"projectId":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$","description":"프로젝트 ID(UUID)"},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"sceneIds":{"default":[],"maxItems":20,"type":"array","items":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,63}$"}},"includeTiming":{"default":false,"description":"true이면 모든 씬과 참조 에셋의 실측 길이로 정수 프레임 시점을 계산해 timing으로 반환합니다.","type":"boolean"},"includeSceneList":{"description":"기본은 sceneIds가 없을 때만 목록 포함","type":"boolean"},"offset":{"default":0,"type":"integer","minimum":0,"maximum":9007199254740991},"limit":{"default":50,"type":"integer","minimum":1,"maximum":100},"includeCode":{"default":false,"type":"boolean"},"startLine":{"default":1,"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"lineCount":{"default":120,"type":"integer","minimum":1,"maximum":400}},"required":["projectId"],"additionalProperties":false}
```

결과 JSON Schema:

```json
{"type":"object","properties":{"project":{"type":"object","properties":{"id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$","description":"프로젝트 ID(UUID)"},"workspaceId":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"워크스페이스 번호"},"title":{"type":"string"},"version":{"type":"integer","minimum":0,"maximum":9007199254740991},"seq":{"type":"integer","minimum":0,"maximum":9007199254740991},"meta":{"type":"object","properties":{"width":{"type":"integer","minimum":16,"maximum":3840,"multipleOf":2},"height":{"type":"integer","minimum":16,"maximum":3840,"multipleOf":2},"fps":{"anyOf":[{"type":"number","const":10},{"type":"number","const":15},{"type":"number","const":24},{"type":"number","const":25},{"type":"number","const":30},{"type":"number","const":50},{"type":"number","const":60}]}},"required":["width","height","fps"],"additionalProperties":false},"theme":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}},"scenes":{"maxItems":1000,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,63}$"},"sha":{"type":"string","pattern":"^[a-f0-9]{64}$"},"description":{"type":"string"},"cutCount":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["id","sha","cutCount"],"additionalProperties":false}},"sceneCount":{"type":"integer","minimum":0,"maximum":9007199254740991},"nextOffset":{"anyOf":[{"type":"integer","minimum":0,"maximum":9007199254740991},{"type":"null"}]},"state":{"type":"string","enum":["working","saved"]}},"required":["id","workspaceId","title","version","seq","meta","theme","scenes","sceneCount","nextOffset","state"],"additionalProperties":false},"timing":{"type":"object","properties":{"width":{"type":"number"},"height":{"type":"number"},"fps":{"type":"number"},"frames":{"type":"number"},"duration":{"type":"number"},"scenes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,63}$"},"start":{"type":"number"},"startFrame":{"type":"number"},"frames":{"type":"number"},"duration":{"type":"number"},"transition":{"type":"object","properties":{"name":{"type":"string","enum":["fade","fade-black","fade-white","push-left","push-right","push-up","push-down","cover-left","cover-right","cover-up","cover-down","uncover-left","uncover-right","uncover-up","uncover-down","wipe-left","wipe-right","wipe-up","wipe-down","whip-pan-left","whip-pan-right","whip-pan-up","whip-pan-down","cube-left","cube-right","cube-up","cube-down","flip-left","flip-right","flip-up","flip-down","split-h","split-v","barn-door-h","barn-door-v","iris-in","iris-out","clock-wipe","radial-wipe","venetian-blinds","checker-wipe","zoom-in","zoom-out","spin","page-peel","BookFlip","Bounce","ButterflyWaveScrawler","CrossZoom","DoomScreenTransition","DreamyZoom","FilmBurn","GlitchDisplace","GlitchMemories","GridFlip","InvertedPageCurl","Mosaic","StereoViewer","Swirl","WaterDrop","ZoomInCircles","cube","displacement","doorway","flyeye","hexagonalize","kaleidoscope","luma","luminance_melt","morph","perlin","pixelize","ripple","swap"]},"duration":{"type":"number","exclusiveMinimum":0,"maximum":3600},"params":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"number"},{"type":"boolean"},{"type":"array","items":{"type":"number"}}]}},"frames":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"required":["name","duration","frames"],"additionalProperties":false},"cuts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,63}$"},"start":{"type":"number"},"end":{"type":"number"},"duration":{"type":"number"},"mediaStart":{"type":"number"},"text":{"type":"string"}},"required":["id","start","end","duration","mediaStart"],"additionalProperties":false}}},"required":["id","start","startFrame","frames","duration","cuts"],"additionalProperties":false}}},"required":["width","height","fps","frames","duration","scenes"],"additionalProperties":false},"scenes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,63}$"},"sha":{"type":"string"},"content":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema1"}},"codeRange":{"type":"object","properties":{"startLine":{"type":"number"},"endLine":{"type":"number"},"totalLines":{"type":"number"}},"required":["startLine","endLine","totalLines"],"additionalProperties":false}},"required":["id","sha","content"],"additionalProperties":false}}},"required":["project","scenes"],"additionalProperties":false,"$defs":{"__schema0":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"$ref":"#/$defs/__schema0"}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}]},"__schema1":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"$ref":"#/$defs/__schema1"}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema1"}}]}}}
```

### update_project — 프로젝트 작업 상태 수정

`PATCH /api/v1/projects/{projectId}`

기준 expectedSeq에 변경 묶음을 원자적으로 적용합니다. insert는 새 씬 전체와 afterId, replace는 수정한 씬 전체를 받습니다. 씬의 code·params·cuts 또는 duration을 모두 보내며 기존 본문을 읽거나 패치하지 않습니다. remove·move는 sceneId, settings는 바꿀 meta 또는 theme의 전체 값을 받습니다. 로컬 씬 파일은 프로그램이 전송하고 LLM은 코드 전체를 재출력하지 마세요. 바뀐 씬만 저장하며 확정 버전을 만들지 않습니다. 충돌 시 최신 내용을 읽고 수정안을 검토하세요.

| 입력 | 위치 | 타입 | 필수 | 설명 |
|---|---|---|---|---|
| `projectId` | path | `string` | 예 | 프로젝트 ID(UUID) |
| `requestId` | body | `string` | 예 | 요청의 고유 ID. 같은 요청을 재전송할 때는 같은 값을 사용합니다. |
| `expectedSeq` | body | `integer` | 예 |  |
| `changes` | body | `object(op: insert \| replace \| remove \| move \| settings)[]` | 예 |  |

입력 JSON Schema:

```json
{"type":"object","properties":{"projectId":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$","description":"프로젝트 ID(UUID)"},"requestId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"요청의 고유 ID. 같은 요청을 재전송할 때는 같은 값을 사용합니다."},"expectedSeq":{"type":"integer","minimum":0,"maximum":9007199254740991},"changes":{"minItems":1,"maxItems":1000,"type":"array","items":{"oneOf":[{"type":"object","properties":{"op":{"type":"string","const":"insert"},"scene":{"type":"object","properties":{"id":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,63}$"},"description":{"type":"string","maxLength":500},"code":{"type":"string","minLength":1,"maxLength":1000000},"params":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}},"cuts":{"maxItems":1000,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,63}$"},"text":{"type":"string"},"speech":{"type":"object","properties":{"id":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"음성 작업 번호"},"sha256":{"type":"string","pattern":"^[a-f0-9]{64}$"}},"required":["id","sha256"],"additionalProperties":false},"duration":{"anyOf":[{"type":"number","exclusiveMinimum":0,"maximum":3600},{"type":"object","properties":{"media":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"소재(영상 에셋 또는 이 컷의 음성 작업) 번호"},"pad":{"type":"array","prefixItems":[{"type":"number","minimum":0,"maximum":10},{"type":"number","minimum":0,"maximum":10}],"items":false,"minItems":2,"maxItems":2}},"required":["media"],"additionalProperties":false}]}},"required":["id","duration"],"additionalProperties":false}},"duration":{"type":"number","exclusiveMinimum":0,"maximum":3600},"transition":{"type":"object","properties":{"name":{"type":"string","enum":["fade","fade-black","fade-white","push-left","push-right","push-up","push-down","cover-left","cover-right","cover-up","cover-down","uncover-left","uncover-right","uncover-up","uncover-down","wipe-left","wipe-right","wipe-up","wipe-down","whip-pan-left","whip-pan-right","whip-pan-up","whip-pan-down","cube-left","cube-right","cube-up","cube-down","flip-left","flip-right","flip-up","flip-down","split-h","split-v","barn-door-h","barn-door-v","iris-in","iris-out","clock-wipe","radial-wipe","venetian-blinds","checker-wipe","zoom-in","zoom-out","spin","page-peel","BookFlip","Bounce","ButterflyWaveScrawler","CrossZoom","DoomScreenTransition","DreamyZoom","FilmBurn","GlitchDisplace","GlitchMemories","GridFlip","InvertedPageCurl","Mosaic","StereoViewer","Swirl","WaterDrop","ZoomInCircles","cube","displacement","doorway","flyeye","hexagonalize","kaleidoscope","luma","luminance_melt","morph","perlin","pixelize","ripple","swap"]},"duration":{"type":"number","exclusiveMinimum":0,"maximum":3600},"params":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"number"},{"type":"boolean"},{"type":"array","items":{"type":"number"}}]}}},"required":["name","duration"],"additionalProperties":false}},"required":["id","code"],"additionalProperties":false},"afterId":{"anyOf":[{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,63}$"},{"type":"null"}],"description":"null이면 맨 앞, 나머지는 해당 씬 뒤"}},"required":["op","scene","afterId"],"additionalProperties":false},{"type":"object","properties":{"op":{"type":"string","const":"replace"},"scene":{"type":"object","properties":{"id":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,63}$"},"description":{"type":"string","maxLength":500},"code":{"type":"string","minLength":1,"maxLength":1000000},"params":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}},"cuts":{"maxItems":1000,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,63}$"},"text":{"type":"string"},"speech":{"type":"object","properties":{"id":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"음성 작업 번호"},"sha256":{"type":"string","pattern":"^[a-f0-9]{64}$"}},"required":["id","sha256"],"additionalProperties":false},"duration":{"anyOf":[{"type":"number","exclusiveMinimum":0,"maximum":3600},{"type":"object","properties":{"media":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"소재(영상 에셋 또는 이 컷의 음성 작업) 번호"},"pad":{"type":"array","prefixItems":[{"type":"number","minimum":0,"maximum":10},{"type":"number","minimum":0,"maximum":10}],"items":false,"minItems":2,"maxItems":2}},"required":["media"],"additionalProperties":false}]}},"required":["id","duration"],"additionalProperties":false}},"duration":{"type":"number","exclusiveMinimum":0,"maximum":3600},"transition":{"type":"object","properties":{"name":{"type":"string","enum":["fade","fade-black","fade-white","push-left","push-right","push-up","push-down","cover-left","cover-right","cover-up","cover-down","uncover-left","uncover-right","uncover-up","uncover-down","wipe-left","wipe-right","wipe-up","wipe-down","whip-pan-left","whip-pan-right","whip-pan-up","whip-pan-down","cube-left","cube-right","cube-up","cube-down","flip-left","flip-right","flip-up","flip-down","split-h","split-v","barn-door-h","barn-door-v","iris-in","iris-out","clock-wipe","radial-wipe","venetian-blinds","checker-wipe","zoom-in","zoom-out","spin","page-peel","BookFlip","Bounce","ButterflyWaveScrawler","CrossZoom","DoomScreenTransition","DreamyZoom","FilmBurn","GlitchDisplace","GlitchMemories","GridFlip","InvertedPageCurl","Mosaic","StereoViewer","Swirl","WaterDrop","ZoomInCircles","cube","displacement","doorway","flyeye","hexagonalize","kaleidoscope","luma","luminance_melt","morph","perlin","pixelize","ripple","swap"]},"duration":{"type":"number","exclusiveMinimum":0,"maximum":3600},"params":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"number"},{"type":"boolean"},{"type":"array","items":{"type":"number"}}]}}},"required":["name","duration"],"additionalProperties":false}},"required":["id","code"],"additionalProperties":false}},"required":["op","scene"],"additionalProperties":false,"description":"기존 씬 전체를 교체합니다. 로컬 파일은 프로그램이 읽어 전송하고 LLM이 코드 전체를 재출력하지 않습니다."},{"type":"object","properties":{"op":{"type":"string","const":"remove"},"sceneId":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,63}$"}},"required":["op","sceneId"],"additionalProperties":false},{"type":"object","properties":{"op":{"type":"string","const":"move"},"sceneId":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,63}$"},"afterId":{"anyOf":[{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,63}$"},{"type":"null"}]}},"required":["op","sceneId","afterId"],"additionalProperties":false},{"type":"object","properties":{"op":{"type":"string","const":"settings"},"meta":{"type":"object","properties":{"width":{"type":"integer","minimum":16,"maximum":3840,"multipleOf":2},"height":{"type":"integer","minimum":16,"maximum":3840,"multipleOf":2},"fps":{"anyOf":[{"type":"number","const":10},{"type":"number","const":15},{"type":"number","const":24},{"type":"number","const":25},{"type":"number","const":30},{"type":"number","const":50},{"type":"number","const":60}]}},"required":["width","height","fps"],"additionalProperties":false},"theme":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}},"required":["op"],"additionalProperties":false}]}}},"required":["projectId","requestId","expectedSeq","changes"],"additionalProperties":false,"$defs":{"__schema0":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"$ref":"#/$defs/__schema0"}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}]}}}
```

결과 JSON Schema:

```json
{"type":"object","properties":{"projectId":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$","description":"프로젝트 ID(UUID)"},"seq":{"type":"integer","minimum":0,"maximum":9007199254740991},"version":{"type":"integer","minimum":0,"maximum":9007199254740991},"changedSceneIds":{"type":"array","items":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,63}$"}},"storedFiles":{"type":"array","items":{"type":"string"}}},"required":["projectId","seq","version","changedSceneIds","storedFiles"],"additionalProperties":false}
```

### save_version — 명시적 버전 저장

`POST /api/v1/projects/{projectId}/versions`

사용자의 명시적 저장이나 벌크 생산 완료 때 현재 작업 상태를 불변 버전으로 고정합니다. 본문·코드를 보내지 않습니다. expectedSeq와 expectedVersion 모두 읽은 기준과 일치해야 합니다. 수정 호출마다 자동 저장하지 마세요. requestId가 같은 재시도는 기존 결과를 반환합니다. 사용자에게 결과를 보여 줄 주소는 /{projectId}/edit이며 ?scene={sceneId}&t={씬 로컬 초}로 수정 지점을 가리킵니다. 이 도구는 디자인 검수나 추출을 수행하지 않습니다.

| 입력 | 위치 | 타입 | 필수 | 설명 |
|---|---|---|---|---|
| `projectId` | path | `string` | 예 | 프로젝트 ID(UUID) |
| `requestId` | body | `string` | 예 | 요청의 고유 ID. 같은 요청을 재전송할 때는 같은 값을 사용합니다. |
| `expectedSeq` | body | `integer` | 예 |  |
| `expectedVersion` | body | `integer` | 예 |  |
| `summary` | body | `string` | 예 |  |

입력 JSON Schema:

```json
{"type":"object","properties":{"projectId":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$","description":"프로젝트 ID(UUID)"},"requestId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"요청의 고유 ID. 같은 요청을 재전송할 때는 같은 값을 사용합니다."},"expectedSeq":{"type":"integer","minimum":0,"maximum":9007199254740991},"expectedVersion":{"type":"integer","minimum":0,"maximum":9007199254740991},"summary":{"type":"string","minLength":1,"maxLength":500}},"required":["projectId","requestId","expectedSeq","expectedVersion","summary"],"additionalProperties":false}
```

결과 JSON Schema:

```json
{"type":"object","properties":{"projectId":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$","description":"프로젝트 ID(UUID)"},"version":{"type":"integer","minimum":0,"maximum":9007199254740991},"seq":{"type":"integer","minimum":0,"maximum":9007199254740991},"summary":{"type":"string"}},"required":["projectId","version","seq","summary"],"additionalProperties":false}
```

### list_assets — 에셋 목록·검색

`GET /api/v1/assets` · 읽기 전용

워크스페이스의 이미지·영상 에셋을 이름·설명으로 검색합니다. 생성·가져오기 진행 상태도 조회합니다. 프로젝트에 실제로 사용하는 재료는 씬 params의 명시적 참조로 결정합니다.

| 입력 | 위치 | 타입 | 필수 | 설명 |
|---|---|---|---|---|
| `workspaceId` | query | `integer` |  | 워크스페이스 번호 |
| `query` | query | `string` |  | 기본 "" |
| `kind` | query | `"image" \| "video"` |  |  |
| `limit` | query | `integer` |  | 기본 30 |
| `offset` | query | `integer` |  | 기본 0 |

입력 JSON Schema:

```json
{"type":"object","properties":{"workspaceId":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"워크스페이스 번호"},"query":{"default":"","type":"string","maxLength":200},"kind":{"type":"string","enum":["image","video"]},"limit":{"default":30,"type":"integer","minimum":1,"maximum":100},"offset":{"default":0,"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}
```

결과 JSON Schema:

```json
{"type":"object","properties":{"assets":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"에셋 번호"},"workspaceId":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"워크스페이스 번호"},"name":{"type":"string"},"description":{"type":"string"},"kind":{"type":"string","enum":["image","video"]},"status":{"type":"string","enum":["queued","running","ready","failed"]},"file":{"anyOf":[{"type":"object","properties":{"sha256":{"type":"string","pattern":"^[a-f0-9]{64}$"},"mime":{"type":"string","enum":["image/png","video/mp4"]},"size":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"width":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"height":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"duration":{"type":"number","exclusiveMinimum":0}},"required":["sha256","mime","size","width","height"],"additionalProperties":false},{"type":"null"}]},"reference":{"anyOf":[{"type":"object","properties":{"$asset":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"에셋 번호"},"sha256":{"type":"string","pattern":"^[a-f0-9]{64}$"}},"required":["$asset","sha256"],"additionalProperties":false},{"type":"null"}]},"source":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}},"error":{"type":["string","null"]},"createdAt":{"type":"string"},"finishedAt":{"type":["string","null"]},"fileUrl":{"type":["string","null"]}},"required":["id","workspaceId","name","description","kind","status","file","reference","source","error","createdAt","finishedAt","fileUrl"],"additionalProperties":false}},"total":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["assets","total"],"additionalProperties":false,"$defs":{"__schema0":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"$ref":"#/$defs/__schema0"}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}]}}}
```

### get_asset — 에셋·생성 결과 조회

`GET /api/v1/assets/{assetId}` · 읽기 전용

에셋 ID로 진행 상태와 결과를 조회합니다. ready의 reference를 씬 params에 그대로 사용합니다. fileUrl은 인증이 필요한 파일 주소이며 코드에 넣지 않습니다. failed는 원래 가져오기·생성 입력과 requestId에 retry=true를 더해 재개할 수 있습니다. 공급자 접수 여부가 불명확하면 자동 재생성하지 않습니다.

| 입력 | 위치 | 타입 | 필수 | 설명 |
|---|---|---|---|---|
| `assetId` | path | `integer` | 예 | 에셋 번호 |

입력 JSON Schema:

```json
{"type":"object","properties":{"assetId":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"에셋 번호"}},"required":["assetId"],"additionalProperties":false}
```

결과 JSON Schema:

```json
{"type":"object","properties":{"asset":{"type":"object","properties":{"id":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"에셋 번호"},"workspaceId":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"워크스페이스 번호"},"name":{"type":"string"},"description":{"type":"string"},"kind":{"type":"string","enum":["image","video"]},"status":{"type":"string","enum":["queued","running","ready","failed"]},"file":{"anyOf":[{"type":"object","properties":{"sha256":{"type":"string","pattern":"^[a-f0-9]{64}$"},"mime":{"type":"string","enum":["image/png","video/mp4"]},"size":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"width":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"height":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"duration":{"type":"number","exclusiveMinimum":0}},"required":["sha256","mime","size","width","height"],"additionalProperties":false},{"type":"null"}]},"reference":{"anyOf":[{"type":"object","properties":{"$asset":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"에셋 번호"},"sha256":{"type":"string","pattern":"^[a-f0-9]{64}$"}},"required":["$asset","sha256"],"additionalProperties":false},{"type":"null"}]},"source":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}},"error":{"type":["string","null"]},"createdAt":{"type":"string"},"finishedAt":{"type":["string","null"]},"fileUrl":{"type":["string","null"]}},"required":["id","workspaceId","name","description","kind","status","file","reference","source","error","createdAt","finishedAt","fileUrl"],"additionalProperties":false}},"required":["asset"],"additionalProperties":false,"$defs":{"__schema0":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"$ref":"#/$defs/__schema0"}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}]}}}
```

### import_asset — 이미지·영상 가져오기

`POST /api/v1/assets/imports`

공개 파일 URL 또는 base64 파일(최대 8MiB)을 워크스페이스 에셋으로 보존합니다. 둘 중 하나만 보냅니다. 내려받기·실측·정규화는 별도 워커에서 수행하며 접수된 에셋 ID로 get_asset을 조회합니다. 같은 requestId 재전송은 같은 에셋을 반환합니다.

| 입력 | 위치 | 타입 | 필수 | 설명 |
|---|---|---|---|---|
| `workspaceId` | body | `integer` |  | 워크스페이스 번호 |
| `requestId` | body | `string` | 예 | 요청의 고유 ID. 같은 요청을 재전송할 때는 같은 값을 사용합니다. |
| `retry` | body | `boolean` |  | 실패한 같은 요청을 재개합니다. 나머지 입력·requestId를 그대로 사용하세요.. 기본 false |
| `name` | body | `string` | 예 |  |
| `description` | body | `string` |  | 기본 "" |
| `kind` | body | `"image" \| "video"` | 예 |  |
| `url` | body | `string` |  |  |
| `base64` | body | `string` |  |  |

입력 JSON Schema:

```json
{"type":"object","properties":{"workspaceId":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"워크스페이스 번호"},"requestId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"요청의 고유 ID. 같은 요청을 재전송할 때는 같은 값을 사용합니다."},"retry":{"default":false,"description":"실패한 같은 요청을 재개합니다. 나머지 입력·requestId를 그대로 사용하세요.","type":"boolean"},"name":{"type":"string","minLength":1,"maxLength":200},"description":{"default":"","type":"string","maxLength":2000},"kind":{"type":"string","enum":["image","video"]},"url":{"type":"string","maxLength":4096,"format":"uri"},"base64":{"type":"string","minLength":4,"maxLength":11184812}},"required":["requestId","name","kind"],"additionalProperties":false}
```

결과 JSON Schema:

```json
{"type":"object","properties":{"asset":{"type":"object","properties":{"id":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"에셋 번호"},"workspaceId":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"워크스페이스 번호"},"name":{"type":"string"},"description":{"type":"string"},"kind":{"type":"string","enum":["image","video"]},"status":{"type":"string","enum":["queued","running","ready","failed"]},"file":{"anyOf":[{"type":"object","properties":{"sha256":{"type":"string","pattern":"^[a-f0-9]{64}$"},"mime":{"type":"string","enum":["image/png","video/mp4"]},"size":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"width":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"height":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"duration":{"type":"number","exclusiveMinimum":0}},"required":["sha256","mime","size","width","height"],"additionalProperties":false},{"type":"null"}]},"reference":{"anyOf":[{"type":"object","properties":{"$asset":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"에셋 번호"},"sha256":{"type":"string","pattern":"^[a-f0-9]{64}$"}},"required":["$asset","sha256"],"additionalProperties":false},{"type":"null"}]},"source":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}},"error":{"type":["string","null"]},"createdAt":{"type":"string"},"finishedAt":{"type":["string","null"]},"fileUrl":{"type":["string","null"]}},"required":["id","workspaceId","name","description","kind","status","file","reference","source","error","createdAt","finishedAt","fileUrl"],"additionalProperties":false}},"required":["asset"],"additionalProperties":false,"$defs":{"__schema0":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"$ref":"#/$defs/__schema0"}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}]}}}
```

### generate_asset — 이미지·영상 생성

`POST /api/v1/assets/generations`

저가 테스트 설정으로 이미지·영상을 생성하고 파일로 보존합니다. prompt에 이번 내용과 계승할 콘셉트를 함께 적으세요. 영어 프롬프트를 권장하며 서버는 번역·스타일 추론을 하지 않습니다. 에셋 ID를 즉시 반환하고 get_asset으로 상태를 조회합니다. 응답 유실에는 같은 requestId로 재시도하세요. 참조 이미지 조건부 생성·캐릭터 일관성 보증은 아직 지원하지 않습니다.

| 입력 | 위치 | 타입 | 필수 | 설명 |
|---|---|---|---|---|
| `workspaceId` | body | `integer` |  | 워크스페이스 번호 |
| `requestId` | body | `string` | 예 | 요청의 고유 ID. 같은 요청을 재전송할 때는 같은 값을 사용합니다. |
| `retry` | body | `boolean` |  | 실패한 같은 요청을 재개합니다. 이미 접수된 공급자 작업은 같은 ID로 조회하고 새로 생성하지 않습니다.. 기본 false |
| `name` | body | `string` | 예 |  |
| `description` | body | `string` |  | 기본 "" |
| `kind` | body | `"image" \| "video"` | 예 |  |
| `prompt` | body | `string` | 예 |  |
| `aspectRatio` | body | `"16:9" \| "9:16" \| "1:1"` |  | 기본 "16:9" |
| `duration` | body | `integer` |  |  |
| `seed` | body | `integer` |  |  |

입력 JSON Schema:

```json
{"type":"object","properties":{"workspaceId":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"워크스페이스 번호"},"requestId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"요청의 고유 ID. 같은 요청을 재전송할 때는 같은 값을 사용합니다."},"retry":{"default":false,"description":"실패한 같은 요청을 재개합니다. 이미 접수된 공급자 작업은 같은 ID로 조회하고 새로 생성하지 않습니다.","type":"boolean"},"name":{"type":"string","minLength":1,"maxLength":200},"description":{"default":"","type":"string","maxLength":2000},"kind":{"type":"string","enum":["image","video"]},"prompt":{"type":"string","minLength":1,"maxLength":12000},"aspectRatio":{"default":"16:9","type":"string","enum":["16:9","9:16","1:1"]},"duration":{"type":"integer","minimum":2,"maximum":5},"seed":{"type":"integer","minimum":0,"maximum":2147483647}},"required":["requestId","name","kind","prompt"],"additionalProperties":false}
```

결과 JSON Schema:

```json
{"type":"object","properties":{"asset":{"type":"object","properties":{"id":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"에셋 번호"},"workspaceId":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"워크스페이스 번호"},"name":{"type":"string"},"description":{"type":"string"},"kind":{"type":"string","enum":["image","video"]},"status":{"type":"string","enum":["queued","running","ready","failed"]},"file":{"anyOf":[{"type":"object","properties":{"sha256":{"type":"string","pattern":"^[a-f0-9]{64}$"},"mime":{"type":"string","enum":["image/png","video/mp4"]},"size":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"width":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"height":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"duration":{"type":"number","exclusiveMinimum":0}},"required":["sha256","mime","size","width","height"],"additionalProperties":false},{"type":"null"}]},"reference":{"anyOf":[{"type":"object","properties":{"$asset":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"에셋 번호"},"sha256":{"type":"string","pattern":"^[a-f0-9]{64}$"}},"required":["$asset","sha256"],"additionalProperties":false},{"type":"null"}]},"source":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}},"error":{"type":["string","null"]},"createdAt":{"type":"string"},"finishedAt":{"type":["string","null"]},"fileUrl":{"type":["string","null"]}},"required":["id","workspaceId","name","description","kind","status","file","reference","source","error","createdAt","finishedAt","fileUrl"],"additionalProperties":false}},"required":["asset"],"additionalProperties":false,"$defs":{"__schema0":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"$ref":"#/$defs/__schema0"}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}]}}}
```

### list_voices — ElevenLabs 목소리 조회

`GET /api/v1/voices` · 읽기 전용

ElevenLabs에서 실제 사용 가능한 목소리와 모델을 조회합니다. 목소리 ID를 선택해 generate_speech에 사용합니다.

| 입력 | 위치 | 타입 | 필수 | 설명 |
|---|---|---|---|---|
| `workspaceId` | query | `integer` |  | 워크스페이스 번호 |
| `query` | query | `string` |  | 기본 "" |
| `limit` | query | `integer` |  | 기본 30 |
| `pageToken` | query | `string` |  |  |

입력 JSON Schema:

```json
{"type":"object","properties":{"workspaceId":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"워크스페이스 번호"},"query":{"default":"","type":"string","maxLength":200},"limit":{"default":30,"type":"integer","minimum":1,"maximum":100},"pageToken":{"type":"string","maxLength":1000}},"additionalProperties":false}
```

결과 JSON Schema:

```json
{"type":"object","properties":{"voices":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"labels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"previewUrl":{"type":["string","null"]}},"required":["id","name","labels","previewUrl"],"additionalProperties":false}},"nextPageToken":{"type":["string","null"]},"models":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"],"additionalProperties":false}}},"required":["voices","nextPageToken","models"],"additionalProperties":false}
```

### generate_speech — 나레이션 생성

`POST /api/v1/speech`

ElevenLabs V4로 원고를 읽고 내부 파생 음성으로 보존합니다. 사용자 에셋 목록에는 넣지 않습니다. get_job으로 완료를 확인한 뒤 result.reference를 컷 speech에, 같은 원고를 text에 둡니다. duration.media에 음성 작업 ID를 지정하면 실측 길이로 컷을 계산합니다.

| 입력 | 위치 | 타입 | 필수 | 설명 |
|---|---|---|---|---|
| `workspaceId` | body | `integer` |  | 워크스페이스 번호 |
| `requestId` | body | `string` | 예 | 요청의 고유 ID. 같은 요청을 재전송할 때는 같은 값을 사용합니다. |
| `retry` | body | `boolean` |  | 기본 false |
| `text` | body | `string` | 예 |  |
| `voiceId` | body | `string` | 예 |  |
| `model` | body | `string` |  | 기본 "eleven_v4" |
| `seed` | body | `integer` |  |  |
| `languageCode` | body | `string` |  |  |

입력 JSON Schema:

```json
{"type":"object","properties":{"workspaceId":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"워크스페이스 번호"},"requestId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"요청의 고유 ID. 같은 요청을 재전송할 때는 같은 값을 사용합니다."},"retry":{"default":false,"type":"boolean"},"text":{"type":"string","minLength":1,"maxLength":10000},"voiceId":{"type":"string","pattern":"^[a-zA-Z0-9_-]{1,100}$"},"model":{"default":"eleven_v4","type":"string","pattern":"^[a-zA-Z0-9_-]{1,100}$"},"seed":{"type":"integer","minimum":0,"maximum":4294967295},"languageCode":{"type":"string","pattern":"^[a-z]{2}$"}},"required":["requestId","text","voiceId"],"additionalProperties":false}
```

결과 JSON Schema:

```json
{"type":"object","properties":{"job":{"type":"object","properties":{"id":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"작업 번호"},"kind":{"type":"string","enum":["speech","render"]},"workspaceId":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"워크스페이스 번호"},"projectId":{"anyOf":[{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$","description":"프로젝트 ID(UUID)"},{"type":"null"}]},"requestId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}},"status":{"type":"string","enum":["queued","running","succeeded","failed"]},"progress":{"type":"number","minimum":0,"maximum":1},"error":{"type":["string","null"]},"result":{"anyOf":[{"anyOf":[{"type":"object","properties":{"reference":{"type":"object","properties":{"id":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"음성 작업 번호"},"sha256":{"type":"string","pattern":"^[a-f0-9]{64}$"}},"required":["id","sha256"],"additionalProperties":false},"file":{"type":"object","properties":{"sha256":{"type":"string","pattern":"^[a-f0-9]{64}$"},"size":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mime":{"type":"string","const":"audio/wav"},"duration":{"type":"number","exclusiveMinimum":0},"sampleRate":{"type":"number","const":48000}},"required":["sha256","size","mime","duration","sampleRate"],"additionalProperties":false},"text":{"type":"string"},"voiceId":{"type":"string"},"model":{"type":"string"},"source":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema1"}}},"required":["reference","file","text","voiceId","model","source"],"additionalProperties":false},{"type":"object","properties":{"projectId":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$","description":"프로젝트 ID(UUID)"},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"sha256":{"type":"string","pattern":"^[a-f0-9]{64}$"},"size":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mime":{"type":"string","const":"video/mp4"},"duration":{"type":"number","exclusiveMinimum":0},"frames":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"fps":{"type":"number","exclusiveMinimum":0},"audioTracks":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["projectId","version","sha256","size","mime","duration","frames","fps","audioTracks"],"additionalProperties":false}]},{"type":"null"}]},"fileUrl":{"type":["string","null"]},"createdAt":{"type":"string"},"finishedAt":{"type":["string","null"]}},"required":["id","kind","workspaceId","projectId","requestId","input","status","progress","error","result","fileUrl","createdAt","finishedAt"],"additionalProperties":false}},"required":["job"],"additionalProperties":false,"$defs":{"__schema0":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"$ref":"#/$defs/__schema0"}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}]},"__schema1":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"$ref":"#/$defs/__schema1"}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema1"}}]}}}
```

### get_job — 음성·추출 작업 조회

`GET /api/v1/jobs/{jobId}` · 읽기 전용

음성·추출의 진행·실패·불변 결과와 인증된 파일 주소를 조회합니다. 응답 유실 시 새 생성보다 기존 작업 조회를 먼저 사용하세요.

| 입력 | 위치 | 타입 | 필수 | 설명 |
|---|---|---|---|---|
| `jobId` | path | `integer` | 예 | 작업 번호 |

입력 JSON Schema:

```json
{"type":"object","properties":{"jobId":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"작업 번호"}},"required":["jobId"],"additionalProperties":false}
```

결과 JSON Schema:

```json
{"type":"object","properties":{"job":{"type":"object","properties":{"id":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"작업 번호"},"kind":{"type":"string","enum":["speech","render"]},"workspaceId":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"워크스페이스 번호"},"projectId":{"anyOf":[{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$","description":"프로젝트 ID(UUID)"},{"type":"null"}]},"requestId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}},"status":{"type":"string","enum":["queued","running","succeeded","failed"]},"progress":{"type":"number","minimum":0,"maximum":1},"error":{"type":["string","null"]},"result":{"anyOf":[{"anyOf":[{"type":"object","properties":{"reference":{"type":"object","properties":{"id":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"음성 작업 번호"},"sha256":{"type":"string","pattern":"^[a-f0-9]{64}$"}},"required":["id","sha256"],"additionalProperties":false},"file":{"type":"object","properties":{"sha256":{"type":"string","pattern":"^[a-f0-9]{64}$"},"size":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mime":{"type":"string","const":"audio/wav"},"duration":{"type":"number","exclusiveMinimum":0},"sampleRate":{"type":"number","const":48000}},"required":["sha256","size","mime","duration","sampleRate"],"additionalProperties":false},"text":{"type":"string"},"voiceId":{"type":"string"},"model":{"type":"string"},"source":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema1"}}},"required":["reference","file","text","voiceId","model","source"],"additionalProperties":false},{"type":"object","properties":{"projectId":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$","description":"프로젝트 ID(UUID)"},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"sha256":{"type":"string","pattern":"^[a-f0-9]{64}$"},"size":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mime":{"type":"string","const":"video/mp4"},"duration":{"type":"number","exclusiveMinimum":0},"frames":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"fps":{"type":"number","exclusiveMinimum":0},"audioTracks":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["projectId","version","sha256","size","mime","duration","frames","fps","audioTracks"],"additionalProperties":false}]},{"type":"null"}]},"fileUrl":{"type":["string","null"]},"createdAt":{"type":"string"},"finishedAt":{"type":["string","null"]}},"required":["id","kind","workspaceId","projectId","requestId","input","status","progress","error","result","fileUrl","createdAt","finishedAt"],"additionalProperties":false}},"required":["job"],"additionalProperties":false,"$defs":{"__schema0":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"$ref":"#/$defs/__schema0"}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}]},"__schema1":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"$ref":"#/$defs/__schema1"}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema1"}}]}}}
```

### render_version — 저장 버전 MP4 추출

`POST /api/v1/projects/{projectId}/renders`

명시적으로 저장한 버전을 Pixi 이미지·영상과 컷 나레이션을 합친 MP4로 추출합니다. 별도 워커가 실행하며 get_job으로 결과·파일 주소를 조회합니다. 프로젝트의 fps를 유지하고 같은 requestId 재전송은 같은 작업입니다.

| 입력 | 위치 | 타입 | 필수 | 설명 |
|---|---|---|---|---|
| `projectId` | path | `string` | 예 | 프로젝트 ID(UUID) |
| `version` | body | `integer` | 예 |  |
| `requestId` | body | `string` | 예 | 요청의 고유 ID. 같은 요청을 재전송할 때는 같은 값을 사용합니다. |
| `retry` | body | `boolean` |  | 기본 false |

입력 JSON Schema:

```json
{"type":"object","properties":{"projectId":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$","description":"프로젝트 ID(UUID)"},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"requestId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"요청의 고유 ID. 같은 요청을 재전송할 때는 같은 값을 사용합니다."},"retry":{"default":false,"type":"boolean"}},"required":["projectId","version","requestId"],"additionalProperties":false}
```

결과 JSON Schema:

```json
{"type":"object","properties":{"job":{"type":"object","properties":{"id":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"작업 번호"},"kind":{"type":"string","enum":["speech","render"]},"workspaceId":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"워크스페이스 번호"},"projectId":{"anyOf":[{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$","description":"프로젝트 ID(UUID)"},{"type":"null"}]},"requestId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}},"status":{"type":"string","enum":["queued","running","succeeded","failed"]},"progress":{"type":"number","minimum":0,"maximum":1},"error":{"type":["string","null"]},"result":{"anyOf":[{"anyOf":[{"type":"object","properties":{"reference":{"type":"object","properties":{"id":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"음성 작업 번호"},"sha256":{"type":"string","pattern":"^[a-f0-9]{64}$"}},"required":["id","sha256"],"additionalProperties":false},"file":{"type":"object","properties":{"sha256":{"type":"string","pattern":"^[a-f0-9]{64}$"},"size":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mime":{"type":"string","const":"audio/wav"},"duration":{"type":"number","exclusiveMinimum":0},"sampleRate":{"type":"number","const":48000}},"required":["sha256","size","mime","duration","sampleRate"],"additionalProperties":false},"text":{"type":"string"},"voiceId":{"type":"string"},"model":{"type":"string"},"source":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema1"}}},"required":["reference","file","text","voiceId","model","source"],"additionalProperties":false},{"type":"object","properties":{"projectId":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$","description":"프로젝트 ID(UUID)"},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"sha256":{"type":"string","pattern":"^[a-f0-9]{64}$"},"size":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mime":{"type":"string","const":"video/mp4"},"duration":{"type":"number","exclusiveMinimum":0},"frames":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"fps":{"type":"number","exclusiveMinimum":0},"audioTracks":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["projectId","version","sha256","size","mime","duration","frames","fps","audioTracks"],"additionalProperties":false}]},{"type":"null"}]},"fileUrl":{"type":["string","null"]},"createdAt":{"type":"string"},"finishedAt":{"type":["string","null"]}},"required":["id","kind","workspaceId","projectId","requestId","input","status","progress","error","result","fileUrl","createdAt","finishedAt"],"additionalProperties":false}},"required":["job"],"additionalProperties":false,"$defs":{"__schema0":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"$ref":"#/$defs/__schema0"}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}]},"__schema1":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"$ref":"#/$defs/__schema1"}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema1"}}]}}}
```

### list_renders — 프로젝트 추출 목록

`GET /api/v1/projects/{projectId}/renders` · 읽기 전용

프로젝트의 추출 작업·상태·결과를 조회합니다.

| 입력 | 위치 | 타입 | 필수 | 설명 |
|---|---|---|---|---|
| `projectId` | path | `string` | 예 | 프로젝트 ID(UUID) |
| `limit` | query | `integer` |  | 기본 30 |
| `offset` | query | `integer` |  | 기본 0 |

입력 JSON Schema:

```json
{"type":"object","properties":{"projectId":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$","description":"프로젝트 ID(UUID)"},"limit":{"default":30,"type":"integer","minimum":1,"maximum":100},"offset":{"default":0,"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["projectId"],"additionalProperties":false}
```

결과 JSON Schema:

```json
{"type":"object","properties":{"jobs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"작업 번호"},"kind":{"type":"string","enum":["speech","render"]},"workspaceId":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"워크스페이스 번호"},"projectId":{"anyOf":[{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$","description":"프로젝트 ID(UUID)"},{"type":"null"}]},"requestId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}},"status":{"type":"string","enum":["queued","running","succeeded","failed"]},"progress":{"type":"number","minimum":0,"maximum":1},"error":{"type":["string","null"]},"result":{"anyOf":[{"anyOf":[{"type":"object","properties":{"reference":{"type":"object","properties":{"id":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"음성 작업 번호"},"sha256":{"type":"string","pattern":"^[a-f0-9]{64}$"}},"required":["id","sha256"],"additionalProperties":false},"file":{"type":"object","properties":{"sha256":{"type":"string","pattern":"^[a-f0-9]{64}$"},"size":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mime":{"type":"string","const":"audio/wav"},"duration":{"type":"number","exclusiveMinimum":0},"sampleRate":{"type":"number","const":48000}},"required":["sha256","size","mime","duration","sampleRate"],"additionalProperties":false},"text":{"type":"string"},"voiceId":{"type":"string"},"model":{"type":"string"},"source":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema1"}}},"required":["reference","file","text","voiceId","model","source"],"additionalProperties":false},{"type":"object","properties":{"projectId":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$","description":"프로젝트 ID(UUID)"},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"sha256":{"type":"string","pattern":"^[a-f0-9]{64}$"},"size":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mime":{"type":"string","const":"video/mp4"},"duration":{"type":"number","exclusiveMinimum":0},"frames":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"fps":{"type":"number","exclusiveMinimum":0},"audioTracks":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["projectId","version","sha256","size","mime","duration","frames","fps","audioTracks"],"additionalProperties":false}]},{"type":"null"}]},"fileUrl":{"type":["string","null"]},"createdAt":{"type":"string"},"finishedAt":{"type":["string","null"]}},"required":["id","kind","workspaceId","projectId","requestId","input","status","progress","error","result","fileUrl","createdAt","finishedAt"],"additionalProperties":false}},"total":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["jobs","total"],"additionalProperties":false,"$defs":{"__schema0":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"$ref":"#/$defs/__schema0"}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}]},"__schema1":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"$ref":"#/$defs/__schema1"}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema1"}}]}}}
```
