# 파이프라인

> REST로 스토리 → 동영상 파이프라인을 시작하고, 단계를 추적하고, 게이트를 승인하고, 감독과 채팅하고, 완료된 파이프라인을 한 단계부터 브랜치하세요.

Source: https://nodaro.ai/ko/docs/developers/api/pipelines

**파이프라인 API**는 코드에서 스토리 → 동영상을 실행합니다. 한 줄짜리 스토리를 보내면, 파이프라인 엔진이 스크립트를 쓰고, 출연진, 소품, 장소를 만들고, 샷을 계획합니다. 그런 다음 키프레임을 렌더링하고, 사운드와 함께 애니메이션을 만들고, 영화를 합칩니다. 각 단계는 내 승인을 기다리거나, 스스로 실행되거나, 감독과의 채팅을 열 수 있습니다.

파이프라인은 Nodaro Cloud에서만 실행됩니다. Community Edition과 Business 에디션은 `403 edition_required`로 응답합니다. 셀프 호스팅 설치에서는 [대본 생성](https://nodaro.ai/docs/nodes/video/generate-script), [이미지 생성](https://nodaro.ai/docs/nodes/image/generate-image), [동영상 생성](https://nodaro.ai/docs/nodes/video/generate-video) 같은 노드로 같은 단계를 워크플로로 구성하세요. 라우트는 Bearer 토큰을 받으며, OAuth 앱 토큰에는 아래에 나열된 스코프가 필요합니다. [인증](https://nodaro.ai/docs/developers/api/authentication)을 참조하세요.

## 엔드포인트
| 메서드 | 경로 | 스코프 | 하는 일 |
| --- | --- | --- | --- |
| `POST` | `/v1/pipelines` | `pipelines:execute` | 파이프라인을 만들고 시작합니다. |
| `GET` | `/v1/pipelines` | `pipelines:read` | 내 파이프라인 목록을 최신순으로 가져옵니다. |
| `GET` | `/v1/pipelines/:id` | `pipelines:read` | 상태, 현재 단계, 크레딧을 가져옵니다. |
| `GET` | `/v1/pipelines/:id/events` | `pipelines:read` | 파이프라인 이벤트를 스트리밍합니다(서버 전송 이벤트). |
| `GET` | `/v1/pipelines/:id/stages/:stage` | `pipelines:read` | 단계 하나의 상태, 출력, 평가 피드백을 가져옵니다. |
| `GET` | `/v1/pipelines/:id/pending-approvals` | `pipelines:read` | 승인을 기다리는 단계 목록을 가져옵니다. |
| `GET` | `/v1/pipelines/:id/timeline` | `pipelines:read` | 조립된 영화를 가져옵니다. 장면, 길이, 오디오가 들어 있습니다. |
| `GET` | `/v1/pipelines/:id/entities?type=` | `pipelines:read` | 파이프라인의 캐릭터, 오브젝트, 장소 또는 장면 목록을 가져옵니다. |
| `POST` | `/v1/pipelines/:id/stages/:stage/approve` | `pipelines:approve` | 단계를 승인합니다. 수정을 함께 보낼 수 있습니다. |
| `POST` | `/v1/pipelines/:id/stages/:stage/reject` | `pipelines:approve` | 피드백과 함께 스크립트를 거부해 다시 쓰게 합니다. |
| `POST` | `/v1/pipelines/:id/sub-gates/:gate/approve` | `pipelines:approve` | 애니메이션 단계 안의 체크포인트를 승인합니다. |
| `POST` | `/v1/pipelines/:id/sub-gates/:gate/reject` | `pipelines:approve` | 해당 체크포인트를 거부하고 파이프라인을 중지합니다. |
| `POST` | `/v1/pipelines/:id/entities/:sceneId/helpers/accept_match_cut_break` | `pipelines:approve` | 장면 이미지 단계에서 매치 컷 불일치 하나를 수락합니다. |
| `POST` | `/v1/pipelines/:id/stages/:stage/chat` | `pipelines:approve` | 감독에게 메시지를 보냅니다(가이드 모드). |
| `GET` | `/v1/pipelines/:id/stages/:stage/chat` | `pipelines:read` | 단계의 채팅을 읽습니다. |
| `POST` | `/v1/pipelines/:id/stages/:stage/chat/turns/:turnId/apply` | `pipelines:approve` | 감독이 제안한 변경을 적용합니다. |
| `POST` | `/v1/pipelines/:id/branch` | `pipelines:execute` | 완료된 파이프라인을 한 단계부터 새 파이프라인으로 다시 실행합니다. |
| `POST` | `/v1/pipelines/:id/fork` | `pipelines:execute` | 파이프라인을 중지하고 캔버스를 일반 노드로 남깁니다. |
| `POST` | `/v1/pipelines/:id/cancel` | `pipelines:execute` | 실행 중인 파이프라인을 취소하고 사용하지 않은 크레딧을 환불합니다. |

## 단계와 모드
파이프라인은 8개 단계를 순서대로 거칩니다. 경로의 `:stage`는 다음 이름 중 하나입니다.

| 단계 | 만드는 것 |
| --- | --- |
| `script` | 스토리 계획입니다. 제목, 장면, 출연진, 장소, 소품이 담깁니다. |
| `characters` | 출연진의 역할마다 캐릭터 하나입니다. |
| `objects` | 스토리에 필요한 소품입니다. |
| `locations` | 스토리가 거쳐 가는 장소입니다. |
| `shot_list` | 각 장면의 샷과 그 카메라, 연속성 선택입니다. |
| `scene_images` | 샷마다 키프레임 하나입니다. |
| `animate_audio_edit` | 대사, 내레이션, 음악, 편집이 들어간 애니메이션 샷입니다. |
| `post_merge` | 최종 병합된 영화입니다. |

생성할 때 고른 `mode`가 누가 파이프라인을 진행시킬지 정합니다.

| 모드 | 진행 방식 |
| --- | --- |
| `manual`(기본값) | 모든 단계가 `awaiting_approval`에서 멈춥니다. 승인, 수정 또는 거부하면 파이프라인이 계속 진행됩니다. |
| `auto` | 엔진이 모든 단계를 스스로 실행합니다. 자동 평가 AI가 스크립트, 출연진·장소·소품의 커버리지, 키프레임을 검사합니다. 차단 판정이 연속 3번 나오면 파이프라인이 실패하고 사용하지 않은 크레딧이 환불됩니다. [매치 컷 불일치](#match-cut-breaks-in-the-scene-images-stage)에서만 멈추며, 모든 불일치가 수락될 때까지 기다립니다. 그동안 상태는 `running`으로 유지되고, `pending-approvals`에 `scene_images` 단계가 나타납니다. |
| `guided` | `manual`과 같지만, `script`와 `post_merge` 단계에서 감독과 채팅할 수 있습니다. |

## 파이프라인 시작
`POST /v1/pipelines`는 파이프라인을 만들고, 크레딧을 예약하고, 시작합니다. `{ id }`와 함께 `201`로 응답합니다.

**curl**

```bash
curl -X POST https://app.nodaro.ai/v1/pipelines \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
"root_node_id": "0e6b2f7c-4a1d-4c8e-9b3f-5d7a2c1e8f4b",
"story_prompt": "A lighthouse keeper must restart the light before the storm hits.",
"format": "short_film",
"target_duration_seconds": 60,
"mode": "auto",
"output_resolution": "720p"
}'
```

**TypeScript SDK**

```ts

const client = createClient({
baseUrl: 'https://app.nodaro.ai',
auth: new StaticTokenAuth(process.env.NODARO_API_KEY!),
})

const { id } = await client.pipelines.create({
root_node_id: crypto.randomUUID(),
story_prompt: 'A lighthouse keeper must restart the light before the storm hits.',
format: 'short_film',
target_duration_seconds: 60,
mode: 'auto',
})
```

```json
{ "id": "c2f8a4e6-1b9d-4f3a-8c7e-6d2b9f1a5e3c" }
```

<TypeTable
type={{
story_prompt: { type: 'string', description: '영화의 내용으로, 최대 4,000자입니다.', required: true },
format: { type: "'trailer' | 'short_film' | 'music_video' | 'reel' | 'commercial'", description: '영화의 종류입니다. 허용되는 길이를 정합니다.', required: true },
target_duration_seconds: { type: 'integer', description: '영화 길이(초)로, 형식의 범위 안이어야 합니다. 최대 600입니다.', required: true },
root_node_id: { type: 'string', description: '파이프라인 루트의 ID입니다. 캔버스 노드에서 시작하지 않은 파이프라인이면 새 UUID 같은 고유 ID를 아무것이나 보내세요.', required: true },
mode: { type: "'manual' | 'auto' | 'guided'", description: '누가 파이프라인을 진행시킬지 정합니다.', default: 'manual' },
output_resolution: { type: "'480p' | '720p' | '1080p' | '4K'", description: '최종 영화의 해상도입니다.', default: '720p' },
language: { type: 'string', description: '스크립트와 대사의 언어입니다.', default: 'en' },
workflow_id: { type: 'string (uuid)', description: '영화를 구성할 내 워크플로입니다.' },
config: { type: 'object', description: '선택 스위치입니다. music_enabled, narration_enabled, lipsync_enabled이며, 각각 기본값은 true입니다.' },
max_cost_credits: { type: 'integer', description: '파이프라인이 쓸 수 있는 크레딧 상한입니다.' },
}}
/>

형식마다 허용되는 길이 범위가 있습니다.

| 형식 | 최소 | 최대 |
| --- | --- | --- |
| `reel` | 7초 | 90초 |
| `commercial` | 10초 | 90초 |
| `trailer` | 30초 | 180초 |
| `short_film` | 12초 | 600초 |
| `music_video` | 30초 | 600초 |

## 파이프라인 추적
`GET /v1/pipelines/:id`는 파이프라인의 상태를 반환합니다. 몇 초마다 폴링하거나, `GET /v1/pipelines/:id/events`를 열어 변경 사항을 서버 전송 이벤트로 받으세요.

```json
{
"id": "c2f8a4e6-1b9d-4f3a-8c7e-6d2b9f1a5e3c",
"status": "running",
"current_stage": "scene_images",
"mode": "auto",
"spent_credits": 412,
"reserved_credits": 1180,
"upfront_credit_estimate": 1650,
"failure_reason": null,
"current_progress_message": "Rendering keyframe 5 of 9",
"branched_from_pipeline_id": null,
"branched_from_stage": null
}
```

`status`는 `queued`, `running`, `awaiting_approval`, `completed`, `failed`, `cancelled` 또는 `forked`입니다. `failure_reason`은 `failed` 파이프라인의 원인을 설명합니다.

파이프라인이 `completed`이면 `GET /v1/pipelines/:id/timeline`이 렌더링하거나 편집기에 넘길 수 있는 데이터로 영화를 반환합니다.

```json
{
"fps": 24,
"width": 1280,
"height": 720,
"scenes": [
{ "compositeUrl": "https://cdn.nodaro.ai/pipelines/scene-1.mp4", "durationSeconds": 8.5 },
{ "compositeUrl": "https://cdn.nodaro.ai/pipelines/scene-2.mp4", "durationSeconds": 11 }
],
"musicUrl": "https://cdn.nodaro.ai/pipelines/score.mp3",
"narrationUrl": "https://cdn.nodaro.ai/pipelines/narration.mp3"
}
```

애니메이션 단계가 실행되는 동안 타임라인에는 `animateProgress`, 즉 `{ totalShots, shotsDone, percent }`도 들어 있습니다. 외부 편집기에서 컷을 마무리하려면 [타임라인 내보내기](https://nodaro.ai/docs/developers/api/studio-productions#export-a-timeline-to-an-editing-app)를 참조하세요.

**curl**

```bash
curl https://app.nodaro.ai/v1/pipelines/c2f8a4e6-1b9d-4f3a-8c7e-6d2b9f1a5e3c \
  -H "Authorization: Bearer $NODARO_API_KEY"

curl https://app.nodaro.ai/v1/pipelines/c2f8a4e6-1b9d-4f3a-8c7e-6d2b9f1a5e3c/timeline \
  -H "Authorization: Bearer $NODARO_API_KEY"
```

**TypeScript SDK**

```ts
const pipeline = await client.pipelines.get(id)
console.log(pipeline.status, pipeline.current_stage)

const timeline = await client.pipelines.getTimeline(id)
for (const scene of timeline.scenes) {
console.log(scene.compositeUrl, scene.durationSeconds)
}
```

## 단계 승인 또는 거부
`manual`과 `guided` 모드에서는 각 단계가 `awaiting_approval`에서 멈춥니다. `GET /v1/pipelines/:id/pending-approvals`는 기다리는 단계를 각각 `{ stage_name, output }` 형태로 나열합니다. 단계 전체는 `GET /v1/pipelines/:id/stages/:stage`로 읽으세요. 이 라우트는 `{ status, output, critic_feedback }`을 반환합니다.

- **승인.** `POST /v1/pipelines/:id/stages/:stage/approve`는 `{ ok: true }`를 반환하고 파이프라인이 다음으로 넘어갑니다. 먼저 단계의 출력을 바꾸려면 `{ edits }`를 보내세요. 승인 전에 출력에 적용되는 JSON Patch입니다.
- **거부.** 스크립트만 거부할 수 있습니다. `{ feedback }`과 함께 `POST /v1/pipelines/:id/stages/script/reject`를 보내면 `{ ok: true }`를 반환하고, 엔진이 내 메모를 반영해 스크립트를 다시 씁니다. 다른 단계는 `400 stage_not_implemented`로 응답합니다. 스크립트는 최대 두 번 거부할 수 있으며, 자동 스크립트 평가 AI가 이미 수정했다면 그보다 적게 거부할 수 있습니다.

**curl**

```bash
curl -X POST https://app.nodaro.ai/v1/pipelines/c2f8a4e6-1b9d-4f3a-8c7e-6d2b9f1a5e3c/stages/script/approve \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "edits": [{ "op": "replace", "path": "/title", "value": "The Last Light" }] }'

curl -X POST https://app.nodaro.ai/v1/pipelines/c2f8a4e6-1b9d-4f3a-8c7e-6d2b9f1a5e3c/stages/script/reject \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "feedback": "Make the story darker and more suspenseful." }'
```

**TypeScript SDK**

```ts
const approvals = await client.pipelines.pendingApprovals(id)
const { output } = await client.pipelines.getStage(id, 'script')

await client.pipelines.approveStage(id, 'script', [
{ op: 'replace', path: '/title', value: 'The Last Light' },
])
// or
await client.pipelines.rejectStage(id, 'script', 'Make the story darker and more suspenseful.')
```

### 애니메이션 단계 안의 체크포인트
`manual`과 `guided` 모드에서 `animate_audio_edit` 단계는 자체 체크포인트 두 곳에서 멈출 수 있습니다.

- **`dialogue_recheck`.** 단계가 대사의 실제 길이를 계획과 비교하고 장면 타이밍을 조정합니다. 어떤 장면이 목표의 10% 이내를 유지할 수 없으면 단계가 내 승인을 기다립니다.
- **`silent_cut_preview`.** 단계가 음악 없이 컷 미리보기를 조립하고, 음악을 생성해 그 비용을 지불하기 전에 내 승인을 기다립니다.

`POST /v1/pipelines/:id/sub-gates/:gate/approve`는 단계를 재개하고 `{ ok: true, gate, resumed_at }`을 반환합니다. `POST /v1/pipelines/:id/sub-gates/:gate/reject`는 단계와 파이프라인을 실패 처리하고 사용하지 않은 크레딧을 환불합니다. `auto` 모드에서는 단계가 멈추지 않고 계속됩니다.

### 장면 이미지 단계의 매치 컷 불일치
`scene_images` 단계는 세 번째 체크포인트인 `match_cut_break_pending`에서 멈출 수 있습니다. 두 샷 사이에 계획한 매치 컷이 맞지 않으면 `auto`를 포함한 모든 모드에서 이곳에서 멈춥니다. 단계 출력의 `match_cut_break_pending`에 해당 샷이 나열됩니다.

위의 하위 게이트 라우트로는 이를 해제할 수 없습니다. 이 라우트는 `400 invalid_sub_gate`로 응답하며, 오류 메시지에 해제할 수 있는 라우트가 나옵니다. 각 불일치는 `{ "shotId": "<shot id>" }`와 함께 `POST /v1/pipelines/:id/entities/:sceneId/helpers/accept_match_cut_break`로 수락하세요. `sceneId`는 샷이 속한 장면의 `id`이며, `metadata.scene_id`가 아닙니다. `GET /v1/pipelines/:id/entities?type=scene`은 장면을 나열하며, 각 장면의 샷은 `metadata.scene_node_data.shots`에 있습니다.

불일치를 수락하려면 `pipelines:approve` 스코프가 필요하며, 크레딧은 들지 않습니다. 호출은 아직 기다리는 불일치 수를 담은 `{ ok: true, pendingRemaining }`을 반환합니다. 마지막 불일치가 수락되면 단계가 계속됩니다. 수락한 불일치는 `scene_images` 단계를 다시 실행해도 수락된 상태로 남습니다. `shot_list` 단계를 다시 실행하면 장면이 새로 계획되고 모든 수락이 지워지므로, 다시 나타난 불일치는 다시 수락해야 합니다.

## 감독과 채팅
`guided` 모드에서는 승인을 기다리는 단계에 채팅이 있습니다. `{ message }`와 함께 `POST /v1/pipelines/:id/stages/:stage/chat`으로 최대 8,000자의 메시지를 보내세요. 감독은 한 문장으로 답하며 변경을 제안할 수 있습니다.

```json
{
"turnId": "f1a3c5e7-9b2d-4f6a-8c1e-3d5b7f9a2c4e",
"role": "assistant",
"content": "I moved the keeper's reason for staying into scene 2 and tightened the ending.",
"proposed_change": {
"change_type": "edit_artifact",
"json_patch": [{ "op": "replace", "path": "/scenes/1/description", "value": "The keeper finds his late wife's log and decides to stay." }]
}
}
```

`proposed_change`는 `null`이거나, `json_patch`가 있는 `edit_artifact`이거나, `from_stage`와 `reason`이 있는 `suggest_branch`입니다.

| 단계 | 감독이 제안할 수 있는 것 | 턴 |
| --- | --- | --- |
| `script` | 계획의 수정(제목, 장면, 출연진, 장소 또는 소품에 대한 JSON Patch), 또는 패치하기에는 변경이 너무 구조적일 때의 브랜치입니다. | 파이프라인당 20회 |
| `post_merge` | 최종 영화의 진단과 이전 단계에서의 브랜치입니다. 영화 자체는 패치할 수 없습니다. | 파이프라인당 8회 |

제안을 수락하려면 `POST /v1/pipelines/:id/stages/:stage/chat/turns/:turnId/apply`를 호출하세요. 이 라우트는 `{ applied: true, attemptId, newOutput }`을 반환하고 단계를 승인합니다. 변경이 계획을 깨뜨리면(예를 들어 장면에서 아직 쓰는 캐릭터를 제거하면) `{ applied: false, error }`를 반환하고, 감독이 무엇을 고쳐야 하는지 설명하는 턴을 추가합니다. 유효하지 않은 제안이거나 단계가 더 이상 기다리지 않으면 `409`로 응답합니다. `GET /v1/pipelines/:id/stages/:stage/chat`은 전체 대화인 `{ turns }`를 반환합니다.

가이드 파이프라인은 시작할 때 채팅용 크레딧을 추가로 예약합니다. 사용하지 않은 크레딧은 환불됩니다.

**curl**

```bash
curl -X POST https://app.nodaro.ai/v1/pipelines/c2f8a4e6-1b9d-4f3a-8c7e-6d2b9f1a5e3c/stages/script/chat \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "message": "Make the keeper'\''s motivation clearer in scene 2." }'
```

**TypeScript SDK**

```ts
const reply = await client.pipelines.chatStage(id, 'script', "Make the keeper's motivation clearer in scene 2.")
if (reply.proposed_change) {
const result = await client.pipelines.applyChatProposal(id, 'script', reply.turnId)
if (!result.applied) console.log(result.error.code)
}
```

## 완료된 파이프라인 브랜치
`POST /v1/pipelines/:id/branch`는 `completed` 파이프라인을 한 단계부터 새 파이프라인으로 다시 실행합니다. `fromStage` 앞의 단계는 승인된 상태로 복사되고, `fromStage`가 실행을 시작하며, 그 뒤의 단계는 새로 만들어집니다. 원본 파이프라인은 `completed` 상태로 남습니다.

브랜치는 캐릭터, 오브젝트, 장소를 새 파이프라인으로 복사하지만 이미지 파일은 재사용하므로, 파일이 중복되지 않습니다. 채팅은 비어 있는 상태로 시작합니다.

**curl**

```bash
curl -X POST https://app.nodaro.ai/v1/pipelines/c2f8a4e6-1b9d-4f3a-8c7e-6d2b9f1a5e3c/branch \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "fromStage": "scene_images" }'
```

**TypeScript SDK**

```ts
const branch = await client.pipelines.branch(id, { fromStage: 'scene_images' })
console.log(branch.pipelineId, branch.clonedStages)
```

이 라우트는 `{ pipelineId, clonedStages, clonedEntities }`와 함께 `201`로 응답합니다. 새 파이프라인의 ID, 승인된 상태로 복사된 단계, 복사된 캐릭터, 오브젝트, 장소의 수입니다. `fromStage`에는 위 표의 어떤 단계 이름이든 쓸 수 있습니다.

## 파이프라인 취소 또는 분기
- **취소.** `POST /v1/pipelines/:id/cancel`은 실행 중인 파이프라인을 중지하고, 아직 실행되지 않은 작업에 예약된 크레딧을 환불하며, `{ ok: true }`를 반환합니다. 이미 완료, 실패 또는 취소된 파이프라인은 `409 already_terminal`로 응답하며, 아무것도 바뀌지 않습니다.
- **분기.** `POST /v1/pipelines/:id/fork`는 파이프라인에서 캔버스를 떼어 냅니다. 파이프라인이 만든 모든 노드가 편집할 수 있는 일반 노드가 되고, 사용하지 않은 크레딧이 환불되며, 상태가 `forked`가 됩니다. 분기는 되돌릴 수 없습니다. 엔진으로 계속하려면 새 파이프라인을 시작하세요.

## 크레딧
파이프라인은 시작할 때 예상 비용을 예약하며, `upfront_credit_estimate`에 그 값이 나옵니다. `spent_credits`와 `reserved_credits`는 실행이 어디까지 왔는지 보여 줍니다. 취소되거나 실패한 파이프라인은 쓰지 않은 크레딧을 환불하며, `max_cost_credits`로 총액의 상한을 정합니다. [크레딧](https://nodaro.ai/docs/developers/api/credits)을 참조하세요.

## MCP와 SDK에서 사용
SDK는 모든 라우트를 `client.pipelines.*`로 감쌉니다. CLI에는 파이프라인 명령이 없습니다. AI 어시스턴트는 다음 MCP 도구를 사용합니다.

| 도구 | 스코프 | 하는 일 |
| --- | --- | --- |
| `start_pipeline` | `pipelines:execute` | 파이프라인을 시작합니다. 기본 모드는 `auto`입니다. |
| `get_pipeline_status` | `pipelines:read` | 상태, 단계, 크레딧을 읽습니다. |
| `pipeline_pending_approvals` | `pipelines:read` | 승인을 기다리는 단계 목록을 가져옵니다. |
| `chat_pipeline_stage`, `apply_chat_proposal` | `pipelines:approve` | 감독과 채팅하고 제안을 적용합니다. |
| `get_pipeline_stage_chat` | `pipelines:read` | 단계의 채팅을 읽습니다. |
| `branch_pipeline` | `pipelines:execute` | 완료된 파이프라인을 브랜치합니다. |

어시스턴트에서 가이드 방식으로 영화를 연출하는 방법은 [Film Director](https://nodaro.ai/docs/mcp/film-director)를 참조하세요.

## 오류
| 상태 | 코드 | 의미 |
| --- | --- | --- |
| `400` | `validation_error` | 본문이 잘못되었습니다. 예를 들어 길이가 5초 미만이거나 3,600초를 넘거나, 브랜치의 `fromStage`가 단계 이름이 아닌 경우입니다. |
| `400` | `duration_out_of_bounds` | 길이가 형식의 범위를 벗어나거나 600초를 넘습니다. |
| `400` | `pipeline_not_completed` | `completed`가 아닌 파이프라인에서 브랜치를 요청했습니다. |
| `400` | `invalid_stage` | 채팅을 보내거나 읽을 때 `script`, `shot_list`, `post_merge`가 아닌 단계를 지정했습니다. |
| `400` | `invalid_stage_name` | 승인에서 8개 단계에 속하지 않는 단계를 지정했습니다. 그런 단계를 읽으면 이 코드와 함께 `404`로 응답합니다. |
| `400` | `stage_not_implemented` | 거부 호출에서 `script`가 아닌 단계를 지정했습니다. |
| `400` | `invalid_change_type_for_stage` | 브랜치만 받는 `post_merge` 단계에 패치가 제안되었습니다. |
| `400` | `invalid_sub_gate` | 하위 게이트 라우트가 처리하는 게이트가 아닙니다. `match_cut_break_pending`의 경우 메시지에 각 불일치를 수락하는 라우트가 나옵니다. |
| `400` | `shot_not_found` | 수락할 샷이 해당 장면에 없습니다. |
| `400` | `not_a_match_cut` | 수락할 샷에 계획된 매치 컷이 없습니다. |
| `400` | `invalid_entity_type` | 엔티티 목록을 `character`, `object`, `location`, `scene`이 아닌 `type`으로 요청했습니다. |
| `401` | `unauthorized` | 토큰이 없거나, 잘못되었거나, 폐기되었습니다. |
| `402` | `insufficient_credits` | 계정의 크레딧이 예약에 부족합니다. |
| `403` | `edition_required` | 인스턴스가 Nodaro Cloud가 아닙니다. |
| `403` | `insufficient_scope` | OAuth 앱 토큰에 라우트의 스코프가 없습니다. |
| `404` | `not_found` | 해당 ID를 가진 내 파이프라인이 없습니다. 하위 게이트 호출에서는 단계가 그 게이트에서 기다리고 있지 않다는 뜻이기도 합니다. |
| `404` | `pipeline_not_found` | 브랜치 호출에서 존재하지 않거나 내 것이 아닌 파이프라인을 지정했습니다. |
| `404` | `stage_not_started` | 읽으려는 단계가 아직 시작되지 않았습니다. |
| `404` | `stage_not_found` | 하위 게이트 호출이 애니메이션 단계가 시작되지 않은 파이프라인에 도달했습니다. 단계 승인이나 거부는 이 코드와 함께 `409`로 응답합니다. |
| `404` | `scene_not_found` | 파이프라인에 해당 ID의 장면이 없습니다. |
| `409` | `patch_invalid` | 채팅 제안이 유효한 변경이 아니어서 적용할 수 없었습니다. |
| `409` | `stage_not_awaiting` | 채팅 제안을 적용하거나 수정과 함께 승인할 때, 단계가 더 이상 승인을 기다리고 있지 않습니다. |
| `409` | `stage_already_advanced` | 수정 없이 승인하거나 거부할 때, 단계가 더 이상 승인을 기다리고 있지 않습니다. |
| `409` | `stage_not_awaiting_approval` | 하위 게이트 호출이 승인을 기다리지 않는 애니메이션 단계에 도달했습니다. |
| `409` | `already_terminal` | 취소 호출이 이미 완료, 실패 또는 취소된 파이프라인에 도달했습니다. |
| `409` | `critic_retry_cap_reached` | 나 또는 자동 스크립트 평가 AI가 스크립트를 이미 두 번 수정했습니다. |
| `409` | `scene_not_planned` | 장면에 아직 샷 계획이 없어 불일치를 수락할 수 없습니다. |
| `501` | `chat_not_wired_for_stage` | 단계에 작동하는 채팅이 없습니다. 현재는 `shot_list`만 해당합니다. |

## 자주 묻는 질문

### 스토리 → 동영상 파이프라인은 무엇을 만드나요?

완성된 영화와 그 바탕이 되는 워크플로입니다. 파이프라인은 스크립트를 쓰고, 캐릭터, 오브젝트, 장소를 만들고, 샷을 계획하고, 키프레임을 렌더링하고, 사운드와 함께 애니메이션을 만들고, 결과를 합칩니다. GET /v1/pipelines/:id/timeline은 조립된 장면을 반환합니다.

### 아무도 승인하지 않아도 파이프라인을 실행할 수 있나요?

네. mode를 auto로 설정해 만들면 엔진이 모든 단계를 스스로 실행하고, 자동 평가 AI가 출력을 검사합니다. 상태가 completed 또는 failed가 될 때까지 GET /v1/pipelines/:id를 폴링하세요. 실행은 두 샷 사이에 계획한 매치 컷이 맞지 않을 때만 멈춥니다. 그러면 상태는 running으로 유지되고, 모든 불일치가 수락될 때까지 GET /v1/pipelines/:id/pending-approvals에 scene_images 단계가 나타납니다.

### 완성된 영화를 한 단계부터 다시 실행하려면 어떻게 하나요?

다시 실행할 단계를 지정해 POST /v1/pipelines/:id/branch를 호출하세요. Nodaro가 그 앞 단계는 유지하고 해당 단계와 이후 단계를 다시 실행하는 새 파이프라인을 만듭니다. 원본은 바뀌지 않습니다.

### 파이프라인을 취소하면 크레딧은 어떻게 되나요?

아직 실행되지 않은 작업에 예약된 크레딧은 환불됩니다. 이미 끝난 작업에 사용된 크레딧은 환불되지 않습니다. 이미 완료, 실패 또는 취소된 파이프라인은 취소할 수 없습니다. 이 호출은 409 already_terminal로 응답하며 아무것도 바꾸지 않습니다.

### 셀프 호스팅 설치에서 파이프라인을 실행할 수 있나요?

아니요. 파이프라인은 Nodaro Cloud에서만 실행되며, 셀프 호스팅 에디션은 403 edition_required로 응답합니다. 셀프 호스팅 설치에서는 같은 단계를 캔버스의 워크플로로 구성하세요.
