노드
POST /v1/<node-type>으로 Nodaro 노드를 실행하고, 노드·모델·피커 값을 조회하며, 레퍼런스와 연출 ID로 프롬프트를 조정하는 방법입니다.
노드 실행은 워크플로를 만들지 않고 Nodaro 노드 하나를 직접 호출합니다. 노드의 설정을 본문에 담아 POST /v1/<node-type>을 보내면 됩니다. generate-image와 generate-video부터 text-to-speech까지 모든 생성 노드가 이 형식을 따르며, 조회 엔드포인트는 어떤 노드, 모델, 설정이 있는지 알려 줍니다. 대부분의 노드 실행은 비동기입니다. 응답으로 jobId를 받고, 결과가 준비될 때까지 이 작업을 폴링합니다.
노드 실행
노드 타입의 경로는 POST /v1/ 뒤에 타입을 붙인 것이고, 본문은 노드의 설정을 담은 JSON입니다.
POST /v1/generate-image
Authorization: Bearer ndr_…
Content-Type: application/json
{ "prompt": "a lighthouse in a storm, oil painting", "provider": "nano-banana-pro", "aspectRatio": "3:4" }돌아오는 응답은 노드에 따라 다릅니다.
- 생성 노드는
{ "jobId": "…" }와 함께200으로 응답합니다. 작업은 워커에서 실행되며, 상태가completed가 될 때까지GET /v1/jobs/:id/status로 작업을 폴링합니다. 작업을 참고하세요. - 이미지 생성과 동영상 생성은 선택한 모델에 맞게 서버가 보정한 설정 목록인
adjustments를 추가할 수 있습니다.generate-video와generate-image는warnings도 추가할 수 있습니다. 파라미터 보정을 참고하세요. - 인라인 노드는
combine-text처럼jobId없이 전체 결과를 바로 반환합니다. - 스크래퍼도
web-scrape처럼 바로 응답합니다. 응답에는 기록용jobId와 데이터 자체가 함께 들어 있습니다.
생성은 시작할 때 크레딧을 예약합니다. 계정 잔액으로 실행을 충당할 수 없으면 402 insufficient_credits로 응답합니다.
경로가 더 긴 노드
언어 모델을 호출하는 텍스트 노드는 대부분 같은 POST /v1/<node-type> 규칙을 따릅니다. generate-script, image-critic, qa-check, describe-to-picker가 그렇습니다. 몇몇 다른 노드는 더 긴 경로에 등록되어 있습니다.
| 노드 타입 | 경로 |
|---|---|
llm-chat | POST /v1/llm-chat/generate |
after-effects | POST /v1/after-effects/generate |
motion-graphics | POST /v1/motion-graphics/generate |
lottie-overlay | POST /v1/lottie-overlay/generate |
3d-title | POST /v1/3d-title/generate |
image-to-text | POST /v1/image-to-text/describe |
video-composer | POST /v1/scene-graph/generate |
SDK의 client.nodes.run(type, params)와 CLI의 nodaro nodes run은 이런 노드 타입을 각각의 경로로 대신 보내 줍니다. 노드 실행을 참고하세요.
언어 모델 경로는 선택 필드 두 개를 받습니다.
reasoningEffort: 모델에 따라none,low,medium,high,xhigh또는max입니다. 생략하거나 모델이 지원하지 않는 수준을 고르면 모델의 기본값이 쓰입니다.xhigh와max는 크레딧 등급이 한 단계 올라갑니다. 이것만으로는 프리미엄 모델의 가격이 프리미엄 그대로입니다. Claude 모델에서는 어떤 추론 강도든 Anthropic 자체 API에서 실행되며, 이때도 크레딧이 한 단계 올라갑니다. 따라서 프리미엄 Claude 모델은 premium-direct 가격이 됩니다.advancedMode: true: Gemini와 Claude 모델에서 쓸 수 있습니다. 요청이 모델 제작사의 자체 API에서 실행되며,temperature,maxTokens, 전체 추론 범위가 적용되는 유일한 실행 경로입니다.reasoningEffort와 별개로 크레딧이 한 단계 올라갑니다. 프리미엄 모델은 premium-direct 가격으로 올라갑니다. 이 실행 경로가 없는 모델은400 advanced_mode_unsupported로 응답합니다.
describe-to-picker는 같은 요청 안에서 결과를 응답하며, 서버 전송 이벤트로 답을 스트리밍할 수도 있습니다. 피커로 설명을 참고하세요. generate-script의 필드는 대본 생성에 있습니다.
예시: 이미지 생성
작업 시작
curl -s -X POST https://app.nodaro.ai/v1/generate-image \
-H "Authorization: Bearer $NODARO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "a knight on a hill at dawn, cinematic",
"provider": "nano-banana-pro",
"aspectRatio": "16:9",
"resolution": "2K"
}'{ "jobId": "0f1a9c2e-5b7d-4e8a-9c31-6d2f8b4a7e10" }import { createClient, StaticTokenAuth } from '@nodaro/sdk'
const client = createClient({
baseUrl: 'https://app.nodaro.ai',
auth: new StaticTokenAuth(process.env.NODARO_API_KEY!),
})
const result = await client.nodes.run('generate-image', {
prompt: 'a knight on a hill at dawn, cinematic',
provider: 'nano-banana-pro',
aspectRatio: '16:9',
resolution: '2K',
})nodaro nodes run generate-image \
--param prompt="a knight on a hill at dawn, cinematic" \
--param provider=nano-banana-pro \
--param aspectRatio=16:9 \
--param resolution=2K작업 폴링
작업이 completed 또는 failed가 될 때까지 2~5초마다 상태를 요청하세요.
curl -s https://app.nodaro.ai/v1/jobs/0f1a9c2e-5b7d-4e8a-9c31-6d2f8b4a7e10/status \
-H "Authorization: Bearer $NODARO_API_KEY" | jq -r .data.status결과 읽기
완료된 이미지 작업은 output_data.imageUrl에 이미지 URL을 담고 있습니다. 동영상 작업은 videoUrl, 오디오 작업은 audioUrl을 사용하며, 많은 작업에 thumbnailUrl도 함께 있습니다.
{
"data": {
"id": "0f1a9c2e-5b7d-4e8a-9c31-6d2f8b4a7e10",
"status": "completed",
"progress": 100,
"output_data": { "imageUrl": "https://…/0f1a9c2e.png" },
"error_message": null
}
}SDK와 CLI가 폴링을 대신 처리할 수 있습니다.
const output = await client.nodes.runAndWait('generate-image', {
prompt: 'a knight on a hill at dawn, cinematic',
provider: 'nano-banana-pro',
})
console.log(output.imageUrl)
// Several candidates at once, in input order:
const results = await client.nodes.runMany('generate-image', [
{ prompt: 'a knight on a hill, sunrise' },
{ prompt: 'a knight on a hill, golden hour' },
{ prompt: 'a knight on a hill, blue hour' },
])
for (const { jobId, output } of results) console.log(jobId, output.imageUrl)nodaro nodes run generate-image \
--param prompt="a knight on a hill at dawn, cinematic" \
--param provider=nano-banana-pro \
--watch --json | jq -r '.output_data.imageUrl'runAndWait는 기본적으로 최대 15분 동안 2,000ms마다 폴링합니다. pollMs와 maxMs로 이 값을 바꾸고, signal에 AbortSignal을 넣어 중지하고, onProgress로 진행 상황을 확인하세요. instanceof로 잡을 수 있는 타입이 지정된 오류를 던집니다.
| 오류 | 발생 시점 |
|---|---|
InsufficientCreditsError, StorageExceededError, JobBlockedError | 작업이 시작되기 전에 실행이 거부되었습니다. |
JobFailedError | 작업이 failed 또는 cancelled로 끝났습니다. jobId와 오류 메시지를 담고 있습니다. |
JobTimeoutError | maxMs가 지났습니다. 작업은 취소되지 않으며 대개는 그대로 완료됩니다. 나중에 client.jobs.get(jobId)로 가져오세요. |
JobAbortedError | 내 signal이 발생했습니다. |
JobHeldError | 결과를 검토하는 배포 환경에서 작업이 pending_review 상태가 되었습니다. 작업은 취소되지 않으므로 나중에 다시 확인하세요. |
CLI에서는 배열이나 중첩 객체처럼 복잡한 본문을 --params-file body.json으로 파일에 담아 보내세요. 같은 키에 대해서는 플래그 값이 파일보다 우선하며, true, false, null과 숫자는 텍스트에서 변환됩니다.
이미지 생성 설정
아래 필드는 POST /v1/generate-image에서 가장 많이 쓰는 설정입니다. 각 설정이 에디터에서 하는 일은 이미지 생성을, 각 모델이 지원하는 기능은 이미지 모델을 읽어 보세요.
속성
타입
마스크 편집이나 리파인은 해당 모델에서 새로 생성하는 것과 같은 비용이 듭니다.
/v1/generate-image는 요청한 것 중 보내지 않은 것이 있을 때 { code, message } 목록인 warnings로 응답할 수 있습니다. 현재 코드는 moodboard_images_not_sent 하나뿐입니다. 무드보드의 이미지 일부가 모델의 레퍼런스 한도에 들어가지 않았다는 뜻입니다. 실행은 계속되며, 작업은 같은 경고를 input_data.warnings에 보관합니다.
프리셋 적용
POST /v1/generate-image는 presetId를 받습니다. generate-image/cinematic-still 같은 기본 제공 ID이거나 내 프리셋의 uuid입니다. 기본 제공 ID는 토큰이 필요 없는 GET /v1/node-presets/factory?nodeType=generate-image로 나열하세요. 라우트는 편집기가 이미지 생성 노드에 적용하는 방식으로 프리셋을 적용하므로, 작업은 편집기의 실행이 보낼 작업과 같습니다.
- 프리셋의 프롬프트와 그 앞뒤 텍스트(
promptPrefix와promptSuffix). - 기본값으로 채워진
{name || default}자리 표시자. - 프롬프트에 합쳐지는 프리셋의
style. - 프리셋의 네거티브 프롬프트. 이를 받는 모델에는 그대로 보내고, 그 밖의 모든 모델에는 프롬프트에
Avoid:줄로 추가합니다. - 프리셋의 설정과, 프리셋이 비워 둔 설정에 대한 편집기의 기본값: 모델
gpt-image-2와 비율16:9. 모델과 가격은 편집기와 같습니다.
내가 보낸 필드는 적용된 노드를 편집한 것처럼 프리셋보다 우선합니다. 내 prompt는 프리셋의 프롬프트를 대신하며 프리셋의 앞뒤 텍스트로 감싸집니다. provider, aspectRatio, resolution, quality, negativePrompt, style과 그 밖의 설정은 프리셋의 값을 대신합니다. 레퍼런스, sessionId, origin은 유지됩니다.
curl -s -X POST https://app.nodaro.ai/v1/generate-image \
-H "Authorization: Bearer $NODARO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"presetId": "generate-image/logo-wordmark", "presetVariables": {"BRAND": "Acme"}}'- 기본값이 없는 자리 표시자는
{BRAND}처럼presetVariables에 값이 있어야 합니다. 값이 없으면 라우트는code: "preset_variables_required"와tokens: ["BRAND"]를 담아400으로 응답합니다. - 알 수 없는
presetId는404 preset_not_found로 응답합니다. - 크레딧 확인은 프리셋을 적용한 뒤의 요청으로 가격을 계산합니다.
presetId와presetVariables는 작업에 저장되지 않습니다. - 사진을 바탕으로 하는 프리셋은
minReferences가 0보다 큽니다. 그 수만큼 이미지를referenceImageUrls로 보내세요. 프리셋을 참고하세요. - 프리셋은
count,models와 함께 쓸 수 있습니다. 다음 섹션을 참고하세요. - 프리셋은 내 팔레트나 무드보드를 쓸 수 없습니다. 내가 보냈거나 프리셋에 저장된
direction이palette:<id>또는moodboard:<id>를 가리키면, 라우트는 무엇이든 만들거나 청구하기 전에 그 키를field에 담아400 validation_error로 응답합니다.
한 번의 호출로 이미지 여러 장
POST /v1/generate-image는 이미지를 한 번에 여러 장 만들 수 있습니다.
count(1~4)는 한 모델로 그 수만큼 이미지를 만듭니다. 각 이미지는 별도의 작업입니다.models(서로 다른 이미지 모델 ID 2~4개,provider가 받는 것과 같은 ID)는provider를 대신하며, 각 모델에 같은 요청을 보냅니다. 각 모델의 설정은 단일 호출에서처럼 그 모델에 맞춰지고, 보정은 모델마다 보고됩니다.- 둘 다 쓰면
count는 모델마다 적용됩니다.models: ["gpt-image-2", "flux-2-max"]와count: 2는 이미지 4장을 만듭니다. 한 요청은 최대 8장을 만듭니다.
curl -s -X POST https://app.nodaro.ai/v1/generate-image \
-H "Authorization: Bearer $NODARO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 0f8e6c1a-try-1" \
-d '{"prompt": "a lighthouse at dusk", "models": ["gpt-image-2", "flux-2-max"], "count": 2, "seed": 41}'{
"jobId": "<first job>",
"jobIds": ["<job 1>", "<job 2>", "<job 3>", "<job 4>"],
"batchId": "6b1f7e2a-0c4d-5e8f-9a1b-2c3d4e5f6a7b",
"groups": [
{ "model": "gpt-image-2", "jobIds": ["<job 1>", "<job 2>"], "credits": <credits> },
{ "model": "flux-2-max", "jobIds": ["<job 3>", "<job 4>"], "credits": <credits>,
"adjustments": [{ "field": "resolution", "from": "1K", "to": "2 MP", "reason": "..." }] }
],
"totalCredits": <credits>
}각 이미지에는 그 이미지를 단일 호출로 만들 때의 비용이 청구됩니다. count와 models가 없거나 count: 1이면 응답은 평소의 { "jobId" }입니다. jobId는 항상 첫 번째 작업이므로, 이 값을 읽는 코드는 계속 작동합니다.
- 크레딧: 일괄 처리 전체 아니면 아무것도. 무엇이든 만들기 전에 잔액을 일괄 처리의 합계와 비교합니다. 부족하면 라우트는
required를 합계로 설정해402 insufficient_credits로 응답하며, 아무것도 만들지 않습니다. 청구가 도중에 실패하면, 이미 만든 작업은 응답 전에 환불되고 제거됩니다. - 멱등성은 이미지마다.
Idempotency-Key: K를 쓰면 이미지 i의 키는K:i입니다. 같은 키로 요청을 반복하면 같은 작업 ID를 반환하며, 작업을 두 번 청구하지 않습니다. 일부만 만들어진 일괄 처리는 마저 완성됩니다. 같은 키로 동시에 보낸 두 번째 요청은 첫 번째를 기다리거나, 15초 뒤409 batch_in_progress로 응답하므로 다시 시도하세요. 같은 키를 다른 본문으로 반복하면422 idempotency_key_reused로 응답합니다. - 시드.
seed를 쓰면 각 모델의 이미지 i는seed + i를 받으므로, 이미지가 서로 다릅니다. - 프리셋과 함께 쓰면
count는 프리셋 요청의 이미지를 N장 만듭니다.models와 함께 쓰면 프리셋이 모델마다 한 번씩 적용되므로, 각 모델은 그 모델에 대한 편집기의 요청을 받습니다. - 나중에 일괄 처리 찾기. 일괄 처리의 모든 작업에는 같은
input_data.batchId와 그 위치인input_data.batchIndex가 담깁니다.GET /v1/jobs?batchId=<id>가 이를 나열합니다. - 무엇이든 만들기 전에 거부되는 경우(
400 validation_error):count가 1~4를 벗어날 때,models가 2개보다 적거나 4개보다 많을 때, 모델이 중복되거나 알 수 없을 때,models와provider를 함께 보낼 때, 이미지가 8장을 넘을 때입니다. 배포 환경이 제공하지 않는 모델은403 model_not_available로 응답합니다.
동영상 생성
동영상을 만드는 경로는 두 개입니다. POST /v1/generate-video는 이미지에서 애니메이션을 만듭니다. imageUrl에 시작 프레임을, 선택적으로 endFrameUrl에 마지막 프레임을 넣거나, 지원하는 모델에서는 레퍼런스만 보낼 수도 있습니다. POST /v1/text-to-video는 프롬프트만으로 클립을 만들므로 prompt가 필수입니다. provider를 생략하면 플랫폼의 기본 동영상 모델이 사용됩니다.
{
"imageUrl": "https://…/frame.png",
"provider": "seedance-2",
"prompt": "she turns toward the window",
"duration": 8,
"resolution": "720p",
"direction": { "cameraMotion": "dolly-in", "timeOfDay": "dawn" }
}응답은 { "jobId": "…" }이며, 해당되는 경우 warnings와 adjustments가 함께 옵니다. 완료된 작업에는 output_data.videoUrl이 있습니다.
속성
타입
모델에 따라 다른 규칙이 몇 가지 있습니다.
- Seedance 2 계열에서는
resolution과aspectRatio가 그대로 전달됩니다. 모델이 지원하지 않는 값은 무시되며, 거부되지 않습니다.4k와adaptive를 지원하는 모델은 Seedance 2뿐입니다. Seedance 2.5는 한 번의 호출로 최대 30초를 만들며, 시작 프레임이 있으면 항상 그 프레임의 화면 비율로 렌더링합니다. - MiniMax H3는
resolution으로2K또는768P를 받습니다. 정확히 이 표기대로 쓰세요.GET /v1/nodes/:type의providerResolutionWire에 보낼 값이 나와 있습니다. - Seedance 2와 MiniMax H3에서는 프레임과 레퍼런스를 함께 쓸 수 있습니다. 시작 프레임이나 종료 프레임과 함께 레퍼런스를 보내면, 프레임은 고정된 시작점이나 끝점이 아니라 프롬프트 속 번호가 매겨진 레퍼런스가 됩니다. Wan 3.0은 둘을 함께 보낼 수 없으므로, 대신 프레임이 레퍼런스 목록 끝에 추가되며 호출은 그대로 성공합니다.
- 레퍼런스 동영상은 비용이 더 듭니다. 레퍼런스 동영상에 요금을 매기는 모델에서는 레퍼런스 클립의 길이와 출력 길이를 더해 가격이 정해지므로, 원본 클립이 길수록 더 많은 크레딧이 예약됩니다. Wan 3.0은 출력 길이만 초 단위로 청구합니다.
characterReferences는 실제 인물의 얼굴을 유지합니다. Gemini Omni와 Gemini Omni Flash에서 쓸 수 있습니다. 각 항목은{ imageUrl, description, bodyImageUrl?, name?, voice? }로, 인물 사진, 최대 2,000자의 인물 설명, 선택 사항인 전신 사진, 최대 100자의 선택 사항인 이름입니다.referenceImageUrls는 모델에 느슨한 맥락일 뿐이지만, 이 입력은 얼굴을 유지합니다. 다른 모델은400 character_references_unsupported로 응답합니다. 같은 요청에 시작 프레임이나 종료 프레임이 있으면400 character_references_with_start_frame또는400 character_references_with_end_frame으로 응답합니다. 요청은 모델의 입력 예산인 7단위에 맞아야 합니다. 이미지는 1, 동영상은 2, 인물은 1이며bodyImageUrl이 있으면 2입니다. 예산을 넘는 요청은400 character_references_quota로 응답합니다. 인물 레퍼런스는 크레딧을 추가하지 않습니다.voice는 인물 레퍼런스에 보이스 하나를 고정합니다. 여러 클립에 걸쳐 적용되며, 형식은{ preset, description?, exampleLine? }이고preset은 Gemini 보이스 ID 중 하나입니다. 한 요청에서 서로 다른 보이스를 최대 3개까지 고정할 수 있으며, 넘으면400 character_voices_over_limit으로 응답합니다. 보이스 목록은 동영상 생성을 참고하세요.
모든 모델의 길이, 해상도, 크레딧 가격은 동영상 모델을 읽거나 GET /v1/models를 호출해 확인하세요.
레퍼런스 사용
레퍼런스는 모델이 맞춰야 할 이미지이며, 동영상에서는 클립과 오디오도 포함됩니다. URL을 나열한 단순 목록으로 보내거나, 경로가 번호와 레이블을 붙여 프롬프트에 써 넣는 구조화된 레퍼런스로 보낼 수 있습니다. 에디터가 연결된 노드를 처리하는 방식과 똑같습니다.
구조화된 레퍼런스
connectedReferences는 POST /v1/generate-image, POST /v1/generate-video, POST /v1/text-to-video, POST /v1/extend-video에서 받습니다. 각 항목은 이미지 하나를 설명합니다.
속성
타입
동영상 경로에서는 경로가 이 항목들을 번호가 매겨진 레퍼런스로 바꿉니다.
- 멘션하지 않은 레퍼런스는 모두 첨부됩니다. URL은 중복이 제거된 채 레퍼런스 목록에 추가되고,
@image_1 (reference): <label>같은 줄이 붙습니다.wired-character항목은 그 대신Use these characters:지시의 일부가 됩니다. - 목록은 번호를 매기기 전에 모델의 한도에 맞게 잘립니다. 따라서 프롬프트의 레퍼런스 번호가 전송되지 않은 이미지를 가리키는 일은 없습니다.
- 프롬프트의
{image:N:label}토큰은 첨부된 레퍼런스를 기준으로 번호가 매겨져the label from @image_N이 됩니다. {ref:<id>}와{ref:<id>:label}토큰은 내가 지정한id로 레퍼런스를 가리킵니다. 플랫폼이 목록에 번호를 매긴 뒤 토큰을 레퍼런스 번호로 바꾸므로, 번호를 직접 계산할 필요가 없습니다. 목록의 번호는 이 순서로 매겨집니다. 단순 목록인referenceImageUrls가 먼저 오고, 그다음 멘션하지 않은 캐릭터, 그다음 나머지 항목이 내가 보낸 순서대로 옵니다. 한도를 넘었거나 모델이 레퍼런스를 받지 않아서 레퍼런스가 첨부되지 않은 토큰은 레이블로, 그다음defaultName으로, 그다음 빈 값으로 대체됩니다. 토큰이 원시 텍스트 그대로 모델에 전달되는 일은 없습니다.- 레퍼런스 ID 목록인
referenceOrder필드는 레퍼런스 순서를 바꾸고 그에 맞게 번호를 다시 매깁니다.POST /v1/generate-image도 이 필드를 받습니다.
connectedReferences는 이미지만 전달합니다. referenceVideoUrls와 referenceAudioUrls는 단순 목록으로 유지됩니다. connectedReferences를 생략하면 경로는 이전과 같이 동작합니다. 내 prompt와 referenceImageUrls가 그대로 전송됩니다.
POST /v1/generate-image와 MCP 도구 generate_image에서 prompt의 {image:N} 또는 {image:N:label} 토큰은 N번째 레퍼런스 이미지를 가리킵니다.
- 연결된 레퍼런스가 없으면 N은 단순 목록인
referenceImageUrls를 순서대로 셉니다. 따라서{image:1}은 내가 보낸 첫 번째 URL입니다. 다른 레퍼런스가describedReferences뿐인 요청도 마찬가지입니다. connectedReferences항목이 하나 이상 있으면 N은connectedReferences항목을 순서대로 셉니다. 같은 요청의 단순 목록referenceImageUrls도 연결된 레퍼런스보다 앞에 첨부되지만, 그 이미지를 가리키는 토큰은 없습니다. 프롬프트에서 사진을 지칭하려면 그 사진을connectedReferences항목으로 보내세요.- 해당 위치에 이미지가 없는 토큰은 레이블로 바뀝니다.
{image:2:person}은person이 됩니다. 레이블이 없는{image:2}는 빠집니다.
편집기와 워크플로 실행도 같은 방식으로 토큰에 번호를 매깁니다. 텍스트 노드처럼 중간 노드를 거쳐 이미지 생성 노드에 도달한 이미지도 전송되고 번호가 매겨집니다.
이미지 레퍼런스를 받는 동영상 모델
| 모델 계열 | 이미지 레퍼런스 |
|---|---|
| Seedance 2 계열 | 최대 9개 |
| HappyHorse Ref2V | 최대 9개 |
| Gemini Omni, Kling 3 Omni, Grok Imagine 이미지로 동영상 만들기 | 최대 7개 |
| VEO 3.1 Fast와 VEO 3.1 Lite | 최대 3개 |
그 밖의 모델에서는 {image:N} 토큰이 레이블로 줄어들고 아무것도 첨부되지 않습니다. VEO 3.1 Quality는 목록에 없습니다. veo3로 보낸 레퍼런스는 무시되며, 실행에는 프레임이 사용됩니다.
레퍼런스만으로 동영상 만들기
POST /v1/generate-video에서는 보내는 레퍼런스 종류 중 하나 이상을 모델이 지원하면 시작 프레임이 선택 사항입니다. 예를 들어 Kling 3 Omni에 referenceImageUrls만 보내는 경우입니다. VEO 3.1 Fast나 Lite에서 레퍼런스만으로 실행하면 자동으로 레퍼런스 모드로 바뀌므로 generationType이 필요하지 않습니다. 모델이 사용할 수 없는 레퍼런스 종류는 포함되지 않습니다. 이미지만 받는 모델에 오디오 레퍼런스만 보내면 400으로 거부됩니다.
종료 프레임만 보내는 요청은 종료 프레임을 레퍼런스에 포함하는 모델에서 받아들여집니다. Seedance 2 계열, MiniMax H3, Wan 3.0이 해당됩니다. referenceImageUrls에 같은 이미지를 반복하지 말고 endFrameUrl을 한 번만 보내세요. @nodaro/shared 패키지가 videoProviderFoldsLoneEndFrame(provider)를 내보내므로, 내 인터페이스에서도 같은 규칙을 쓸 수 있습니다.
요청에 시작 프레임도, 레퍼런스 모드도, 모델이 쓸 수 있는 레퍼런스도 없으면 모델에 따라 응답이 다릅니다.
- Kling 3 Omni, HappyHorse Ref2V, Hailuo 2.3처럼 텍스트만으로 동영상을 만들 수 없는 모델은
400 image_required로 응답합니다. 메시지에서 레퍼런스로 대신할 수 있는지 알려 줍니다. 이런 모델이 무엇인지는GET /v1/models가 기준입니다. - 그 밖의 모든 모델은
400 validation_error로 응답하며, 프롬프트만으로 클립을 만들려면POST /v1/text-to-video를 사용하라고 안내합니다.
동영상 연장
POST /v1/extend-video는 provider: "seedance-2-extend"일 때만 connectedReferences와 referenceImageUrls를 받습니다. 다른 연장 모델은 이 필드들을 400으로 거부합니다. 원본 클립의 마지막 프레임이 내 이미지 다음에 레퍼런스 자리 하나를 차지하므로 한도는 내 이미지 8개이며, 그래서 내 번호가 밀리지 않습니다. 원본의 마지막 2초는 @video_1로 전달되며 연장 요금에 이미 포함되어 있으므로, 레퍼런스 이미지는 크레딧을 추가하지 않습니다. 동영상 연장에는 direction과 subject가 없습니다. 프롬프트가 이미 룩이 정해진 클립을 이어 가기 때문입니다.
설명으로 지정하는 레퍼런스
describedReferences는 설명할 수는 있지만 아직 이미지가 없는 대상, 예를 들어 스크립트 속 배역에 이름을 붙입니다. POST /v1/generate-image, /v1/generate-video, /v1/text-to-video, /v1/extend-video는 { name, description } 항목을 최대 10개 받습니다. 이름은 최대 80자, 설명은 최대 2,000자입니다.
- 아무것도 첨부되지 않습니다. 설명으로 지정한 레퍼런스는 레퍼런스 자리를 차지하지 않으며, 다른 레퍼런스의 번호도 바뀌지 않습니다.
- 모델에는 텍스트 한 줄로 전달됩니다. 형식은
<Name> — <description>.입니다. 프롬프트에 이름을 그대로 쓰세요. 예를 들어Natalie walks down the pier.처럼 쓰면, 이 줄이 Natalie가 누구인지 모델에 알려 줍니다. 이 레퍼런스에는@멘션을 쓰지 마세요. 그 문법은 첨부된 이미지를 가리킵니다. - 단독으로도 작동합니다.
connectedReferences없이describedReferences만 보내도 됩니다. 캐릭터가 생기기 전에 이야기를 먼저 쓰는 경우가 대표적입니다. - 이름이나 설명이 없는 항목은 제외되며, 반복된 이름은 한 번만 쓰입니다. 설명으로 지정한 레퍼런스에는 URL이 없으므로 모든 연장 모델이 받습니다.
실행별 설명과 캡션
connectedReferences항목의descriptionOverride필드는 이번 실행에만 해당하는 레퍼런스의 정체를 최대 2,000자로 알려 줍니다. 레퍼런스의 지시에 이미 있는 설명 자리를 저장된 설명보다 먼저 채웁니다. 지시에 설명이 없으면 한 줄을 추가하므로, 모델은 이 내용을 두 번이 아니라 한 번만 듣습니다.referenceVideoCaptions와referenceAudioCaptions필드는POST /v1/generate-video와/v1/text-to-video에서 레퍼런스 클립과 오디오를 각각 최대 500자로 설명합니다. 인덱스가 맞춰져 있어referenceVideoCaptions[0]은referenceVideoUrls[0]을 설명합니다. 각 캡션은@video_1: <caption>.같은 줄이 되며, 빈 항목은 정렬을 깨지 않고 클립 하나를 건너뜁니다.
프롬프트에서 레퍼런스 멘션
POST /v1/generate-image에서는 레퍼런스를 끝의 목록에 두는 대신 문장 안에 넣을 수 있습니다. @<name-slug>:<index>라고 쓰거나, 가져올 요소를 지정하려면 @<name-slug>:<index>:<role>이라고 쓰세요.
{
"prompt": "a wide shot of @nessie:1 rising beside @dock:2:material",
"connectedReferences": [
{ "id": "cr-1", "defaultName": "Nessie", "source": "wired-creature", "url": "https://…/nessie.png" },
{ "id": "ob-1", "defaultName": "Dock", "source": "wired-object", "url": "https://…/dock.png" }
]
}모델은 “레퍼런스 이미지 A의 크리처가 레퍼런스 이미지 B의 재질 옆에서 솟아오르는 와이드 샷”이라는 뜻의 영어 문장 a wide shot of the creature from reference image A rising beside the material from reference image B를 받습니다.
- 슬러그는
defaultName에서 만들어집니다. 소문자로 바꾸고, 그 밖의 문자가 이어진 부분은 각각-하나로 바꿉니다.Old Town은old-town이 됩니다. 따로 설정할 슬러그 필드는 없습니다. - 인덱스는 멘션과 레퍼런스를 짝짓는 데만 쓰입니다. 프롬프트에는 쓰이지 않으며, 레퍼런스 번호는 플랫폼이 직접 매깁니다.
- 역할은 이미지(
manual과wired-image)의 경우object,person,face,clothes,background,style,pose,texture입니다. 크리처는creature,anatomy,markings,pose,color,style을 받습니다. 오브젝트는object,shape,material,color,texture,style을 받습니다. 그 밖의 한 단어는 쓴 그대로 전달됩니다. 역할이 없으면 항목의defaultRole이 적용됩니다. ~lock과~nolock접미사를@town:1:background~lock처럼 멘션 뒤에 붙이면, 그 멘션에서 해당 레퍼런스의 외모 고정을 켜거나 끕니다.- 이름은 캐릭터, 장소, 이미지, 크리처, 오브젝트 순서로 해석됩니다. 캐릭터와 이미지가 같은 이름을 쓰면 캐릭터를 뜻하며, 같은 종류 안에서는 먼저 일치하는 항목이 우선합니다.
- 슬러그가 숫자로 시작하는 이름은 멘션할 수 없습니다. 예를 들어
3D Render는3d-render가 됩니다. 멘션하려면 레퍼런스 이름을 바꾸세요. 모델의 한도를 넘은 레퍼런스를 멘션하면 그대로 텍스트로 남습니다. - 레퍼런스를 멘션하면 그 레퍼런스가 이동합니다. 끝의 목록에서 내가 입력한 위치로 옮겨지고, 그 뒤에 오는 레퍼런스들의 글자가 문장 순서에 맞게 바뀝니다. 크리처나 오브젝트의 경우, 멘션은 끝에 추가될 줄도 대체합니다.
레퍼런스 고정
POST /v1/generate-image의 referenceLock은 장면 앞에 Nodaro의 검증된 레퍼런스 충실도 문구를 추가합니다.
| 값 | 추가 내용 | 용도 |
|---|---|---|
standard | 레퍼런스에 보이는 것만 사용하고, 닮은 모습을 유지하고, 레퍼런스를 함께 구성하라는 지시 | 여러 레퍼런스로 만드는 구성 |
multi-person | 위와 같은 지시에, 얼굴을 절대 바꾸거나 섞지 말라는 규칙을 더한 것 | 한 샷에 얼굴이 둘 이상인 경우 |
텍스트가 아니라 ID를 보냅니다. 문구는 플랫폼이 관리하며, 클라이언트를 업데이트하지 않아도 개선됩니다. 필드를 생략하면 고정이 추가되지 않습니다.
ID로 지정하는 시네마틱 연출
direction은 카메라, 조명, 룩을 문장 대신 피커 ID로 설명합니다. POST /v1/generate-image, POST /v1/generate-video, POST /v1/text-to-video가 이 필드를 받습니다. Nodaro가 ID마다 검증된 자체 문구를 프롬프트에 써 넣으므로, 저장된 요청은 클라이언트가 쓴 텍스트에 고정되지 않고 시간이 지나며 개선된 문구를 반영합니다.
{
"prompt": "a knight on a hill",
"provider": "nano-banana-pro",
"direction": {
"shotSize": "wide-shot",
"lens": "wide-24mm",
"lightingStyle": "rembrandt",
"style": "anime",
"mood": ["happy", "joyful"]
}
}키와 ID의 출처
각 키는 크리에이티브 컨트롤 피커의 필드입니다. 유효한 ID는 에디터의 피커가 쓰는 것과 같은 카탈로그인 GET /v1/picker-catalogs/<picker>에서 가져오세요.
| 키 | 피커 | 이미지 | 동영상 |
|---|---|---|---|
shotSize, angle, coverage, composition, vantage | 프레이밍 | 예 | 예 |
pose | 포즈 | 예 | 예 |
compositionEffect | 컴포지션 효과 | 예 | 예 |
cameraFormat | 카메라 / 필름 | 예 | 예 |
lens | 렌즈 | 예 | 예 |
aperture, shutterSpeed, isoValue | 노출 설정 | 예 | 아니요 |
timeOfDay, lightingStyle, lightingDirection, lightingRatio, colorTemperature | 조명 | 예 | 예 |
colorLook | 색감 / 룩 | 예 | 예 |
atmosphere | 대기 효과 | 예 | 예 |
postProcess | 후처리 효과 | 예 | 아니요 |
style | 스타일 | 예 | 예 |
mood | 분위기 | 예 | 예 |
aesthetic | 감성 | 예 | 예 |
photoGenre | 사진 장르 | 예 | 아니요 |
photographer | 사진작가 | 예 | 아니요 |
renderQuality | 렌더링 품질 | 예 | 아니요 |
setting | 배경 | 예 | 예 |
era | 시대 | 예 | 예 |
backdrop | 배경막 | 예 | 예 |
cameraMotion | 카메라 모션 | 아니요 | 예 |
actionFx | 액션 효과 | 아니요 | 예 |
temporalSpeed, temporalFreeze, temporalDirection, temporalShutter | 시간 표현 | 아니요 | 예 |
transition | 전환 | 아니요 | 예 |
loopSubject | 루프 소재 | 아니요 | 예 |
경로에 해당하지 않는 키는 받아들여지지만 아무것도 추가하지 않습니다. 따라서 하나의 룩 ID 맵을 이미지 경로와 동영상 경로에 그대로 보낼 수 있습니다.
값과 한도
- ID 하나 또는 목록. 다중 선택 키인
mood,aesthetic,photographer,atmosphere,postProcess,composition,lightingStyle은 각자의 한도까지 받으며, 초과한 ID는 제외됩니다. 단일 선택 키에 목록을 보내면 첫 번째 항목이 사용됩니다. - 두 가지 한도는 거부됩니다. 한 키에 항목이 8개를 넘는 경우와 ID가 100자를 넘는 경우이며,
400 validation_error로 응답합니다. - 없는 것과 빈 것은 다릅니다. 키가 없으면 힌트가 없다는 뜻이지, 기본값을 쓰라는 뜻이 아닙니다. 빈 문자열이나 빈 목록은 아무것도 추가하지 않습니다.
- 내 팔레트와 무드보드는 절대 건너뛰지 않습니다.
/v1/generate-image에서는colorLook이"palette:<id>"도 받고,style이"moodboard:<id>"도 받습니다. 내 팔레트와 무드보드를 참고하세요. - 알 수 없는 키와 ID는 거부되지 않고 건너뜁니다. 이전 서버에 새 클라이언트가 요청하면 오류 대신 힌트가 줄어들 뿐이므로, 새 키를 보내는 클라이언트보다 서버를 먼저 업데이트하세요.
- 사용자 지정 카탈로그 팩. 자체 카탈로그 팩을 등록한 배포 환경에서는
GET /v1/picker-catalogs가 팩이 추가한 ID를 나열합니다. 이 ID는 받아들여지지만direction에 문구를 추가하지 않습니다.
문구가 들어가는 위치
구절은 내 프롬프트 뒤의 [style] 섹션에 추가됩니다. 필름 줄에는 cameraFormat, colorLook, style, era가 들어가고, 장면 줄에는 나머지 룩 키가 들어갑니다.
a knight on a hill
[style]:
<film line>
<scene line>- 한 줄 안의 순서는 키를 보낸 순서가 아니라 플랫폼이 정한 고정 순서를 따릅니다. 두 키가 같은 구절을 만들면 한 번만 쓰입니다.
- 비어 있는 줄은 생략됩니다. 아무 키도 내용을 추가하지 않으면 섹션 자체가 없으며, 내
prompt가 그대로 모델에 전달됩니다. - 동영상 경로에서는 모션 키가 다르게 동작합니다.
cross-dissolve같은 짧은 전문 용어를 추가하며, 모션은 샷의 일부이므로 본문의 내 문장 뒤에 남습니다.cameraMotion이 가장 먼저 옵니다.[style]섹션에는 룩 키만 들어갑니다.
프롬프트가 너무 길 때
모델마다 받을 수 있는 프롬프트 길이가 다릅니다. direction을 모두 채우면 그것만으로도 작은 한도를 넘을 수 있습니다. 예를 들어 이미지 쪽의 Seedream은 3,000자, 동영상 쪽의 Kling은 1,000자입니다. 이런 경우 Nodaro는 고정 순서의 끝에서부터 연출 구절을 하나씩 제거해 프롬프트가 한도에 들어가게 합니다. 그 전에 다른 것이 제거되는 일은 없습니다.
- 피사체 구절은 모든 연출 구절이 제거된 뒤에만 제거됩니다.
- 내 문장, 내 레퍼런스와 이를 연결하는 구문,
@멘션,Style:과Avoid:줄은 항상 남습니다. - 힌트가 하나도 남지 않았는데도 프롬프트가 한도에 들어가지 않을 때만 텍스트 끝이 잘리고
...이 붙습니다.
동영상 경로에서는 경로가 추가하는 레퍼런스 지시도 예산에 포함되며, 이 지시는 절대 제거되지 않습니다. 네거티브 프롬프트 설정이 없는 모델에서는 내 negativePrompt가 Avoid: 줄로 추가되고 이 줄의 공간이 먼저 확보되므로, 긴 네거티브 프롬프트는 내 문장이 아니라 힌트 구절을 줄입니다. 선택 사항인 injectCharacterContext 텍스트는 이 단계 이후에 추가되며 예산에 포함되지 않습니다.
작업에는 무슨 일이 있었는지 기록됩니다. input_data.prompt는 모델이 받은 내용이고, input_data.userPrompt는 내가 보낸 텍스트이며(direction만 보냈다면 빈 문자열), input_data.direction은 내가 보낸 그대로의 ID입니다.
노드에 저장된 연출
저장된 워크플로의 이미지 생성 노드는 API, MCP 또는 워크플로를 만드는 앱이 써 넣은 같은 direction 객체를 데이터에 담을 수 있습니다. 에디터는 모든 실행과 최종 프롬프트 미리보기에서 이를 반영합니다. 저장된 ID는 연결된 프레이밍, 조명, 스타일 피커에 더해집니다. 연결된 힌트가 먼저 오고, 그다음 저장된 ID가 옵니다. 프리셋과 워크플로 내보내기는 노드의 나머지 데이터와 함께 ID도 보존합니다.
내 팔레트와 무드보드
팔레트는 내가 만든 212개 색상의 모음이고, 무드보드는 내가 만든 112개 레퍼런스 이미지와 설명의 모음입니다. 한 번 저장해 두면, 기본 제공 색감이나 스타일을 고르는 것과 같은 방식으로 어떤 POST /v1/generate-image 실행이든 이것으로 조정할 수 있습니다. 모든 에디션에서 작동합니다. 편집기에서 하는 일은 팔레트와 무드보드를 읽어 보세요.
| 팔레트 | 무드보드 | |
|---|---|---|
| 담는 내용 | 1#RRGGBB 형식의 색상 2 | 이름, 최대 1,000자의 선택 설명, 이미지 1~12개 { url, caption? } |
| 사용 방법 | direction.colorLook: "palette:<id>" | direction.style: "moodboard:<id>" |
| 실행에 들어가는 것 | 모든 색상이 일상적인 이름과 16진수 코드로 프롬프트에 들어감 | 설명이 프롬프트에 들어가고, 이미지가 스타일 레퍼런스로 들어감 |
{
"prompt": "a lighthouse on a cliff at dusk",
"provider": "nano-banana-pro",
"referenceImageUrls": ["https://example.com/my-lighthouse.png"],
"direction": {
"colorLook": "palette:0f9e8d7c-6b5a-4c3d-9e2f-1a2b3c4d5e6f",
"style": "moodboard:1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"lightingStyle": "rembrandt"
}
}팔레트와 무드보드 관리
| 메서드 | 경로 | 본문 또는 쿼리 | 하는 일 |
|---|---|---|---|
GET | /v1/palettes | limit(1~100, 기본값 50), cursor | 내 팔레트를 최신순으로 반환합니다: { data, nextCursor }. |
POST | /v1/palettes | { name, colors, description? } | 팔레트를 만듭니다. 201로 응답합니다. 색상은 대문자로 저장됩니다. |
GET | /v1/palettes/:id | 없음 | 팔레트 하나입니다. |
PATCH | /v1/palettes/:id | name, colors, description 중 아무것이나 | 보낸 필드만 씁니다. description: null은 설명을 지웁니다. |
DELETE | /v1/palettes/:id | 없음 | { success: true }를 반환합니다. |
GET | /v1/moodboards | limit, cursor | 내 무드보드를 최신순으로 반환합니다: { data, nextCursor }. |
POST | /v1/moodboards | { name, description?, images } | 무드보드를 만듭니다. 201로 응답합니다. |
GET | /v1/moodboards/:id | 없음 | 무드보드 하나입니다. |
PATCH | /v1/moodboards/:id | name, description, images 중 아무것이나 | 보낸 필드만 씁니다. images는 이미지 전체를 교체합니다. |
DELETE | /v1/moodboards/:id | 없음 | { success: true }를 반환합니다. 연결된 이미지는 건드리지 않습니다. |
- 무드보드 이미지는 링크이며, 사본이 아닙니다. 각
url은localhost나 사설 네트워크가 아닌http또는https주소이며, 최대 2,048자입니다. 캡션은 최대 200자입니다. 내가 삭제한 이미지나 요금제의 미디어 보존 기간에 따라 제거된 이미지는 무드보드에서도 사라집니다. nextCursor는 불투명한 토큰입니다. 다음 페이지를 받으려면cursor로 다시 보내세요. 마지막 페이지에서는null입니다.- 나만 쓸 수 있습니다. 다른 사람의 팔레트나 무드보드는 존재하지 않는 것과 똑같이
404로 응답합니다. - 범위: OAuth 앱 토큰은 읽기에
assets:read, 쓰기에assets:write가 필요합니다. 쓰기는 분당 30회로 제한됩니다. - 같은 라우트가 SDK(
client.palettes,client.moodboards), CLI(nodaro palettes,nodaro moodboards), MCP 도구list_palettes,save_palette,list_moodboards,save_moodboard에 있습니다. MCPgenerate_image는palette_id와moodboard_id를 받습니다. MCP에는 삭제 기능이 없습니다.
실행에 들어가는 것
- 팔레트는
[style]섹션의 필름 줄, 기본 제공 색감이 들어갈 자리에color palette: <name> <hex>, …를 추가합니다. 모델은 이름이 붙은 색상을 더 잘 따르므로, 각 색상에는 가장 가까운 일상적인 이름과 정확한 16진수 코드가 붙습니다. 팔레트의 메모는 프롬프트에 추가되지 않습니다. - 무드보드는 기본 제공 스타일이 들어갈 자리에 설명을 한 줄로 추가합니다. 설명이 없으면 이미지만 들어갑니다.
- 다른 모든 연출 구절처럼, 프롬프트가 모델의 한도를 넘으면 이 구절이 먼저 제거됩니다.
- 무드보드의 이미지는 레퍼런스 이미지로 모델에 보내지며, 각 이미지에는 피사체나 구도가 아니라 색상, 빛, 질감, 분위기를 가져가라고 모델에 알려 주는 짧은 줄이 붙습니다. 이미지에 캡션이 있으면 캡션도 추가됩니다.
- 내 레퍼런스가 먼저입니다. 내가 직접 보낸 이미지는 자리와 번호를 유지합니다. 무드보드의 이미지는 그 뒤에 남은 레퍼런스 자리만 채웁니다.
- 모델이 받는 수를 절대 넘지 않습니다. 들어가지 않는 이미지는 무드보드의 순서대로 보내지 않으며, 응답에
moodboard_images_not_sent경고가 담깁니다. 레퍼런스 이미지를 받지 않는 모델은 이미지를 하나도 받지 않으며, 설명은 그래도 들어갑니다. - 가격. 이미지는 레퍼런스로 계산되므로, 실행은 그만큼의 레퍼런스가 있는 다른 실행과 같은 가격이 매겨집니다. 실행 전에 확인하는 가격과 예약되는 가격은 같은 이미지, 곧 실제로 보내는 이미지를 셉니다.
aspectRatio: "auto"는 실행이 보내는 첫 번째 이미지의 비율을 유지합니다. 내 이미지를 보내지 않으면 무드보드의 첫 번째 이미지입니다.- 작업은 사본이 아니라 ID를 저장합니다. 다시 실행하면 그 시점의 팔레트나 무드보드를 사용합니다.
오류와 한도
| 상태 | 코드 | 경우 |
|---|---|---|
400 | validation_error | 뒤에 uuid가 없는 palette: 또는 moodboard:, colorLook 이외의 키에 쓴 팔레트, style 이외의 키에 쓴 무드보드, 같은 키에서 다른 값과 함께 쓴 경우, 또는 presetId도 함께 보낸 요청입니다. field가 키를 알려 줍니다. |
404 | direction_not_found | 팔레트나 무드보드가 내 것이 아니거나, 더 이상 존재하지 않습니다. field는 direction.colorLook 또는 direction.style입니다. |
두 응답 모두 무엇이든 만들거나 청구하기 전에 옵니다. 팔레트와 무드보드는 generate-image에서만 작동합니다. POST /v1/generate-video와 POST /v1/text-to-video는 이를 받으면 400 validation_error로 응답합니다. 저장된 워크플로에서 direction이 팔레트나 무드보드를 가리키는 이미지 생성 노드는 워크플로가 실행될 때마다 그 내용을 새로 읽습니다.
ID로 피사체 지정
subject는 같은 개념을 샷에 등장하는 대상에 적용합니다. 인물, 인물의 스타일링, 프레임 속 소품입니다. POST /v1/generate-image, POST /v1/generate-video, POST /v1/text-to-video가 이 필드를 받습니다.
{
"prompt": "on the seawall at dusk",
"provider": "nano-banana-pro",
"subject": {
"type": "woman",
"age": "age-30s",
"ethnicity": "east-asian",
"hairBase": "base-short-straight",
"makeup": "makeup-smoky",
"outerwear": "outerwear-trench",
"heldProp": "smartphone"
}
}- 키는 인물 피커와 스타일링 피커의 필드입니다.
type,age,ethnicity,faceShape,hairColor,skinTone,makeup,outfit,outerwear,footwear등이 있으며, 소품 키 세 개가 더 있습니다.heldProp(손에 든 소품),material(재질),animal(동물)입니다.GET /v1/picker-catalogs/person과/styling이 모든 필드와 ID를 나열합니다. subject와direction은 겹치지 않습니다. 키가 서로 다르므로 하나의 선택이 구절 두 개를 추가하는 일은 없습니다.pose는direction에 속합니다.customAge만 숫자입니다. 나이를 세 단위로 정확히 지정하려면"age": "age-custom"과 함께"customAge": 34를 보내세요. 값은 반올림되며 0~120 사이로 유지됩니다.- 목록에는 키마다 한도가 있습니다.
jewelry,wardrobeState,distinctiveFeature는 ID 3개를 받습니다.ethnicity,regionalAesthetic,hairColor,eyeColor,lipState,eyeState,skinTexture,hairState,heldProp,material은 2개를 받습니다.animal을 포함한 나머지 키는 1개를 받습니다. 초과한 ID는 제외됩니다. - 거부되는 한도: 한 키에 항목이 8개를 넘거나, ID가 100자를 넘거나, 키가 128개를 넘거나, 키 이름이 64자를 넘으면
400 validation_error로 응답합니다. - 알 수 없는 키는 제거되고 알 수 없는 ID는 건너뜁니다. 작업의
input_data.subject에 실제로 사용된 ID가 정확히 기록됩니다. - 피사체 구절은 내 문장의 일부입니다. 연출 구절보다 앞에 오며,
[style]섹션에는 절대 들어가지 않습니다. 인물은 한 구절, 스타일링은 또 다른 구절이 되며, 겹치는 선택은 한 번만 쓰입니다. 이미지 경로에서는 선택마다 전체 구절이 추가되고, 동영상 경로에서는 시작 프레임이 이미 피사체를 보여 주므로 짧은 용어가 추가됩니다.
생성 세션
이미지, 동영상, 음악 제작 도구처럼 API로 만든 창작 도구는 작업을 내 프로젝트 안의 세션에 보관할 수 있습니다. 프로젝트는 세션을 담고, 각 세션은 항목을 담습니다. 모든 기기에서 같은 세션이 보이며, 앱은 자체적으로 아무것도 저장하지 않습니다.
# 1. Open a session, optionally inside one of your projects.
SESSION=$(curl -s -X POST "https://app.nodaro.ai/v1/sessions" \
-H "Authorization: Bearer $NODARO_API_KEY" -H "Content-Type: application/json" \
-d '{"projectId": "<project-id>", "tool": "create-image", "title": "Beach"}' | jq -r .data.id)
# 2. Generate inside it: send sessionId beside the usual settings.
curl -s -X POST "https://app.nodaro.ai/v1/generate-image" \
-H "Authorization: Bearer $NODARO_API_KEY" -H "Content-Type: application/json" \
-d "{\"prompt\": \"a red car\", \"sessionId\": \"$SESSION\", \"origin\": \"create-image\"}"
# 3. Read the session's feed, newest first.
curl -s "https://app.nodaro.ai/v1/sessions/$SESSION/items" -H "Authorization: Bearer $NODARO_API_KEY"| 메서드 | 경로 | 하는 일 |
|---|---|---|
POST | /v1/sessions | 세션을 만듭니다. 본문: tool(필수: 소문자, 숫자, 대시로 최대 40자), projectId(선택, 내 프로젝트 중 하나), title(선택, 최대 200자). |
GET | /v1/sessions | 내 세션을 최신순으로 반환합니다. 쿼리: projectId, tool, includeArchived=true, limit(1~100, 기본값 50), cursor. 각 세션에는 itemCount와, 가장 최근에 완료된 항목의 이미지인 previewUrls가 최대 4개 담깁니다. |
GET | /v1/sessions/:id | 세션 하나입니다. |
PATCH | /v1/sessions/:id | title(null은 제목 없음)과 archived를 바꿉니다. 세션의 프로젝트는 만들 때 정해지며 바뀌지 않습니다. |
DELETE | /v1/sessions/:id | 세션과 그 링크를 삭제합니다. 작업은 유지되며, 그 작업을 담은 다른 모든 세션에 그대로 남습니다. |
GET | /v1/sessions/:id/items | 피드: 세션에서 만들었거나 가져온 모든 항목을 최신순으로 반환합니다. 각 항목에는 jobId, addedAt, imported, importedFrom { sessionId, title }, 그리고 GET /v1/jobs/:id가 반환하는 형식의 job이 있습니다. limit과 cursor로 페이지를 넘기세요. |
POST | /v1/sessions/:id/items | { "jobIds": [...] }로 작업 1~100개를 사본이 아닌 링크로 가져옵니다. ID 중 하나라도 내 작업이 아니면 아무것도 가져오지 않습니다(404). { imported, requested }로 응답합니다. |
DELETE | /v1/sessions/:id/items/:jobId | 이 세션에서 항목 하나를 제거합니다. 링크만 사라지며, 작업과 그 작업을 담은 다른 모든 세션은 그대로 남습니다. |
- 작업을 만드는 모든 라우트는
sessionId를 받습니다.generate-image,generate-video,text-to-video,text-to-speech,generate-music같은 라우트입니다. 내 세션 중 하나여야 하며, 요청과 같은 워크스페이스 쪽에 있어야 합니다. 그렇지 않으면 요청은404로 응답하며, 아무것도 만들거나 청구하지 않습니다. 워크플로 실행은 절대 세션에 정리되지 않습니다. - 작업의
session_id는 작업을 만들 때 한 번 설정됩니다.GET /v1/jobs/:id와GET /v1/jobs에 표시됩니다. - 작업을 만드는 모든 라우트는 앱 slug인
origin도 받습니다. 작업은 이를input_data.origin에 저장하며,GET /v1/jobs?origin=으로 필터링할 수 있습니다. slug가 아닌 값은400 validation_error로 응답하며, 아무것도 만들거나 청구하지 않습니다. - 작업을 다른 프로젝트로 옮기려면, 그 프로젝트의 세션으로 항목을 가져온 다음 이전 세션에서 제거하세요. 제거는 아무것도 삭제하지 않습니다.
DELETE /v1/jobs/:id로 작업을 삭제하면 모든 세션에서 사라집니다. - 범위: OAuth 앱 토큰으로 세션을 읽으려면
jobs:read가 필요합니다. 만들기, 편집, 가져오기, 제거에는assets:write가 필요하며, 생성에sessionId를 보낼 때도 마찬가지입니다. 일부 워크플로로 제한된 개인 API 토큰은 이 라우트에 접근할 수 없습니다.
노드 조회
GET /v1/nodes는 서버가 아는 모든 노드 타입을 나열하고, GET /v1/nodes/:type은 하나를 반환합니다. 둘 다 공개되어 있어 토큰이 필요 없고, 5분 동안 캐시됩니다. 알 수 없는 타입에는 404 not_found로 응답합니다.
curl -s https://app.nodaro.ai/v1/nodes/generate-image | jq .dataconst { data: nodes } = await client.nodes.list()
const imageNodes = nodes.filter((n) => n.category === 'ai-image')
const { data: generateImage } = await client.nodes.get('generate-image')
console.log(generateImage.providers)nodaro nodes list --category ai-image
nodaro nodes get generate-image{
"data": {
"type": "generate-image",
"label": "Generate Image",
"category": "ai-image",
"description": "Generate an image from a text prompt using an AI provider.",
"outputType": "image",
"creditCost": "3-682",
"providers": ["nano-banana-pro", "gpt-image-2", "gpt-image-2-5-flare", "seedream-5-pro", "z-image"],
"capabilities": ["supports-reference-image", "supports-aspect-ratio"],
"inputSchema": {
"fields": [
{ "key": "prompt", "type": "text", "required": true },
{ "key": "provider", "type": "select", "options": ["nano-banana-pro", "gpt-image-2"] },
{ "key": "aspectRatio", "type": "select" },
{ "key": "promptPrefix", "type": "text" },
{ "key": "promptSuffix", "type": "text" }
]
}
}
}| 필드 | 의미 |
|---|---|
type | 노드 타입입니다. 대부분의 노드 타입에서는 경로이기도 합니다. POST /v1/<type>입니다. |
label, category, description | 에디터가 노드에 붙이는 이름과 분류입니다. |
outputType | text, image, video, audio, data 또는 none입니다. |
creditCost | 노드의 크레딧 비용 또는 그 범위입니다. 실행에 청구되는 가격으로, 노드의 실행 버튼에 표시되는 수치와 같습니다. 동영상 자르기, 동영상 루프, 동영상 합치기에서는 5초 단위 한 단계의 가격이며, 실행 비용은 단계 수에 따라 정해집니다. Nodaro Cloud 전용이며, 크레딧이 없는 에디션에서는 생략됩니다. |
providers | 노드가 provider에 받는 모델 ID입니다. |
capabilities | supports-reference-image 같은 기능 플래그입니다. |
inputSchema.fields | 노드의 설정으로, 타입, 필수 여부, 옵션이 함께 있습니다. |
프롬프트를 받는 모든 노드는 promptPrefix와 promptSuffix도 나열합니다. 프롬프트 앞뒤에 추가되는 텍스트입니다. 프롬프트 앞뒤 텍스트를 참고하세요. 모델별 한도가 있는 노드에는 추가 필드가 있습니다.
| 필드 | 의미 |
|---|---|
maxDurationSec | 노드가 받는 가장 긴 길이입니다. |
sparseProviders | 세그먼트 길이가 몇 가지뿐인 모델입니다. 그 사이의 값은 가장 가까운 값으로 맞춰집니다. |
providerResolutions | 모델별 해상도입니다. 예: { "minimax-h3": ["2K", "768P"] } |
providerResolutionWire | 해상도마다 보낼 정확한 값입니다. 예를 들어 MiniMax H3의 저렴한 등급에는 768p가 아니라 768P를 보냅니다. |
soundtrack | 동영상 생성 Pro에서 서버가 원본 오디오 soundtrack 입력을 받는다는 뜻입니다. |
디스크립터는 계속 확장되므로 모르는 필드는 무시하세요. 같은 데이터로 노드 참조가 만들어집니다.
모델 조회
GET /v1/models는 종류와 제작사별로 묶은 모델 카탈로그를 반환합니다. 공개되어 있고 5분 동안 캐시되며, MCP list_models 도구가 반환하는 것과 같은 데이터입니다.
curl -s "https://app.nodaro.ai/v1/models?kind=video&mode=i2v" | jq '.totalModels'const catalog = await client.models.list({ kind: 'video', mode: 'i2v' })
for (const section of catalog.sections)
for (const family of section.families)
for (const m of family.models) console.log(m.id)nodaro models list --kind video --mode i2v응답은 { sections, recommendations, totalModels }입니다. 모든 모델은 제작사를 밝히고 제작사의 마크를 담습니다.
| 필드 | 타입 | 의미 |
|---|---|---|
maker | { id, name } | 모델을 만든 회사입니다. id는 회사마다 하나씩 바뀌지 않는 값으로, google, black-forest-labs, bytedance처럼 소문자와 하이픈으로 씁니다. 내 에셋은 이 값을 키로 삼고, 새 서버가 제작사를 추가할 수 있으므로 모르는 id도 처리하세요. 모델이 묶이는 family와 항상 같지는 않습니다. |
logoUrl | string 또는 null | 이 서버의 미디어 CDN에 있는 제작사의 공식 마크 SVG이며, 서버가 공개하지 않으면 null입니다. 그럴 때는 제작사 이름이나 첫 글자 같은 대체 표시를 그리세요. 주소는 받은 그대로 쓰세요. 파일 버전이 담겨 있으므로, 마크가 바뀌면 새 주소로 옵니다. |
MCP list_models 도구는 maker는 담지만 logoUrl은 담지 않습니다.
각 모델에는 기능(modes, features, aspectRatios, resolutions, durations), Nodaro Cloud에서의 변형별 크레딧 pricing, 짧은 promptTips, 그리고 모델 계열에 출처가 있는 프롬프트 가이드가 있을 때만 true인 doctrineCovered가 있습니다. pricing 크레딧은 실행에 청구되는 가격으로, 에디터의 실행 버튼에 표시되는 수치와 같습니다.
텍스트 음성 변환 모델이나 대화 모델 같은 음성 모델은, 음성이 길이로 가격이 매겨지는 곳에서 두 번째 pricing 행 <model>:per-100-chars를 나열합니다. 이 행은 보낸 텍스트의 100자가 시작될 때마다의 가격이며, 실행당 최소 8단위입니다. 요청은 전체로 가격을 계산한 뒤 한 번만 반올림하므로, N = max(8, ceil(characters / 100))일 때 N × 이 행의 크레딧은 상한일 뿐입니다. 정확한 가격은 식별자 <model>:per-100-chars:<N>u이며, POST /v1/credits/model-costs와 GET /v1/credits/model-cost가 이 식별자에 답합니다. 곱한 값이 아니라 이 값으로 견적을 내세요. 이 행이 없으면 모든 :<N>u 식별자도 없습니다. 모델의 고정 가격 행으로 견적을 내세요. 단위별로 가격이 매겨지는 실행의 정확한 가격을 참고하세요.
| 쿼리 | 값 | 필터 대상 |
|---|---|---|
kind | image, video 또는 audio | 한 종류의 미디어 |
mode | 예: t2i, i2v, t2v, tts, video-analysis | 한 가지 작업 유형 |
family | 제작사, 예: Google 또는 Bytedance | 한 제작사 |
featuredOnly | true | 추천 모델 |
모델 페이지에서도 같은 카탈로그를 볼 수 있습니다.
피커 값 조회
크리에이티브 컨트롤 피커에는 유효한 ID를 담은 공개 카탈로그가 있으며, 이 ID가 direction과 subject에 들어가는 값입니다.
| 메서드 | 경로 | 반환 내용 |
|---|---|---|
GET | /v1/picker-catalogs | 모든 피커입니다. nodeType, label, kind, 필드, optionCount, imageCount를 반환합니다. |
GET | /v1/picker-catalogs/:nodeType | 피커 하나의 옵션입니다. ?detail=full은 각 옵션의 description과 promptHint를 추가하고, ?category=는 단일 필드 피커를 필터링하며, ?field=는 다중 필드 피커의 필드 하나를 반환합니다. |
GET | /v1/catalogs | 배포 환경이 큐레이션한 모든 카탈로그를 한 번에 반환합니다. data는 배포 환경이 카탈로그 팩을 등록했을 때만 있습니다. |
POST | /v1/text-to-picker | “AI 채우기”입니다. 자유롭게 쓴 장면 설명에서 여러 피커의 ID를 고릅니다. llmModel과 reasoningEffort가 없으면 Claude Opus 5.5를 high로 실행하며, 채우는 피커 수와 관계없이 28크레딧이 듭니다. |
모든 옵션에는 id, label, 그리고 프롬프트에 쓰는 짧은 구문인 term이 있으며, 이미지가 있는 옵션에는 imageUrl도 있습니다. 카탈로그는 @nodaro/prompts npm 패키지에 데이터로도 포함되어 있습니다. 전체 형식은 피커 카탈로그를 읽어 보세요. 터미널에서는 nodaro pickers list, nodaro pickers get mood --full, nodaro pickers analyze "<text>"를 사용하세요.
구조화된 LLM 출력
POST /v1/llm/structured는 내가 제공한 JSON Schema에 맞춰 답을 강제하는 언어 모델 호출을 한 번 실행하고, 검증한 뒤 객체로 반환합니다. 모델 등급에 따라 크레딧으로 청구됩니다.
{
"system": "You write production plans.",
"input": "A rainy chase through Rome.",
"jsonSchema": {
"type": "object",
"properties": { "title": { "type": "string" } },
"required": ["title"]
}
}응답은 { jobId, output, usage: { inputTokens, outputTokens } }이며, output은 내 스키마의 형태를 따릅니다.
- 필드:
system,input,jsonSchema, 그리고 선택 사항인schemaName(최대 64자),llmModel,reasoningEffort,maxRetries,origin,advancedMode,temperature,maxTokens입니다.system과input은 각각 최대 100,000자이며,input은 최소 1자가 필요합니다. - 모델:
llmModel이 없으면 Gemini 3.6 Flash에서 실행됩니다. - 스키마: 루트는 일반 객체 스키마여야 하며, 최대 64KB, 깊이 20단계까지입니다.
properties,required,additionalProperties,items, 기본 타입,enum,const, 숫자와 길이의 범위,multipleOf,exclusiveMinimum,description을 사용할 수 있습니다.anyOf와oneOf는 루트 아래에서만 받아들여집니다.not,if,then,else,dependent키워드, 외부$ref, 루트의 조합 키워드는400으로 응답합니다. 루트 아래에서required분기를anyOf로 묶으면 받아들여지지만 강제되지는 않으므로, 여러 필드에 걸친 규칙은 직접 확인하세요. - 재시도:
maxRetries는 0~3이며 기본값은 2입니다. 유효하지 않은 답이 검증 오류와 함께 모델로 다시 보내지는 횟수입니다. - 샘플링:
maxTokens는 모든 호출에 적용되며 모델 자체 한도를 넘을 수 없습니다.temperature는advancedMode: true를 함께 보내지 않으면 무시되며, 이 모드는 크레딧이 한 단계 올라갑니다. 프리미엄 모델은 premium-direct 가격으로 올라갑니다. - 소요 시간: 이 호출은 동기식이며 몇 분이 걸릴 수 있습니다. 시도마다 두 실행 경로 각각에서 최대 240초가 걸릴 수 있으므로, 최악의 경우 기본
maxRetries에서 24분, 최댓값에서 32분이 걸립니다. HTTP 클라이언트의 시간 제한을 늘리거나 아래의 작업 방식을 사용하세요. SDK의 기본timeoutMs인 60초는 너무 짧습니다. - 오류:
400 validation_error,401,402,500 internal_error, 재시도를 모두 쓴 뒤의502 llm_error,503 provider_unavailable입니다.
SDK에서는 client.llm.structured(body)를 호출하세요.
작업으로 실행
POST /v1/llm/structured/jobs는 같은 본문을 받고 즉시 { jobId }로 응답합니다. GET /v1/jobs/:id/status를 폴링하세요. completed이면 output_data가 { output, inputTokens, outputTokens }이고, failed이면 error_message가 이유를 알려 줍니다. GET /v1/jobs?type=llm-structured&origin=<your app>으로 초안을 다시 찾을 수 있습니다. 추가 필드 세 개를 받습니다.
label: 작업의 표시 이름으로, 최대 120자입니다.videoUrl: 동영상에서 초안을 만듭니다. 동영상은 먼저 내가 소유한 별도 작업으로 동영상 분석 가격에 분석되며, 분석 결과가 내input에 추가됩니다. 분석이 진행되는 동안output_data.stage는analyzing이고, 그다음drafting이 됩니다.videoAnalysis: 그 분석에 쓰는{ llmModel?, selectionMode? }입니다.
POST /v1/jobs/:id/cancel로 초안을 취소하면 실행 중인 분석도 취소됩니다. 분석이 거부되면 초안에 예약된 크레딧이 모두 해제됩니다. 언어 모델 호출을 nodaro.ai로 보내는 설치에서는 작업 방식이 503 provider_unavailable로 응답하므로, 그곳에서는 동기식 호출을 사용하세요. SDK에서는 client.llm.structuredJob(body)를 호출하세요.
자주 묻는 질문
Nodaro API로 이미지를 생성하려면 어떻게 하나요?
POST /v1/generate-image에 프롬프트와 공급자를 보내세요. 응답에는 jobId가 들어 있습니다. 상태가 completed가 될 때까지 GET /v1/jobs/:id/status를 폴링한 다음 output_data.imageUrl을 읽으세요. SDK에서는 client.nodes.runAndWait가 두 단계를 모두 처리합니다.
API에서 어떤 노드를 실행할 수 있나요?
GET /v1/nodes가 나열하는 모든 노드 타입을 실행할 수 있습니다. 경로는 POST /v1/ 뒤에 노드 타입을 붙인 것이며, 노드의 설정을 JSON 본문으로 보냅니다. 일부 텍스트 노드는 /v1/llm-chat/generate처럼 더 긴 경로를 사용합니다.
direction 객체란 무엇인가요?
{ "shotSize": "wide-shot", "timeOfDay": "golden-hour" }처럼 피커 ID를 한 단계로 나열한 맵으로, 이미지나 동영상 생성과 함께 보냅니다. Nodaro가 ID마다 검증된 자체 문구를 프롬프트에 써 넣습니다. 유효한 ID는 GET /v1/picker-catalogs에서 가져옵니다.
API에서 캐릭터를 일관되게 유지하려면 어떻게 하나요?
캐릭터의 이미지를 connectedReferences에 넣고, source는 wired-character로, defaultName은 캐릭터 이름으로 보내세요. 경로가 이미지를 첨부하고 정체성 지시를 프롬프트에 써 넣습니다. 에디터와 같은 방식입니다.
조회 엔드포인트에 토큰이 필요한가요?
아니요. GET /v1/nodes, GET /v1/models, GET /v1/picker-catalogs, GET /v1/catalogs는 공개되어 있으며 5분 동안 캐시됩니다. 카탈로그 데이터만 제공하며, 내 계정에 관한 정보는 절대 노출하지 않습니다.
프롬프트와 direction ID를 합친 길이가 모델에 너무 길면 어떻게 되나요?
Nodaro는 프롬프트가 한도에 들어갈 때까지 연출 구절을 끝에서부터 하나씩 먼저 제거합니다. 피사체 구절은 마지막에 제거되며, 공간을 만들기 위해 내가 쓴 문장과 레퍼런스가 제거되는 일은 없습니다.
관련 문서
작업
워크플로
피커 카탈로그
이미지 생성
Nodaro의 AI 모델
최종 업데이트