# TypeScript SDK

> @nodaro/sdk를 설치하고 API 토큰으로 인증한 다음, Node.js, 브라우저, 엣지 런타임에서 TypeScript로 Nodaro 노드와 워크플로를 실행하세요.

Source: https://nodaro.ai/ko/docs/developers/sdk

**Nodaro TypeScript SDK**는 Nodaro REST API의 타입 지정 클라이언트이며, npm에 `@nodaro/sdk`로 게시되어 있습니다. 단일 노드와 워크플로 전체를 실행하고, 결과를 기다리고, 캐릭터, 보이스, 미디어 등을 관리합니다. Node.js, 브라우저, 엣지 런타임에서 동작하며, 모든 API 오류는 처리할 수 있는 타입 지정 오류 클래스로 전달됩니다.

## 설치
```bash
npm install @nodaro/sdk
```

- 이 패키지는 Apache-2.0 라이선스의 오픈 소스입니다. [npm의 @nodaro/sdk](https://www.npmjs.com/package/@nodaro/sdk)와 [GitHub의 소스](https://github.com/nodaroai/app.nodaro.ai/tree/main/packages/client)를 참조하세요.
- ES 모듈 빌드와 CommonJS 빌드를 TypeScript 타입 정의와 함께 제공합니다.
- Node.js 20 이상이 필요합니다. 최신 브라우저, React Native, Cloudflare Workers, Deno, Bun처럼 전역 `fetch`와 `URL`이 있는 모든 런타임에서도 실행됩니다.
- 다른 Nodaro 패키지 2개를 함께 설치합니다. 전송 타입과 모델 카탈로그를 담은 `@nodaro/shared`, 프롬프트 헬퍼를 담은 `@nodaro/prompts`입니다.
- `@supabase/supabase-js`와 `@supabase/ssr`은 선택적 피어 의존성입니다. `supabaseAuth`나 `@nodaro/sdk/supabase`를 통해 Supabase로 사용자를 로그인시키는 브라우저 앱에서만 설치하세요.
- [Nodaro CLI](https://nodaro.ai/docs/developers/cli)는 이 SDK를 기반으로 만들어졌으므로, CLI 명령과 SDK 호출은 같은 엔드포인트에 도달합니다.

## API 토큰 받기
### 토큰 만들기
[Nodaro](https://app.nodaro.ai)에 로그인하고 **설정 › API 토큰**을 열어 **토큰 만들기**를 클릭하세요.

### 한 번만 복사
토큰은 `ndr_`로 시작하며 한 번만 표시됩니다. Nodaro는 토큰의 해시만 보관하므로 잃어버린 토큰은 다시 표시할 수 없습니다. 대신 새 토큰을 만드세요.

### 코드에 넣지 않기
토큰은 `NODARO_TOKEN` 같은 환경 변수나 시크릿 관리자에 저장하세요. 클라이언트 쪽 코드에는 절대 넣지 마세요.

개인 토큰은 나로서 동작합니다. 많은 사용자가 연결하는 앱처럼 다른 사람을 대신해 동작하려면 [OAuth](https://nodaro.ai/docs/developers/oauth)를 사용하세요. 모든 방식은 [인증](https://nodaro.ai/docs/developers/sdk/auth)에서 비교합니다.

## 첫 번째 호출
```ts

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

const { data: nodes } = await client.nodes.list()
console.log(`${nodes.length} node types available`)
```

`client.nodes.list()`는 비용이 들지 않고 스코프도 필요하지 않으므로, 연결을 처음 테스트하기에 좋습니다. 셀프 호스팅 설치에서는 `baseUrl`을 내 인스턴스의 주소로 설정하세요. Nodaro와 같은 출처에서 제공되는 브라우저 앱에서는 빈 문자열을 사용하세요. `createClient`의 모든 옵션은 [클라이언트](https://nodaro.ai/docs/developers/sdk/client)에 나와 있습니다.

## 이미지를 만든 다음 동영상 만들기
```ts
const image = await client.nodes.runAndWait("generate-image", {
prompt: "A snow leopard resting on a rock at sunrise",
provider: "nano-banana-2",
})
console.log(image.imageUrl)

const video = await client.nodes.runAndWait("generate-video", {
prompt: "The snow leopard slowly turns its head toward the camera",
imageUrl: image.imageUrl,
provider: "seedance-2-fast",
duration: 4,
})
console.log(video.videoUrl)
```

`runAndWait`는 실행을 시작하고, 폴링하고, 작업의 출력으로 이행합니다. 이미지는 `imageUrl`, 동영상은 `videoUrl`, 오디오는 `audioUrl`입니다. 첫 번째 호출은 [Nano Banana 2](https://nodaro.ai/docs/models/image/nano-banana-2)로 [이미지 생성](https://nodaro.ai/docs/nodes/image/generate-image) 노드를 실행합니다. 두 번째 호출은 [Seedance 2 Fast](https://nodaro.ai/docs/models/video/seedance-2-fast)로 [동영상 생성](https://nodaro.ai/docs/nodes/video/generate-video) 노드를 실행해 그 이미지에 움직임을 줍니다.

노드의 기본 모델을 쓰려면 `provider`를 빼세요. 각 노드 페이지에 실행할 수 있는 모델과 크레딧 가격이 나와 있으며, 코드에서는 `client.models.list()`가 같은 카탈로그를 반환합니다. [모델과 크레딧](https://nodaro.ai/docs/developers/sdk/models-and-credits)을 참조하세요.

## 실행 방식
생성은 비동기로 진행됩니다. 요청은 워커에서 **작업**을 시작하고 바로 반환됩니다. 결과는 몇 초 또는 몇 분 뒤에 도착합니다.

| 호출 | 반환값 | 다음에 할 일 |
| --- | --- | --- |
| `client.nodes.run(type, params)` | `{ jobId }` | 상태가 `completed`, `failed`, `cancelled` 중 하나가 될 때까지 `client.jobs.getStatus(jobId)`를 폴링하세요. |
| `client.nodes.runAndWait(type, params, opts)` | 작업의 출력 | 할 일이 없습니다. SDK가 최대 15분 동안 2초마다 폴링합니다. |
| `client.nodes.runMany(type, paramsList, opts)` | 요청마다 `{ jobId, output }` 하나 | 할 일이 없습니다. 실행이 함께 시작되고 입력 순서대로 이행합니다. |
| `client.workflows.run(id)` | `{ executionId, status }` | 실행이 끝날 때까지 `client.executions.get(executionId)`를 폴링하세요. |

[텍스트 합치기](https://nodaro.ai/docs/nodes/automate/combine-text) 같은 몇몇 노드 유형은 인라인으로 실행되어 `jobId` 없이 결과를 바로 반환합니다. 모든 실행 메서드는 [노드 실행](https://nodaro.ai/docs/developers/sdk/nodes)에서, 상태는 [작업과 실행](https://nodaro.ai/docs/developers/sdk/jobs-and-executions)에서 설명합니다.

### 진행률 표시와 기다리기 중지
```ts

const controller = new AbortController()
stopButton.onclick = () => controller.abort()

try {
const clip = await client.nodes.runAndWait(
"generate-video",
{ prompt: "Waves roll onto a black sand beach", provider: "seedance-2-fast", duration: 4 },
{
signal: controller.signal,
onProgress: (status) => setProgressBar(status.progress ?? 0),
},
)
showVideo(clip.videoUrl)
} catch (err) {
if (err instanceof JobAbortedError && err.jobId) {
await client.jobs.cancel(err.jobId)
} else {
throw err
}
}
```

`onProgress`는 폴링할 때마다 작업 상태를 받으며, 모델이 알려 주는 경우 0~100의 `progress`가 들어 있습니다. 시그널을 중단하면 기다리기만 멈춥니다. 작업은 `client.jobs.cancel(jobId)`로 취소할 때까지 계속 실행되며, 취소하면 예약된 크레딧도 환불됩니다.

결과는 도착하는 즉시 하나씩 표시하세요. 2단계 흐름에서는 동영상 단계가 실행되는 동안 이미지를 먼저 보여 주세요.

## 인증 방식 선택
| 공급자 | 사용 시기 | 토큰 출처 |
| --- | --- | --- |
| `StaticTokenAuth` | 고정 토큰 하나를 쓰는 서버 코드 | API 토큰(`ndr_...`) 또는 OAuth 액세스 토큰(`ndr_app_...`) |
| `CallbackAuth` | 토큰을 직접 갱신하거나 교체할 때 | 매 요청 전에 호출되는 내 함수 |
| `supabaseAuth` | 사용자가 같은 Nodaro 인스턴스에 로그인하는 브라우저 앱 | 사용자의 현재 세션 |

모든 요청은 공급자에게 토큰을 요청하고, 그 토큰을 `Authorization: Bearer <token>`으로 보냅니다. 공급자가 `null`을 반환하면 요청은 헤더 없이 전송됩니다. 각 공급자와 브라우저 관련 규칙은 [인증](https://nodaro.ai/docs/developers/sdk/auth)을 참조하세요.

## 오류 처리
API가 오류로 응답하면 모든 메서드는 `NodaroError`의 타입 지정 하위 클래스를 발생시킵니다. 구체적인 클래스를 먼저 처리하고 `NodaroError`는 마지막에 처리하세요.

```ts

InsufficientCreditsError,
NodaroError,
RateLimitedError,
UnauthorizedError,
} from "@nodaro/sdk"

try {
await client.workflows.run(workflowId)
} catch (err) {
if (err instanceof InsufficientCreditsError) {
showPaywall({ required: err.required, available: err.available })
} else if (err instanceof UnauthorizedError) {
askForANewToken()
} else if (err instanceof RateLimitedError) {
retryLater()
} else if (err instanceof NodaroError) {
console.error(`API error ${err.status} (${err.code}): ${err.message}`)
} else {
throw err // a network failure, not an API answer
}
}
```

모든 `NodaroError`에는 `message`, `insufficient_credits` 같은 고정 `code`, HTTP `status`가 있습니다. 모든 클래스와 발생 시점은 [오류](https://nodaro.ai/docs/developers/sdk/errors)에 나와 있습니다.

## 자주 쓰는 예제
### 워크플로를 실행하고 기다리기
`client.workflows.run()`은 워크플로의 모든 노드를 한 번 실행하는 **실행**을 시작하고 바로 반환됩니다. 상태가 종료 상태가 될 때까지 `client.executions.get()`을 폴링하세요.

```ts
const { executionId } = await client.workflows.run(workflowId)

for (;;) {
const { data } = await client.executions.get(executionId)
console.log(`${data.completedNodes}/${data.totalNodes} nodes done`)

if (["completed", "failed", "cancelled", "timed_out"].includes(data.status)) {
if (data.status !== "completed") throw new Error(data.errorMessage ?? data.status)
console.log(`Done. Used ${data.totalCreditsUsed} credits.`)
break
}
await new Promise((resolve) => setTimeout(resolve, 2_000))
}
```

일부 노드만 실행하려면 두 번째 인수로 `{ nodeIds: [...] }`를 전달하세요. [워크플로와 프로젝트](https://nodaro.ai/docs/developers/sdk/workflows)를 참조하세요.

### 후보 여러 개를 한 번에 생성하기
```ts
const results = await client.nodes.runMany("generate-image", [
{ prompt: "A lighthouse at dawn, watercolor" },
{ prompt: "A lighthouse at dusk, watercolor" },
{ prompt: "A lighthouse in a storm, watercolor" },
])
for (const { jobId, output } of results) console.log(jobId, output.imageUrl)
```

`runMany`는 실행이 하나라도 실패하면 거부됩니다. 모델이 가장 좋은 결과를 고르게 하려면 URL을 [`client.reduce.run()`](https://nodaro.ai/docs/developers/sdk/llm-and-reduce)에 전달하세요.

이미지는 호출 한 번으로 같은 일을 할 수 있습니다. [`runBatchAndWait()`](https://nodaro.ai/docs/developers/sdk/nodes#runbatchandwaittype-params-opts)는 `count`(이미지 1~4장) 또는 `models`(이미지 모델 2~4개)를 받습니다. 일괄 처리는 전체로 청구되며, 이미지 하나가 실패해도 나머지는 버려지지 않습니다.

```ts
const { results } = await client.nodes.runBatchAndWait("generate-image", {
prompt: "A lighthouse at dusk, watercolor",
count: 4,
})
for (const r of results) if (r.status === "completed") console.log(r.output.imageUrl)
```

### 파일을 업로드해 사용하기
```ts
const upload = await client.uploads.upload(file) // a File, in the browser or Node.js
const portrait = await client.nodes.runAndWait("generate-image", {
prompt: "The same person as a watercolor portrait",
referenceImageUrls: [upload.url],
})
```

업로드는 공개 `url`을 반환하며, 이 URL은 이미지, 동영상, 오디오 URL을 받는 모든 노드에 전달할 수 있습니다. [미디어와 업로드](https://nodaro.ai/docs/developers/sdk/media-and-uploads)를 참조하세요.

### 실행 전에 가격 확인하기
```ts
const { total } = await client.credits.balance()
const { data: prices } = await client.credits.modelCosts(["nano-banana-pro", "nano-banana-pro:4K"])

if (total < prices["nano-banana-pro:4K"]) showPaywall()
```

가격과 잔액은 Nodaro Cloud에 있습니다. [모델과 크레딧](https://nodaro.ai/docs/developers/sdk/models-and-credits)과 [크레딧](https://nodaro.ai/docs/concepts/credits)을 참조하세요.

### 폴링 대신 작업 완료 알림 받기
https 엔드포인트를 한 번 등록하세요. 그 뒤로 Nodaro는 내 계정의 모든 `job.completed`와 `job.failed`에 대해 서명된 POST를 보내며, 서버가 2xx 상태로 응답할 때까지 다시 시도합니다.

```ts
const { data: endpoint, secret } = await client.webhooks.create({ url: "https://example.com/nodaro-webhook" })
// `secret` is shown only once: keep it with your server's configuration.
await client.webhooks.test(endpoint.id) // a ping; see client.webhooks.deliveries(endpoint.id)
```

받는 쪽에서는 본문을 신뢰하기 전에 원본 본문의 서명을 확인하세요.

```ts

const event = await verifyWebhookSignature({
payload: rawBody,
header: request.headers.get("nodaro-signature"),
secret: process.env.NODARO_WEBHOOK_SECRET!,
})
if (event.type === "job.completed") console.info(event.data.job.output_data)
```

이벤트, 본문, 재시도 일정은 [`client.webhooks`](https://nodaro.ai/docs/developers/sdk/jobs-and-executions#clientwebhooks)와 [Webhook](https://nodaro.ai/docs/developers/api/webhooks)을 참조하세요.

### 생성 전에 프롬프트 다듬기
```ts
const { prompt } = await client.promptHelper.enhance({
nodeType: "generate-image",
prompt: "snow leopard on a rock",
})
```

[프롬프트 마법사](https://nodaro.ai/docs/developers/sdk/pickers-and-prompts)는 대략적인 아이디어를 지정한 노드에 맞는 자세한 프롬프트로 다시 작성합니다. 호출할 때마다 크레딧이 듭니다.

## AI 코딩 어시스턴트와 함께 SDK 사용하기
- **Claude Code 플러그인.** `/plugin marketplace add nodaroai/app.nodaro.ai`를 실행한 다음 `/plugin install nodaro`를 실행하세요. 플러그인은 SDK의 패턴, 모델, 크레딧을 아는 스킬을 추가하고, Nodaro의 호스팅 MCP 서버를 연결합니다. [에이전트 스킬](https://nodaro.ai/docs/developers/agent-skills)을 참조하세요.
- **다른 어시스턴트.** npm의 패키지 README는 코딩 어시스턴트를 위해 쓴 짧은 입문 내용으로 시작합니다. 이 내용을 요청과 함께 Cursor나 다른 어시스턴트에 붙여 넣으세요.
- **코드 없이.** 어시스턴트가 나 대신 Nodaro를 실행하게 하려면 [MCP](https://nodaro.ai/docs/mcp)로 연결하세요.

## 참조
<Card title="클라이언트" href="/docs/developers/sdk/client" description="createClient, 모든 옵션, 워크스페이스, 전체 리소스 목록입니다." />
<Card title="인증" href="/docs/developers/sdk/auth" description="StaticTokenAuth, CallbackAuth, supabaseAuth와 공유 브라우저 세션입니다." />
<Card title="오류" href="/docs/developers/sdk/errors" description="모든 오류 클래스, 그 상태와 코드, 복구 방법입니다." />
<Card title="노드 실행" href="/docs/developers/sdk/nodes" description="타입이 지정된 매개변수와 함께 쓰는 run, runAndWait, runMany입니다." />
<Card title="워크플로와 프로젝트" href="/docs/developers/sdk/workflows" description="워크플로 만들기, 업데이트, 공유, 내보내기, 실행입니다." />
<Card title="작업과 실행" href="/docs/developers/sdk/jobs-and-executions" description="실행 폴링, 목록, 취소, 삭제입니다." />

SDK는 [REST API](https://nodaro.ai/docs/developers/api)와 같은 엔드포인트를 감쌉니다. 아직 SDK 메서드가 없는 엔드포인트는 `client.request()`로 호출하세요. 같은 인증과 타입 지정 오류가 그대로 유지됩니다.

## 자주 묻는 질문

### @nodaro/sdk는 무엇인가요?

Nodaro REST API의 공식 TypeScript 클라이언트입니다. 노드와 워크플로를 실행하고, 결과를 기다리고, 캐릭터, 보이스, 미디어 등을 관리합니다. 모든 호출에 타입이 지정된 메서드와 타입이 지정된 오류가 있습니다.

### Nodaro SDK는 어떻게 인증하나요?

Nodaro의 설정 › API 토큰에서 토큰을 만들고, 환경 변수에 저장한 다음, new StaticTokenAuth(token)으로 createClient에 전달하세요. 브라우저 앱과 OAuth 앱은 대신 supabaseAuth나 CallbackAuth를 사용합니다.

### SDK는 어떤 런타임을 지원하나요?

Node.js 20 이상, 그리고 최신 브라우저, React Native, Cloudflare Workers, Deno, Bun처럼 전역 fetch와 URL이 있는 모든 런타임을 지원합니다.

### 결과를 직접 폴링해야 하나요?

아니요. client.nodes.runAndWait가 실행을 시작하고, 작업을 2초마다 폴링하고, 출력 URL로 이행합니다. 완전히 제어해야 할 때만 client.jobs.getStatus로 직접 루프를 작성하세요.

### 셀프 호스팅 Nodaro 설치에서도 SDK를 사용할 수 있나요?

네. baseUrl을 내 인스턴스의 주소로 설정하세요. 스튜디오 프로덕션, Recast, 조직 같은 일부 리소스는 Nodaro Cloud에만 있으며, 다른 곳에서는 404로 응답합니다.
