# 3D 장면

> REST로 편집 가능한 3D 클레이 장면을 생성, 편집, 렌더링하고, 3D 렌더링 Pro의 견적과 실행, 장면 리비전, 에셋, 결과물 조회 방법을 설명합니다.

Source: https://nodaro.ai/ko/docs/developers/api/3d-scenes

**3D 장면 API**는 프롬프트와 선택 사항인 이미지 또는 동영상 레퍼런스로 편집 가능한 애니메이션 클레이 장면을 만들고, 편집하고, MP4로 렌더링합니다. 생성 결과는 동영상이 아니라 **장면 플랜**입니다. 장면 플랜에는 오브젝트, 오브젝트의 움직임, 카메라, 조명이 담겨 있으므로, 렌더링하기 전에 프레이밍, 카메라 이동, 블로킹을 확인할 수 있습니다. 그다음 렌더링 결과는 동영상 모델의 레이아웃 레퍼런스가 됩니다.

**3D 렌더링 Pro**는 별도의 기능으로, 배포에 해당 엔진이 있는 경우 완성된 샷의 제작과 렌더링을 하나의 유료 작업으로 처리합니다. 기본 제작 엔진은 모든 에디션에서 작동하지만, 고급 엔진과 3D 렌더링 Pro는 배포에 따라 다르므로 먼저 배포의 지원 기능을 확인하세요. 크레딧은 Nodaro Cloud에서만 적용됩니다. 이 라우트들은 Bearer 토큰을 받습니다. [인증](https://nodaro.ai/docs/developers/api/authentication)을 참조하세요.

## 엔드포인트
| 메서드 | 경로 | 기능 |
| --- | --- | --- |
| `GET` | `/v1/3d-scene/capabilities` | 이 배포가 지원하는 항목: 기본 엔진, 고급 엔진, 3D 렌더링 Pro. |
| `POST` | `/v1/3d-scene/generate` | 프롬프트로 새 장면을 제작합니다. `{ jobId }`를 반환합니다. |
| `POST` | `/v1/3d-scene/edit` | 지시문 또는 연산으로 장면의 새 리비전을 만듭니다. `{ jobId }`를 반환합니다. |
| `POST` | `/v1/render-video/plan` | 장면 리비전을 MP4로 렌더링합니다. |
| `POST` | `/v1/pro-3d-render/quote` | 3D 렌더링 Pro 실행의 견적을 냅니다. 아무것도 예약하지 않습니다. |
| `POST` | `/v1/pro-3d-render` | 견적을 받은 3D 렌더링 Pro 작업을 실행합니다. |
| `POST` | `/v1/3d-scene/revisions/:revisionId/edits` | 저장된 장면에 결정론적 편집을 작업 없이 저장합니다. |
| `GET` | `/v1/3d-scene/revisions/:revisionId` | 저장된 리비전의 장면 매니페스트와 에셋 디스크립터. |
| `GET` | `/v1/3d-scene/revisions/:revisionId/assets/:assetId` | GLB나 카메라 트랙 같은 리비전의 재생용 에셋. |
| `GET` | `/v1/3d-scene/revisions/:revisionId/source` | 보관된 경우, 리비전의 편집 가능한 네이티브 소스. |
| `GET` | `/v1/3d-scene/deliveries/:jobId` | 내보내기가 전달한 결과물: 소스 리비전, 다이제스트, 디스크립터. |
| `GET` | `/v1/3d-scene/deliveries/:jobId/assets/:assetId` | 전달된 에셋 하나의 바이트. |

`POST /v1/generate-3d-scene`과 `POST /v1/edit-3d-scene`은 생성 라우트와 편집 라우트의 별칭이며, 검사와 크레딧이 같습니다. `POST /v1/render-video`도 `planType`과 `plan`을 받습니다. 이 별칭 덕분에 SDK의 범용 노드 실행기가 같은 라우트에 접근할 수 있습니다.

## 배포 지원 기능 확인
`GET /v1/3d-scene/capabilities`는 기본 엔진 지원 여부와 `advanced` 블록을 알려 주며, 사용할 수 있는 고급 엔진이 없으면 이 블록은 `null`입니다. `pro` 블록은 3D 렌더링 Pro를 사용할 수 있는지(`available`)를 알려 주고, 제공할 수 있는 엔진, 품질 프로필, 스타일, 화면 비율, 수정 횟수 상한을 나열합니다. 전체 어휘가 아니라 이 응답을 바탕으로 컨트롤을 만드세요.

요청한 엔진을 배포가 제공할 수 없으면 크레딧 확인 전에 `503 SCENE_CAPABILITY_UNAVAILABLE`로 응답합니다. Nodaro는 스스로 기본 제작 엔진으로 대체하지 않습니다. 또한 `GET /v1/nodes`는 3D 렌더링 Pro를 사용할 수 없는 곳에서는 해당 노드를 목록에서 빼고, 대화형 [3D 미리 보기 임베드](https://nodaro.ai/docs/developers/embed/scene3d)를 사용할 수 있으면 생성 노드에 `scene3d-embed-v1` 기능을 표시합니다.

## 장면 생성
`POST /v1/3d-scene/generate`는 새 장면을 제작하고 `{ jobId }`를 반환합니다. [작업 API](https://nodaro.ai/docs/developers/api/jobs)로 작업을 폴링하세요. 완료된 작업의 `output_data.scenePlan`이 편집 가능한 장면입니다.

**curl**

```bash
curl -X POST https://app.nodaro.ai/v1/3d-scene/generate \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
"prompt": "A red suitcase rolls behind a central pillar and reappears. Dolly right over four seconds.",
"durationSeconds": 4,
"fps": 24,
"aspectRatio": "16:9",
"references": [
{ "id": "suitcase-appearance", "kind": "image", "role": "appearance", "url": "https://cdn.nodaro.ai/uploads/suitcase.png" }
]
}'
```

**TypeScript SDK**

```ts

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

const scene = await client.nodes.runAndWait('generate-3d-scene', {
prompt: 'A red suitcase rolls behind a central pillar and reappears. Dolly right over four seconds.',
durationSeconds: 4,
fps: 24,
aspectRatio: '16:9',
references: [{ id: 'suitcase-appearance', kind: 'image', role: 'appearance', url: appearanceImageUrl }],
})
```

**CLI**

```bash
nodaro nodes run generate-3d-scene --params-file scene.json --watch --json
```

<TypeTable
type={{
prompt: { type: 'string', description: '오브젝트와 그 움직임, 카메라 배치와 이동, 타이밍입니다.', required: true },
references: { type: 'array', description: '레퍼런스는 최대 8개이며, 그중 동영상은 1개까지입니다. 형식은 { id, url, kind, role, objectId? }입니다. kind는 image 또는 video이고, role은 appearance, layout, motion 중 하나입니다.' },
inputAssets: { type: 'array', description: '저장된 GLB 파일 최대 8개입니다. 형식은 { id, revisionId, assetId, label? }입니다. 가져오기를 지원하는 고급 엔진이 필요합니다.' },
durationSeconds: { type: 'number', description: '장면 길이로, 1~60초입니다.', default: '4' },
fps: { type: 'number', description: '초당 프레임 수로, 15~60입니다.', default: '24' },
aspectRatio: { type: "'16:9' | '9:16' | '1:1' | '4:5'", description: '화면 비율입니다.', default: '16:9' },
engine: { type: "'basic' | 'blender-cloud' | 'blender-local'", description: '제작 엔진입니다. 고급 엔진은 배포에서 사용할 수 있어야 합니다.', default: 'basic' },
llmModel: { type: 'string', description: '기본 엔진 전용: 장면을 작성하는 언어 모델입니다. 크레딧 등급을 결정합니다.' },
reasoningEffort: { type: 'string', description: '기본 엔진 전용: 모델이 생각하는 시간입니다. 강도를 높이면 등급이 올라갈 수 있습니다.' },
acceptedSceneSchemaVersions: { type: 'number[]', description: '클라이언트가 읽을 수 있는 장면 플랜 버전입니다. 예: [1, 2].' },
maxRepairPasses: { type: 'number', description: '고급 엔진: 실행에 쓸 수 있는 수정 패스 횟수입니다.' },
}}
/>

- **레퍼런스는 근사치입니다.** 이미지는 보이지 않는 형상까지 알려 주지 않으며, 동영상은 움직임과 레이아웃의 가이드로 읽힙니다. 렌더링하기 전에 미리 보기를 확인하세요.
- **클립 전체만 가능.** 동영상 레퍼런스는 전체가 분석됩니다. 클립의 일부만 쓰려면 먼저 [동영상 자르기](https://nodaro.ai/docs/nodes/video/trim-video)로 자르세요. 일부 시간 범위를 지정하면 크레딧이 쓰이기 전에 거부됩니다.
- **저장된 형상.** `inputAssets`는 이미 가지고 있는 GLB 파일의 정확한 리비전을 선택합니다. 서버가 접근 권한과 파일 다이제스트를 직접 확인하므로 URL이나 해시를 보내지 말고, 이미지와 동영상은 `references`에 두세요. 기본 엔진은 가져온 형상을 과금 전에 거부합니다.
- **좌표.** 장면은 미터 단위를 쓰며, Y축이 위쪽이고, 회전은 라디안 단위이며, 프레임은 0부터 셉니다.

## 장면 편집
`POST /v1/3d-scene/edit`는 `scenePlan`, 그 `revisionId`를 담은 `expectedRevisionId`, 그리고 `prompt`(“기둥을 1미터 뒤로 옮겨 주세요” 같은 지시문) 또는 `operations` 중 하나를 받습니다. 편집에 성공하면 이전 리비전을 `parentRevisionId`로 가진 새 리비전이 만들어지며, 보낸 플랜은 그대로 유지됩니다. 리비전이 일치하지 않으면 거부됩니다. 완료된 작업은 `scenePlan`과 `changeSummary`를 반환합니다.

**curl**

```bash
curl -X POST https://app.nodaro.ai/v1/3d-scene/edit \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d @edit.json

# edit.json:
# { "scenePlan": { ... }, "expectedRevisionId": "rev_7c2e...",
#   "operations": [{ "op": "set-camera", "changes": { "focalLengthMm": 50 } }] }
```

**TypeScript SDK**

```ts
const edited = await client.nodes.runAndWait('edit-3d-scene', {
scenePlan: scene.scenePlan,
expectedRevisionId: scene.scenePlan.revisionId,
operations: [{ op: 'set-camera', changes: { focalLengthMm: 50 } }],
})
```

| 연산 | 필드 | 변경 내용 |
| --- | --- | --- |
| `set-object` | `objectId`, `changes` | ID를 제외한 오브젝트의 모든 필드. 키가 지정된 포즈를 바꾸려면 해당 키프레임 변경 사항도 포함하세요. |
| `add-object` | `object` | 오브젝트를 추가합니다. |
| `remove-object` | `objectId` | 오브젝트를 제거합니다. |
| `set-camera` | `changes` | 초점 거리 같은 카메라 설정. |
| `set-lighting` | `changes` | 조명. |
| `set-background` | `color` | 배경색. |

지시 편집 중에 오브젝트를 바꾸지 않으려면 `lockedObjectIds`를 보내세요. 새 `references`는 ID를 기준으로 기존 레퍼런스와 병합됩니다. 같은 ID는 기존 레퍼런스를 교체하며, 전체 개수는 생성 한도 안에 있어야 합니다. 편집된 장면 전체가 검증되므로, 편집 때문에 부모나 레퍼런스를 잃은 항목이 남지 않습니다.

연산은 언어 모델을 호출하지 않으며 0크레딧이 듭니다. 지시 편집은 장면 제작과 같은 등급을 사용합니다. 되돌리거나 비교할 수 있도록 이전 리비전을 보관하세요.

### 저장된 장면에 편집 저장
저장된 버전 2 장면의 경우, `POST /v1/3d-scene/revisions/:revisionId/edits`는 결정론적 편집(변형, 머티리얼 색상, 표시 여부, 카메라 오프셋)을 작업 없이, 언어 모델 요금 없이 저장합니다. `newRevisionId`, 기준 리비전의 `expectedContentHash`, `operations`, 그리고 선택적으로 `lockedObjectIds`를 보내세요. 이 라우트는 `{ scenePlan, changeSummary }`를 반환합니다.

다이제스트가 오래되었거나 리비전 ID가 충돌하면 `409`로 응답합니다. 전송 중에 요청이 실패하면 같은 `newRevisionId`와 같은 본문으로 다시 시도하세요. 이 라우트에는 장면 편집 권한이 필요하며, OAuth 앱 토큰에는 `workflows:write`가 필요합니다. 새 리비전은 별도로 저장됩니다. 그 사이에 아무도 활성 리비전을 바꾸지 않았는지 확인한 다음, 워크플로에서 새 리비전을 직접 선택하세요.

## MP4로 장면 렌더링
`{ "planType": "3d-scene", "plan": scenePlan }`와 함께 `POST /v1/render-video/plan`을 호출하면 정확히 하나의 리비전을 렌더링합니다. 카메라, 프레임 크기, 프레임 속도, 길이는 플랜에서 가져오며, 언어 모델을 호출하지 않고 프롬프트도 다시 읽지 않습니다. 완료된 작업은 `videoUrl`, `thumbnailUrl`, `sceneRevisionId`, `renderer: "scene3d/three"`를 반환합니다.

**curl**

```bash
curl -X POST https://app.nodaro.ai/v1/render-video/plan \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{ \"planType\": \"3d-scene\", \"plan\": $(cat scene-plan.json) }"
```

**TypeScript SDK**

```ts
const clip = await client.nodes.runAndWait('render-video', {
planType: '3d-scene',
plan: edited.scenePlan,
})
```

렌더링 가격은 플랜 자체의 `width`와 `height`로 정해집니다.

| 프레임 | Nodaro Cloud 가격 | 예시 |
| --- | --- | --- |
| 긴 변 1920px 이하, 모든 형태 | 55크레딧 | 1920x1080, 1080x1920, 1920x1920 |
| 긴 변 1920px 초과, 5.12메가픽셀 이하 | 83크레딧 | 2560x1440, 1440x2560 |
| 긴 변 1920px 초과, 5.12메가픽셀 초과 | 138크레딧 | 2048x2560, 2560x2560 |

생성 라우트가 제공하는 모든 화면 비율은 1920px 이하로 렌더링되므로, 크기를 바꾸지 않은 장면은 항상 기본 가격입니다. 더 큰 등급은 플랜에 더 큰 `width`와 `height`를 직접 설정했을 때만 적용됩니다. 모델 비용 식별자는 `render-video`, `render-video:3d-large`, `render-video:3d-xlarge`입니다.

## 렌더링을 동영상 레퍼런스로 사용
클레이 렌더링에는 레이아웃이 담겨 있습니다. 피사체의 위치, 무엇이 무엇 앞에 있는지, 프레이밍, 카메라 이동, 타이밍입니다. 동시에 텍스처가 없는 회색 클레이라는 질감도 담겨 있어서, 따로 지시하지 않으면 동영상 모델이 그 질감을 따라 합니다. 다음 두 가지 규칙을 지키면 레이아웃은 살리고 클레이 질감은 버릴 수 있습니다.

1. **항상 레퍼런스의 범위를 정하세요.** [동영상 생성](https://nodaro.ai/docs/nodes/video/generate-video)의 `referenceVideoUrls[N]`에 MP4를 넣고, `referenceVideoCaptions[N]`에 무엇을 맞추고 무엇을 무시할지 적은 캡션을 넣으세요. 워크플로에서는 Nodaro가 이 캡션을 자동으로 추가합니다. 문구는 다음과 같습니다.

   
```
레이아웃 레퍼런스 전용입니다. 피사체의 위치와 블로킹, 전경의 가림, 프레이밍, 카메라 앵글, 카메라 모션, 타이밍을 맞추세요. 텍스처가 없는 회색 클레이 임시 질감, 단조로운 임시 색상, 머티리얼, 조명, 빈 배경은 무시하세요. 그중 어느 것도 목표 질감이 아닙니다. 질감은 프롬프트와 다른 레퍼런스에서 가져오세요
```

영어 원문 프롬프트:

```
LAYOUT reference only — match its subject positions and blocking, its foreground occlusion, its framing, its camera angle, its camera motion and its timing. Ignore its untextured grey clay placeholder look, its flat placeholder colours, its materials, its lighting and its empty background; none of that is the target look. Take the look from the prompt and from the other references
```

2. **실제처럼 보여야 하는 인물마다 캐릭터 레퍼런스를 따로 주세요.** 레퍼런스가 없는 인물은 클레이 대역으로 남습니다. 장소나 스타일 이미지를 위해 레퍼런스 슬롯 두 개는 비워 두세요.

원래의 외관 이미지도 함께 보내고, 렌더링을 시작 프레임으로는 절대 쓰지 마세요. 시작 프레임은 질감을 고정하며, 캡션은 시작 프레임에 적용되지 않습니다.

## 3D 렌더링 Pro 실행
3D 렌더링 Pro는 호스팅된 빌드 엔진에서 장면 제작과 내보내기를 중단 없이 이어지는 하나의 작업으로 처리합니다. 한 번의 실행이 끝나면 정확한 구성과 MP4를 함께 받습니다. 가격은 배포가 정하므로 견적이 기준입니다.

### 견적
`POST /v1/pro-3d-render/quote`는 `{ quoteId, expiresAt, maxCredits, breakdown, pricingVersion, capabilitiesVersion, normalizedInputHash }`를 반환합니다. 아무것도 예약하지 않습니다. `maxCredits`는 청구액이 아니라 상한입니다.

### 실행
`POST /v1/pro-3d-render`는 같은 본문에 `quoteId`를 더하고, 8~255자의 `Idempotency-Key` 헤더를 함께 받습니다. `{ jobId }`를 반환합니다. 견적 이후에 본문이 바뀌었거나 견적이 만료되었으면 무엇이든 예약되기 전에 거부됩니다. 시간 초과된 요청을 다시 시도할 때는 같은 키를 다시 사용하세요. 그래야 하나의 요청이 두 번의 유료 실행이 되지 않습니다.

**curl**

```bash
curl -X POST https://app.nodaro.ai/v1/pro-3d-render/quote \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d @pro.json

curl -X POST https://app.nodaro.ai/v1/pro-3d-render \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Idempotency-Key: suitcase-shot-0001" \
  -H "Content-Type: application/json" \
  -d @pro-with-quote.json
```

**TypeScript SDK**

```ts
const caps = await client.scene3d.capabilities()
if (caps.pro?.available) {
const params = {
source: {
kind: 'prompt' as const,
prompt: 'A red suitcase rolls behind a central pillar and reappears',
references: [{ id: 'look', kind: 'image' as const, role: 'appearance' as const, url: appearanceImageUrl }],
},
durationSeconds: 30,
fps: 24,
aspectRatio: '21:9',
maxRepairPasses: 2,
acceptedSceneSchemaVersions: [2],
}
const quote = await client.scene3d.quotePro(params)
console.log(quote.maxCredits, quote.breakdown)
const shot = await client.scene3d.renderProAndWait({ ...params, quoteId: quote.quoteId })
console.log(shot.videoUrl, shot.sceneRevisionId)
}
```

본문의 `source`는 다음 중 정확히 하나입니다.

| `source` | 동작 |
| --- | --- |
| `{ kind: "prompt", prompt, references?, inputAssets? }` | 새 장면을 제작한 다음 렌더링합니다. |
| `{ kind: "scene", revisionId, sourceJobId }` | 해당 리비전만 그대로 렌더링하며, 제작 비용이나 빌드 비용이 들지 않습니다. 작업 기록에만 보관된 기본 엔진 장면에는 `sourceJobId`가 필요합니다. |
| `{ kind: "scene", revisionId, sourceJobId, editPrompt }` | 먼저 장면을 수정한 다음 렌더링합니다. 단순 내보내기에는 `editPrompt`를 생략하세요. 빈 문자열은 다른 요청으로 처리됩니다. |
| `{ kind: "local-export", exportId, connectionId }` | 사용할 수 있는 경우, 페어링된 데스크톱 앱에서 완료된 내보내기를 사용합니다. |

| 필드 | 기능 |
| --- | --- |
| `engine` | `blender-cloud`(기본값), 또는 데스크톱이 페어링된 경우 `blender-local`. |
| `localConnectionId` | `blender-local`에 쓰는 페어링된 데스크톱. |
| `quality`, `style` | 품질 프로필과 스타일 `clay`. |
| `maxRepairPasses` | 0~2, 기본값 2. 각 패스는 유료 작업입니다. |
| `durationSeconds`, `fps`, `aspectRatio` | 타이밍과 프레임. `aspectRatio`에는 `21:9`도 포함됩니다. |
| `acceptedSceneSchemaVersions` | 클라이언트가 읽을 수 있는 장면 플랜 버전. |
| `workflowId`, `nodeId`, `forcePrivate` | 일반적인 실행 컨텍스트. |

모델 필드는 없습니다. 플래너가 고정되어 있습니다. `scene` 소스에서 장면 자체의 타이밍을 유지하려면 타이밍 필드를 생략하세요. 타이밍 필드를 보내면 장면의 타이밍이 다시 조정되며, 호환되지 않는 값은 거부됩니다. 프롬프트 소스는 버전 2 장면을 만들므로, 버전 2를 받지 않는 클라이언트는 비용 없이 거부됩니다.

### 완료된 실행이 반환하는 내용
| 필드 | 담긴 내용 |
| --- | --- |
| `videoUrl`, `scenePlan`, `sceneRevisionId` | MP4, 렌더링의 바탕이 된 정확한 구성, 그 리비전의 ID. 나중에 렌더링만 하는 방식으로 이 리비전을 다시 내보낼 수 있습니다. |
| `posterAssetId`, `shotStills` | 포스터, 그리고 샷마다 하나씩 `{ shotIndex, frame, assetId, url }` 형식으로 된 스틸 이미지. 샷 순서대로 정렬되며 추가 비용이 없습니다. |
| `validation` | `{ status, reportAssetId, warnings }`. |
| `renderer`, `metadata` | 렌더러, 그리고 `{ width, height, fps, frames, duration }`. |
| `metadata.summary`, `repairPasses`, `admissionRetries`, `mechanicalPasses`, `restoredAssertions` | 장면을 제작한 실행은 무엇을 했는지 보고합니다. 짧은 요약, 실행한 수정, 기타 패스입니다. 렌더링만 하는 내보내기에는 이 필드가 없습니다. |
| `metadata.review` | 장면이 모든 필수 검사를 통과했지만 시각 검토자의 승인 없이 전달된 경우에만 있습니다. |

각 스틸의 `url`은 공개 링크가 아니라 배포의 인증된 엔드포인트이므로, 내 토큰으로 가져오세요. 그래도 생성에 이미지 레퍼런스로 전달할 수 있으며, 이 경우 Nodaro가 해당 실행에 그 파일 하나를 잠시 읽을 권한을 줍니다.

`metadata.review` 자체가 있는지 확인한 다음 `verdict`를 읽으세요. `refused`는 수정 한도를 다 쓴 뒤에도 검토자가 여전히 이의를 제기했다는 뜻이며, `objections`에 검토자가 찾은 문제가 나열됩니다. `unavailable`은 검토가 쓸 만한 답을 내지 못해 아무도 장면을 판단하지 않았다는 뜻입니다. 두 경우 모두 필수 검사는 통과했으므로 `validation.status`는 여전히 `passed`입니다. 경고 코드는 계속 추가될 수 있으므로, 모르는 코드는 참고 정보로 취급하세요. 모든 필드와 경고 코드는 [3D 렌더링 Pro](https://nodaro.ai/docs/nodes/video/pro-3d-render)를 참조하세요.

### 실행이 실패할 때
제출 시 발생하는 오류는 거부입니다. `503 SCENE_CAPABILITY_UNAVAILABLE`, `503 price_not_configured`(운영자가 가격을 설정하지 않았으며, 아무것도 예약되지 않음), `400 validation_error`가 있습니다. 실행 중 실패는 작업에 기록되며, 오류 메시지는 코드로 시작합니다.

| 코드 | 재시도 여부 | 의미 |
| --- | --- | --- |
| `SCENE_PROVIDER_UNAVAILABLE` | 예, 몇 분 뒤에 | 플래너의 모델을 사용할 수 없거나 과부하 상태였습니다. |
| `SCENE_PLANNING_TIMEOUT` | 예 | 플래닝이 너무 오래 걸렸습니다. 다시 시도하거나, 브리프와 레퍼런스를 줄이세요. |
| `SCENE_PLANNER_OUTPUT_INVALID` | 그대로는 안 됨 | 플랜을 만들 수 없었습니다. 브리프를 단순하게 하거나 레퍼런스를 줄이세요. |
| `SCENE_QUALITY_FAILED` | 먼저 초안 확인 | 수정 한도를 다 쓴 뒤에도 필수 검사가 실패했거나 레시피가 거부되었습니다. |
| `SCENE_RESOURCE_LIMIT`, `SCENE_EXPORT_UNSUPPORTED`, `SCENE_REVISION_CONFLICT`, `SCENE_BUILD_TIMEOUT`, `SCENE_RENDER_FAILED` | 경우에 따라 다름 | 빌드나 렌더링을 끝낼 수 없었습니다. |

실패한 고급 엔진 작업에도 빌드한 결과가 남아 있을 수 있습니다. 어떤 패스가 장면을 빌드했다면 `output_data`에 초안이 담깁니다. `scenePlan`, `sceneRevisionId`, `deliveryId`, `posterAssetId`, 그리고 `status: "failed"`인 `validation`입니다. 초안은 편집하거나 렌더링할 수 있는 일반 리비전입니다. 컴파일된 패스가 없으면 초안도 없지만, `validation.sourceRetained`가 거부된 레시피를 보관했는지 알려 줍니다. 보관된 레시피는 편집 권한이 있으면 결과물의 `source-json` 디스크립터에서 무료로 읽을 수 있습니다. 같은 프롬프트를 다시 실행하면 같은 제작 비용을 두 번 내게 됩니다.

워크플로 실행에서 결과를 보관한 실패 노드는 `nodeStates[nodeId].output`에도 그 결과를 담습니다. 상태가 아니라 필드가 있는지를 확인하고, `output`이 있다고 해서 성공으로 판단하지 마세요.

## 장면 버전과 저장된 에셋
장면 플랜은 두 가지 버전 중 하나입니다. 버전별 필드를 읽기 전에 `schemaVersion`을 확인하세요.

| 버전 | 저장하는 내용 | 생성 주체 |
| --- | --- | --- |
| 1 | 기본 도형, 그룹, 오브젝트와 카메라의 희소 키프레임. | 기본 엔진. |
| 2 | 이름이 있는 엔티티, 저장된 GLB 형상, 매 프레임 샘플링된 카메라, 연속된 샷. | 고급 엔진과 3D 렌더링 Pro. |

버전 2 플랜은 각 에셋을 불투명한 `assetId`, 바이트 길이, SHA-256 다이제스트로 나열하며, 저장소 키나 다운로드 URL은 절대 포함하지 않습니다. 버전 2는 강체 애니메이션이 있는 클레이 형상을 받으며, 텍스처가 있거나 스키닝되었거나 모핑되는 에셋은 거부합니다. 한도는 엔티티 100개, 메시 노드 2,000개, 삼각형 200,000개, 샷 32개, 재생용 에셋 64MiB입니다. 전체 형식은 [장면 플랜 형식](https://nodaro.ai/docs/developers/embed/scene3d-format)을 참조하세요.

저장된 리비전에는 전용 라우트가 있습니다. 이 라우트들은 Bearer 토큰이 필요하고, `Cache-Control: no-store`로 응답하며, 삭제되었거나 접근할 수 없는 리비전에는 `404`를 반환합니다.

- `GET /v1/3d-scene/revisions/:revisionId`는 매니페스트와 에셋 디스크립터를 반환합니다. 재생용 에셋에는 리비전이 속한 워크플로의 보기 권한이 필요하고, 네이티브 소스에는 편집 권한이 필요합니다. 개인 리비전은 소유자만 볼 수 있습니다.
- `GET /v1/3d-scene/deliveries/:jobId`는 내보내기가 전달한 결과물을 반환합니다. `sourceKind`, 정확한 `sceneRevisionId`, 소스 다이제스트, 디스크립터(포스터, 검증 보고서, 샷마다 하나씩인 `shot-still`)입니다. `…/assets/:assetId`는 범위 요청을 지원하며 바이트를 반환합니다. 읽기에는 결과물과 소스 워크플로 모두에 대한 접근 권한이 필요하며, 비용은 들지 않습니다.

SDK에서는 `client.scene3d.getDelivery`, `deliveryAssetBytes`, `retainedRecipe`, `assetBytes`, `sourceBytes`, `applyEdits`가 이 라우트들을 감쌉니다.

## MCP에서 사용
AI 어시스턴트는 `workflows:execute` 범위로 `generate_3d_scene`, `edit_3d_scene`, `render_3d_scene`을 사용하며, 배포가 지원하는 경우 `pro_3d_render`도 사용합니다. [MCP로 3D 장면 사용](https://nodaro.ai/docs/mcp/3d-scenes)을 참조하세요. 내 페이지에 장면을 표시하려면 [3D 미리 보기 임베드](https://nodaro.ai/docs/developers/embed/scene3d)를 참조하세요.

## 오류
| 상태 | 코드 | 의미 |
| --- | --- | --- |
| `400` | `validation_error` | 본문이 유효하지 않습니다. 예를 들어 레퍼런스 목록이 한도를 넘었거나, 알 수 없는 엔진이거나, `quoteId`가 없거나, `Idempotency-Key`가 없거나 잘못되었거나, 클라이언트가 받지 않는 스키마 버전인 경우입니다. |
| `401` | `unauthorized` | 토큰이 없거나, 유효하지 않거나, 해지되었습니다. |
| `402` | `insufficient_credits` | Nodaro Cloud 전용입니다. 계정 잔액으로 예약 금액을 감당할 수 없습니다. |
| `404` | `not_found` | 리비전이나 결과물이 없거나, 접근할 수 없습니다. |
| `409` | — | 편집의 리비전이나 콘텐츠 다이제스트가 더 이상 일치하지 않습니다. 장면을 다시 읽으세요. |
| `503` | `SCENE_CAPABILITY_UNAVAILABLE` | 이 배포에서는 해당 엔진, 가져오기 지원 또는 3D 렌더링 Pro를 사용할 수 없습니다. |
| `503` | `price_not_configured` | 이 배포에는 3D 렌더링 Pro의 크레딧 가격이 설정되어 있지 않습니다. 아무것도 예약되지 않았습니다. |

## 자주 묻는 질문

### 3D 장면 API는 무엇을 만드나요?

동영상이 아니라 편집 가능한 장면 플랜을 만듭니다. 플랜에는 오브젝트, 오브젝트의 움직임, 카메라, 조명이 담깁니다. 원하는 만큼 편집한 다음 POST /v1/render-video/plan으로 MP4로 렌더링하세요.

### 3D 장면에는 크레딧이 얼마나 드나요?

Nodaro Cloud에서 장면 제작에는 언어 모델 등급에 따라 11크레딧, 33크레딧 또는 44크레딧이 들며, 동영상 레퍼런스를 제공하면 동영상 분석 가격이 더해집니다. 렌더링은 긴 변이 1920px 이하이면 55크레딧, 5.12메가픽셀 이하이면 83크레딧, 그보다 크면 138크레딧입니다.

### 결정론적 편집은 무료인가요?

네. 오브젝트 이동이나 렌즈 변경처럼 연산으로 보내는 편집은 언어 모델을 호출하지 않으며 0크레딧이 듭니다. 프롬프트로 작성하는 지시 편집은 장면 제작과 가격이 같습니다.

### 클레이 렌더링을 동영상 레퍼런스로 쓰려면 어떻게 하나요?

동영상 생성의 referenceVideoUrls에 MP4를 넣고, referenceVideoCaptions에 레이아웃과 카메라는 맞추고 회색 클레이 질감은 무시하라는 캡션을 넣으세요. 외관 이미지와, 실제처럼 보여야 하는 모든 인물의 캐릭터 레퍼런스도 함께 보내세요.

### 3D 렌더링 Pro와 3D 장면 생성은 어떻게 다른가요?

3D 장면 생성은 저렴하고 편집 가능한 클레이 미리 보기를 만들며, 별도의 렌더링으로 MP4를 만듭니다. 3D 렌더링 Pro는 배포에 해당 엔진이 있는 경우, 완성된 샷의 제작과 렌더링을 하나의 유료 작업으로 처리합니다.
