# 모델 및 크레딧

> Nodaro 인스턴스가 제공하는 AI 모델을 나열하고, 내 크레딧 잔액을 확인하고, 실행 전에 모델 가격을 조회하고, 요금제와 내 구독을 읽는 방법을 안내합니다.

Source: https://nodaro.ai/ko/docs/developers/sdk/models-and-credits

<strong>`client.models`</strong>는 Nodaro 인스턴스가 제공하는 AI 모델 카탈로그를 반환하고, <strong>`client.credits`</strong>는 내 크레딧 잔액과 모든 모델의 가격을 반환합니다. <strong>`client.billing`</strong>은 판매 중인 요금제와 내 구독을 반환합니다. 이들을 함께 사용해 사용자가 고를 수 있는 모델과 실행마다 드는 비용을 보여 주세요. 이 메서드들은 [크레딧 REST API](https://nodaro.ai/docs/developers/api/credits)와 같은 엔드포인트를 호출합니다. 가격과 잔액은 Nodaro Cloud에만 있으며, 셀프 호스팅한 Community Edition과 Business 에디션 설치에는 크레딧 시스템이 없습니다. [크레딧](https://nodaro.ai/docs/concepts/credits)을 참고하세요.

## 메서드
| 메서드 | 기능 |
| --- | --- |
| [`models.list(opts?)`](#modelslistopts) | 모델을 종류와 제작사별로 묶어 나열합니다 |
| [`credits.balance()`](#creditsbalance) | 내 크레딧 잔액과 등급을 읽어 옵니다 |
| [`credits.modelCosts(ids)`](#creditsmodelcostsids) | 모델 또는 베리언트 최대 50개의 크레딧 가격을 조회합니다 |
| [`credits.transactions(options?)`](#creditstransactionsoptions) | 내 크레딧 거래 내역을 한 페이지씩 읽어 옵니다 |
| [`credits.claimWelcomeOffer(input?)`](#creditsclaimwelcomeofferinput) | 자사 웹 앱에서 환영 혜택을 수락합니다 |
| [`credits.markWelcomeOfferSeen()`](#creditsmarkwelcomeofferseen) | 환영 팝업이 표시되었음을 기록합니다 |
| [`billing.plans()`](#clientbilling) 외 | 판매 중인 요금제와 크레딧 팩, 내 구독, 배포 환경의 결제 방식을 읽어 옵니다 |

## client.models
### models.list(opts?)
모델 카탈로그를 종류(이미지, 동영상, 오디오)와 제작사별로 묶어 반환합니다(`GET /v1/models`). 각 모델에는 기능, Nodaro Cloud에서의 크레딧 가격, 짧은 프롬프트 팁이 있습니다. 이 엔드포인트는 공개되어 있으며, 서버는 응답을 5분 동안 캐시합니다. MCP `list_models` 도구도 같은 데이터를 반환합니다.

```ts
list(opts?: {
kind?: "image" | "video" | "audio"
mode?: string
family?: string
featuredOnly?: boolean
}): Promise<ModelsListResult>
```

<TypeTable
type={{
kind: { type: '"image" | "video" | "audio"', description: "이 종류의 모델만 반환합니다." },
mode: { type: 'string', description: "이 모드를 지원하는 모델만 반환합니다. 예를 들면 t2i(텍스트로 이미지), t2v(텍스트로 동영상), i2v(이미지로 동영상)입니다." },
family: { type: 'string', description: "이 제작사의 모델만 반환합니다. 예를 들면 Google입니다." },
featuredOnly: { type: 'boolean', default: 'false', description: "추천 모델만 반환합니다." },
}}
/>

```ts
const catalog = await client.models.list({ kind: "video", mode: "i2v" })

for (const section of catalog.sections) {
for (const family of section.families) {
for (const model of family.models) {
console.log(family.family, model.id, model.durations, model.pricing?.[0]?.credits)
}
}
}
```

결과에는 종류마다 하나씩 있으며 각각 모델의 `families`를 담은 `sections`, 자주 쓰는 용도별 모델 ID 목록인 `recommendations`, 그리고 `totalModels`가 있습니다. 각 모델에는 다음 필드가 있습니다.

| 필드 | 타입 | 설명 |
| --- | --- | --- |
| `id` | `string` | 모델 ID로, 노드 실행의 `provider` 매개변수에 씁니다. |
| `label`, `description` | `string` | 표시 이름과 짧은 설명입니다. |
| `modes` | `string[]` | `t2i`, `i2i`, `t2v`, `i2v`처럼 모델이 하는 일입니다. |
| `useCases` | `string[]` | 모델에 맞는 용도입니다. |
| `aspectRatios`, `resolutions`, `qualities`, `durations` | 배열 | 해당하는 경우, 모델이 받는 값입니다. |
| `features` | `string[]` | 추가 기능입니다. |
| `pricing` | `{ identifier, credits, note? }[]` | 각 베리언트의 크레딧 가격입니다. 실행에 청구되는 가격으로, **실행** 버튼의 금액과 같습니다. Nodaro Cloud 전용입니다. |
| `featured` | `boolean` | 추천 모델인지 여부입니다. |
| `promptTips` | `string[]` | 모델에 맞는 짧은 프롬프트 작성 조언입니다. |
| `doctrineCovered` | `boolean` | Nodaro가 모델 계열의 프롬프트 가이드를 출처와 함께 갖춘 경우에만 `true`입니다. “제작사 가이드” 배지는 이 값이 `true`일 때만 표시하세요. |
| `maker` | `{ id, name }` | 모델을 만든 회사입니다. `id`는 회사마다 하나인 안정적인 값으로, `"google"`이나 `"black-forest-labs"` 같은 것이므로 내 에셋의 키로 쓸 수 있습니다. 새 서버는 제작사를 추가할 수 있으므로, 모르는 ID도 처리하세요. 제작사가 모델이 묶인 `family`와 항상 같지는 않습니다. |
| `logoUrl` | `string \| null` | 제작사의 공식 로고로, 인스턴스 자체 미디어 CDN의 SVG입니다. 인스턴스가 그 제작사의 로고를 게시하지 않으면 `null`이므로, 대신 제작사의 이름이나 첫 글자를 그리세요. 로고가 바뀌면 새 주소로 오므로 주소를 받은 그대로 쓰세요. MCP 도구는 이 필드를 생략합니다. |

오래된 서버는 `maker`와 `logoUrl`을 생략합니다.

```ts
for (const section of catalog.sections)
for (const family of section.families)
for (const m of family.models)
console.log(m.id, m.maker?.name ?? family.family, m.logoUrl ?? "(no mark)")
```

같은 카탈로그가 [모델](https://nodaro.ai/docs/models) 페이지에도 있습니다. 어떤 모델을 쓸지에 대한 조언은 [모델 선택](https://nodaro.ai/docs/guides/choosing-models)을 읽어 보세요.

## client.credits
### credits.balance()
내 크레딧 잔액과 등급을 반환합니다(`GET /v1/user/credits`). 로그인한 사용자가 없으면 `UnauthorizedError`를 발생시킵니다.

```ts
balance(): Promise<UserBalance>
```

```ts
const balance = await client.credits.balance()
console.log(`${balance.total} credits (${balance.effectiveTier})`)
```

| 필드 | 타입 | 설명 |
| --- | --- | --- |
| `total` | `number` | 지금 사용할 수 있는 크레딧입니다. |
| `subscription` | `number` | 이번 달에 남은 구독 크레딧입니다. 연간 요금제에서도 매달 `monthlyAllocation`으로 초기화되며, 쓰지 않은 크레딧은 이월되지 않습니다. |
| `topup` | `number` | 따로 구매한 크레딧입니다. |
| `dailySpent` | `number` | 오늘 사용한 크레딧입니다. |
| `dailyLimit` | `number \| null` | 일일 사용 한도이며, 한도가 없으면 `null`입니다. |
| `monthlyAllocation` | `number` | 월간 결제와 연간 결제 모두에서 요금제가 매달 지급하는 크레딧입니다. |
| `tier` | `string` | `"free"`나 `"pro"`처럼 저장된 구독 등급입니다. |
| `effectiveTier` | `string` | 실제로 적용되는 등급입니다. `"payg"`는 종량제를 뜻합니다. 구독은 없지만 구매한 크레딧이 있으며, 모든 모델을 쓸 수 있고 워터마크와 일일 한도가 없습니다. |
| `features` | `Record<string, unknown>` | 등급의 기능입니다. |
| `periodEnd` | `string \| null` | 현재 결제 기간이 끝나는 날짜로, ISO 8601 날짜입니다. 연간 요금제에서는 1년 뒤이지만, 구독 크레딧은 여전히 매달 초기화됩니다. |
| `appCreditsAllowance` | `number` | 무료 등급에서 앱을 실행해 얻은 크레딧입니다. |
| `freeGrantState` | `FreeGrantState` | 가입 무료 지급의 상태로, `"unclaimed"`, `"granted"`, `"withheld"`, `"revoked"` 중 하나입니다. `"withheld"`는 계정은 작동하지만 지급이 이루어지지 않았다는 뜻이므로 활성화 방법을 보여 주세요. 배포 환경에 가입 지급이 없으면 없습니다. |
| `freeGrantNotice` | `"browser_unmeasured"` | `"withheld"`일 때만 있으며, 지급 요청에서 브라우저 신호가 전혀 보이지 않았을 때입니다. 앱에서 무료 크레딧을 받으려면 일반 브라우저와 새로고침이 필요하다고 안내할 수 있습니다. |
| `welcomeOffer` | `WelcomeOfferState` | 배포 환경에서 환영 혜택이 켜져 있는 동안에만 있습니다. 없으면 팝업, 배너, 차단이 없다는 뜻입니다. [`claimWelcomeOffer()`](#creditsclaimwelcomeofferinput)를 참고하세요. |
| `externalWallet` | `{ available: number \| null }` | 배포가 공유 외부 지갑을 쓸 때 있습니다. `null`은 금액을 알 수 없다는 뜻입니다. 그 자리에 `total`을 표시하지 마세요. [외부 지갑](https://nodaro.ai/docs/developers/external-wallet)을 참고하세요. |

무엇을 표시할지 정할 때는 `tier`보다 `effectiveTier`를 우선하세요.

### credits.modelCosts(ids)
모델과 그 베리언트의 크레딧 가격을 한 번의 호출로 조회합니다(`POST /v1/credits/model-costs`). 앞에서부터 최대 50개의 식별자를 확인합니다.

```ts
modelCosts(ids: string[]): Promise<{
data: Record<string, number>
missing: string[]
errors: string[]
}>
```

<TypeTable
type={{
ids: { type: 'string[]', required: true, description: "가격 식별자입니다. nano-banana-pro 같은 모델 ID, 또는 nano-banana-pro:4K나 seedance-2-fast:8s:720p처럼 베리언트를 붙인 모델 ID입니다." },
}}
/>

```ts
const { data, missing } = await client.credits.modelCosts([
"nano-banana-pro",
"nano-banana-pro:4K",
"seedance-2-fast:8s:720p",
])
console.log(data["nano-banana-pro:4K"])
if (missing.length) console.warn("No price for:", missing)
```

- <strong>`data`</strong>는 가격이 있는 각 식별자를 크레딧 가격에 대응시킵니다.
- <strong>`missing`</strong>은 가격이 없는 식별자를 나열합니다. 이런 식별자에는 대시를 표시하세요.
- <strong>`errors`</strong>는 조회에 실패한 식별자를 나열합니다. 나머지 가격은 그대로 전달됩니다.

각 모델의 식별자는 [`models.list()`](#modelslistopts)의 `pricing` 필드와 각 모델 페이지에 있습니다. 품질, 해상도, 길이 같은 설정이 가격을 바꾸므로, 실행에 쓸 베리언트를 지정하세요. 실행 시점의 가격은 실행이 시작될 때 다시 확인되므로, 이 호출은 미리 보기입니다.

음성 모델은 텍스트 길이에 따라 가격이 정해집니다. 시작된 100자 블록마다의 식별자는 `<model>:per-100-chars`입니다. 예: `elevenlabs-v4:per-100-chars`. 음성을 길이로 가격 매기지 않는 설치에서는 이 식별자가 `missing`에 들어가므로, 응답으로 서버가 어떤 규칙으로 청구하는지도 알 수 있습니다.

요청은 전체로 가격을 계산한 뒤 한 번만 반올림합니다. 따라서 시작된 블록이 N개인 요청의 정확한 가격은 식별자 `<model>:per-100-chars:<N>u`이며, N은 8부터 모델의 요청당 상한까지입니다. N에 블록 가격을 곱한 값은 이보다 클 수 있습니다. 단위별로 청구되는 모든 실행은 같은 형태의 id로 가격을 확인합니다. `<row>:<N>u`는 그 행의 N단위 정확한 가격입니다. 예: `apply-edl:4u`, `trim-video:5u`, `ltx-2.3-pro-retake:per-second:2.5u`, `elevenlabs-dubbing:3u`. 보이스 체인저 Pro 변환 전체는 `voice-changer-pro:<slot>,<slot>,…`이며, `@nodaro/shared`의 `voiceChangerProQuoteCreditId(slots)`로 만듭니다. [단위별로 가격이 매겨지는 실행의 정확한 가격](https://nodaro.ai/docs/developers/api/credits#exact-prices-of-runs-priced-per-unit)을 참고하세요.

### credits.transactions(options?)
내 크레딧 거래 내역 한 페이지를 최신순으로 반환합니다(`GET /v1/credits/transactions`). Cloud 에디션 전용이며, 셀프 호스팅 설치는 404로 응답합니다.

```ts
transactions(options?: { limit?: number; cursor?: string }): Promise<CreditTransactionsPage>
```

<TypeTable
type={{
limit: { type: 'number', description: "페이지당 행 수로, 1~50입니다.", default: '20' },
cursor: { type: 'string', description: "다음 페이지를 읽기 위한 이전 페이지의 nextCursor입니다." },
}}
/>

페이지는 `{ data: CreditTransaction[], nextCursor }`입니다. 더 읽을 행이 없으면 `nextCursor`는 `null`입니다.

| 필드 | 형식 | 설명 |
| --- | --- | --- |
| `id` | `string` | 거래 ID입니다. |
| `created_at` | `string` | ISO 8601 날짜입니다. |
| `credits_charged` | `number \| null` | 정산된 뒤 실행에 든 비용입니다. 홀드보다 적을 수 있고, 홀드가 환불되면 `0`이며, 홀드가 아직 열려 있는 동안에는 `null`입니다. |
| `credits_reserved` | `number \| null` | 실행이 접수될 때 잡아 둔 홀드입니다. 바뀌지 않습니다. |
| `credits_used` | `number \| null` | 같은 홀드의 원래 이름으로, 기존 연동을 위해 유지됩니다. `credits_charged`를 쓰세요. |
| `action` | `string \| null` | 크레딧이 쓰인 모델 또는 작업입니다. |
| `status` | `string \| null` | `"reserved"`, `"committed"` 또는 `"refunded"`입니다. |
| `metadata` | `Record<string, unknown>` | 실행 자체의 청구 세부 정보입니다. |
| `payer` | `"user" \| "workspace"` | `"workspace"`는 내 잔액이 아니라 수업 또는 팀 예산에서 냈다는 뜻입니다. |
| `workspaceId` | `string \| null` | `payer`가 `"workspace"`일 때 비용을 낸 워크스페이스입니다. |

**어떤 금액을 읽을지.** 실행은 먼저 크레딧을 홀드한 뒤 정산됩니다. 홀드의 전부 또는 일부가 청구되고, 나머지는 잔액으로 돌아옵니다. 지출을 알려면 `credits_charged`를 더하세요. [크레딧](https://nodaro.ai/docs/developers/api/credits)을 참고하세요.

```ts
let cursor: string | undefined
let spent = 0
do {
const page = await client.credits.transactions({ limit: 50, cursor })
for (const t of page.data) spent += t.credits_charged ?? 0
cursor = page.nextCursor ?? undefined
} while (cursor)
console.log(`${spent} credits charged`)
```

### credits.claimWelcomeOffer(input?)
환영 혜택에 대한 사용자의 “예”를 한 번에 기록합니다(`POST /v1/credits/welcome-offer/claim`). 마케팅 이메일 수신 동의를 기록한 다음, 그 동의와 함께 가입 무료 지급을 요청합니다. 결정은 여전히 악용 검사가 내리므로 `grant`를 읽으세요. 지급이 이미 결정된 계정은 동의만 기록됩니다. Nodaro Cloud 전용이며, 셀프 호스팅 인스턴스는 404로 응답합니다.

```ts
claimWelcomeOffer(input?: WelcomeOfferClaimInput): Promise<WelcomeOfferClaimResult>
```

**이 메서드에는 [`supabaseAuth()`](https://nodaro.ai/docs/developers/sdk/auth#supabaseauthsupabase)로 로그인한 브라우저 세션이 필요합니다.** 동의는 사람의 선택이기 때문입니다. API 토큰이나 OAuth 앱 토큰은 `403`을 받습니다. 배포 환경에서 혜택이 꺼져 있는 동안에는 호출이 `409 welcome_offer_disabled`로 응답하므로, `balance().welcomeOffer`가 있을 때만 혜택을 보여 주세요.

`WelcomeOfferClaimInput`은 `collectSignupKeys()`가 반환한 `SignupKeys`에 `sourceApp`을 더한 것입니다. `sourceApp`은 소문자, 숫자, 대시로 된 최대 40자의 내 앱 슬러그입니다. 형식이 잘못된 슬러그는 거부되지 않고 없는 것으로 저장됩니다.

| 필드 | 타입 | 설명 |
| --- | --- | --- |
| `consent` | `"granted"` | 이메일 수신 동의가 기록되었습니다. |
| `grant` | `FreeGrantState` | `"granted"`: 지금 또는 이전에 지급되었습니다. `"withheld"`: 악용 검사가 지급을 거부했습니다. `"revoked"`: 이전에 지급이 회수되었습니다. `"unclaimed"`: 아무것도 바뀌지 않았으므로 실패로 처리하고 사용자가 다시 시도하게 하세요. |
| `credits` | `number` | 지급 크레딧 수로, `balance().welcomeOffer.credits`와 같습니다. 지급은 구독 크레딧을 이 수까지 채웁니다. 그 위에 더해지지 않습니다. |

`WelcomeOfferState`에는 필드가 세 개 있습니다. `popupSeen`(일회성 팝업이 표시됨), `consentPending`(계정에 크레딧이 있지만 아직 이메일 수신 동의가 남아 있음), `credits`(요청 시 지급되는 크레딧)입니다. 혜택에는 고정된 숫자 대신 `credits` 값을 보여 주세요. 이 값은 서버를 따릅니다.

### credits.markWelcomeOfferSeen()
일회성 환영 팝업이 표시되었음을 기록하므로(`POST /v1/credits/welcome-offer/seen`), 모든 앱에서 `welcomeOffer.popupSeen`이 `true`가 됩니다. 다시 호출해도 아무것도 바뀌지 않습니다. 호출이 실패해도 팝업이 한 번 더 표시될 수 있을 뿐이므로 오류는 무시해도 됩니다. 규칙은 `claimWelcomeOffer()`와 같습니다.

```ts
markWelcomeOfferSeen(): Promise<{ ok: true }>
```

```ts

const { welcomeOffer } = await client.credits.balance()
if (welcomeOffer && !welcomeOffer.popupSeen) {
showPopup({ credits: welcomeOffer.credits }) // the server's figure, never a constant
void client.credits.markWelcomeOfferSeen().catch(() => {})
}

// On "Yes":
const keys = await collectSignupKeys()
const { grant, credits } = await client.credits.claimWelcomeOffer({ ...keys, sourceApp: "my-app" })
if (grant === "granted") showToast(`Your ${credits} free credits are ready`)
```

## client.billing
<strong>`client.billing`</strong>은 판매 중인 요금제와 가격, 호출자의 구독, 배포 환경의 결제 방식을 읽습니다. 구매, 요금제 변경, 결제 설정은 Nodaro 앱에서 합니다. 그 경로는 API 토큰과 앱 토큰을 거부하므로 SDK에는 해당 메서드가 없습니다. 대신 사용자를 앱의 요금제 페이지로 안내하세요.

```ts
// A pricing screen: works signed out.
const { data: plans, topUps } = await client.billing.plans()

// Mark the plan the signed-in account is on.
const { effectiveTier } = await client.billing.subscription()
const current = plans.find((p) => p.id === effectiveTier) // undefined: pay-as-you-go, or a tier not on sale
```

| 메서드 | 반환 | 참고 |
| --- | --- | --- |
| `plans()` | `Promise<BillingPlansResult>` | `GET /v1/billing/plans`. **공개**: 자격 증명이 필요 없으며, 모든 호출자에게 같은 응답을 줍니다. `{ data: BillingPlan[], topUps, topUpRange }`를 반환합니다. Nodaro Cloud 전용이며, 셀프 호스팅 인스턴스는 `NotFoundError`를 발생시킵니다. 사용자에게 판매하지 않는 배포 환경은 `ForbiddenError`를 발생시킵니다. 잠시 가격을 읽을 수 없으면 상태 503의 `NodaroError`를 발생시킵니다. |
| `subscription()` | `Promise<BillingSubscriptionResult>` | `GET /v1/billing/subscription`. `{ data: BillingSubscription \| null, effectiveTier }`를 반환합니다. 계정이 구독한 적이 없으면 `data`는 `null`입니다. `effectiveTier`는 계정의 권한이 따르는 등급입니다. 보통 요금제 `id`이며, 구독 없이 크레딧을 구매했으면 `"payg"`, 또는 `"enterprise"`처럼 판매하지 않는 등급입니다. 브라우저 세션, 워크플로 제한이 없는 개인 API 토큰, 또는 OAuth 앱 토큰이 필요합니다. Nodaro Cloud 전용입니다. |
| `surface()` | `Promise<BillingSurface>` | `GET /v1/billing/surface`. 공개이며, 모든 에디션에 있습니다. 배포 환경이 사용량을 측정하는지(측정하지 않으면 `providerId`가 `"none"`), 비용을 어떤 단위로 표시하는지(`displayUnit`) 알려 줍니다. |
| `account()` | `Promise<BillingAccountSummary \| null>` | `GET /v1/billing/account`. 배포 환경의 결제 공급자가 보고하는 호출자의 `plan`, `balance`, `dailyAllowance`이며, 그 공급자의 `unit`으로 표시됩니다. 공급자가 응답하지 못하면 `null`이며, 이는 잔액 0이 아닙니다. 모든 에디션에 있습니다. |

`BillingPlan`에는 다음 필드가 있습니다.

| 필드 | 타입 | 설명 |
| --- | --- | --- |
| `id` | `string` | `"free"`, `"basic"`, `"standard"`, `"pro"`, `"business"` 중 하나입니다. |
| `name` | `string` | 표시 이름입니다. |
| `prices` | `{ monthly, yearly } \| null` | 무료 요금제는 `null`입니다. `monthly`는 `{ amount, currency, credits }`입니다. `yearly`에는 연간 금액을 개월 수로 나눈 `perMonth`가 더해집니다. `amount`는 통화의 기본 단위이며(`12`는 12.00), `currency`는 `"USD"` 같은 ISO 4217 코드입니다. 지금 판매할 수 없는 가격은 `null`일 수 있으므로, 무료가 아니라 이용할 수 없음으로 표시하세요. |
| `prices.*.credits` | `{ amount, per } \| null` | 가격으로 지급되는 크레딧이며, `per`는 `"month"` 또는 `"year"`입니다. 연간 가격도 월간 가격처럼 요금제의 크레딧을 매달 지급합니다. |
| `signupCredits` | `number \| null` | 무료 요금제에서 가입할 때 한 번 지급되는 크레딧입니다. 유료 요금제에서는 `null`입니다. |
| `parallelism` | `number` | 워크플로 하나에서 동시에 실행되는 노드 수입니다. |
| `storageBytes` | `number` | 내 미디어의 저장 공간으로, 바이트 단위입니다. |

`topUps`는 크레딧 팩으로, `{ id, credits, price }`이며 작은 것부터 나옵니다. 지금 가격을 알릴 수 없으면 `price`는 `null`입니다. `topUpRange`(`{ min, max, currency }`)는 앱에서 팩과 같은 비율로 크레딧으로 충전할 수 있는 정수 금액의 범위입니다. 요금제 없이 크레딧을 구매하는 방법은 [구독 없는 API](https://nodaro.ai/docs/developers/pay-as-you-go)를 참고하세요.

## 모델과 가격 표시
모델마다 옆에 가격을 보여 주는, 노드 하나용 모델 선택기를 만드세요.

```ts
const { data: node } = await client.nodes.get("generate-video")
const { data: prices } = await client.credits.modelCosts(node.providers ?? [])

const options = (node.providers ?? []).map((id) => ({
id,
price: prices[id] ?? null, // null: no price on this instance
}))
```

노드의 기본 모델을 쓰려면 실행에서 `provider`를 생략하세요. 사용자가 모델을 고르면, 그 ID를 `provider`로 [`client.nodes.runAndWait()`](https://nodaro.ai/docs/developers/sdk/nodes)에 보내세요.

## 자주 묻는 질문

### 코드에서 Nodaro가 제공하는 모델을 나열하려면 어떻게 하나요?

client.models.list()를 호출하세요. 모든 이미지, 동영상, 오디오 모델을 종류와 제작사별로 묶어 반환하며, 모드, 화면 비율, 해상도, 길이와 함께 Nodaro Cloud에서는 크레딧 가격도 알려 줍니다.

### 실행하기 전에 모델 하나의 가격을 알아보려면 어떻게 하나요?

nano-banana-pro나 nano-banana-pro:4K 같은 모델의 가격 식별자를 넣어 client.credits.modelCosts를 호출하세요. data 필드가 각 식별자를 크레딧 가격에 대응시킵니다.

### SDK로 내 크레딧 잔액을 확인하려면 어떻게 하나요?

client.credits.balance()를 호출하세요. total 필드는 사용할 수 있는 크레딧 수이며, 구독 크레딧과 충전 크레딧으로 나뉩니다.

### SDK로 내 실행 비용을 합산하려면 어떻게 하나요?

각 페이지의 nextCursor로 client.credits.transactions()를 차례로 읽으며 credits_charged를 더하세요. 실행이 먼저 잡아 두는 credits_reserved는 실제로 낸 금액이 아닙니다.

### 내 앱에 Nodaro의 요금제와 가격을 보여 주려면 어떻게 하나요?

client.billing.plans()를 호출하세요. 로그인이 필요 없으며, 모든 요금제를 월간 및 연간 가격, 크레딧과 함께 반환하고 크레딧 팩도 반환합니다. 요금제 구매는 Nodaro 앱에서 합니다.

### 셀프 호스팅 설치에도 크레딧 가격이 있나요?

아니요. 셀프 호스팅한 Community Edition과 Business 에디션 설치에는 크레딧 시스템이 없으므로, 응답에 모델 가격과 크레딧 비용이 포함되지 않습니다.
